/**
 * Copyright (c) 2006, yher2.net
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * * Neither the name of the nor the names of its contributors may be used to endorse or 
 *   promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
 * SUCH DAMAGE.
 */
package net.yher2.workstyle.manager;

import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.yher2.commons.lang.NumberUtils;
import net.yher2.commons.lang.Pager;
import net.yher2.workstyle.FileData;
import net.yher2.workstyle.TaskQuery;
import net.yher2.workstyle.exception.NotFoundException;
import net.yher2.workstyle.torque.AttachedFilePeer;
import net.yher2.workstyle.torque.Status;
import net.yher2.workstyle.torque.StatusPeer;
import net.yher2.workstyle.torque.Tag;
import net.yher2.workstyle.torque.Task;
import net.yher2.workstyle.torque.TaskComment;
import net.yher2.workstyle.torque.TaskCommentPeer;
import net.yher2.workstyle.torque.TaskPeer;
import net.yher2.workstyle.torque.TaskRelation;
import net.yher2.workstyle.torque.TaskRelationPeer;
import net.yher2.workstyle.torque.bean.TaskBean;
import net.yher2.workstyle.torque.bean.TaskCommentBean;

import org.apache.commons.lang.StringUtils;
import org.apache.torque.NoRowsException;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;

public class TaskManager {
	Connection con = null;

	public TaskManager(Connection con) {
		this.con = con;
	}

	public List<TaskBean> search(TaskQuery query, Pager pager) throws TorqueException {
		Criteria criteria = new Criteria();
		setSearchCriteria(criteria, query);
		if (pager != null) {
			Criteria criteriaPager = new Criteria();
			setSearchCriteria(criteriaPager, query);
			pager.setRowCount(TaskPeer.doSelectCount(criteriaPager, con));
			
			criteria.setLimit(pager.getLimit());
			criteria.setOffset(pager.getOffset());
		}
		setSearchOrder(query, criteria);
		
		List<Task> taskList = TaskPeer.doSelectJoinStatus(criteria, con);
		
		return toBeanList(taskList);
	}

//	public List<TaskBean> searchUnlinkedTask(int taskId, TaskQuery query, Pager pager) throws TorqueException {
//		Criteria criteria = new Criteria();
//		setSearchUnlinkedTaskCriteria(criteria, query, taskId);
//		if (pager != null) {
//			Criteria criteriaPager = new Criteria();
//			setSearchUnlinkedTaskCriteria(criteriaPager, query, taskId);
//			pager.setRowCount(TaskPeer.doSelectCount(criteriaPager, con));
//			
//			criteria.setLimit(pager.getLimit());
//			criteria.setOffset(pager.getOffset());
//		}
//		setSearchOrder(query, criteria);
//		
//		List<Task> taskList = TaskPeer.doSelectJoinStatus(criteria, con);
//		
//		return toBeanList(taskList);
//	}
	
//	private void setSearchUnlinkedTaskCriteria(Criteria criteria, TaskQuery query, int taskId) throws TorqueException {
//		List<Integer> idList = getLinkedTaskIdList(taskId);
//		if (idList.size() > 0) {
//			criteria.addNotIn(TaskPeer.TASK_ID, idList);
//		}
//		setSearchCriteria(criteria, query);
//	}

	private void setSearchOrder(TaskQuery query, Criteria criteria) {
		switch (query.getOrder()) {
		case TaskQuery.ORDER_BY_STATUS_ASC:
			criteria.addAscendingOrderByColumn(StatusPeer.SORT_ORDER);
			break;
		case TaskQuery.ORDER_BY_STATUS_DESC:
			criteria.addDescendingOrderByColumn(StatusPeer.SORT_ORDER);
			break;
		case TaskQuery.ORDER_BY_ID_ASC:
			criteria.addAscendingOrderByColumn(TaskPeer.TASK_ID);
			break;
		case TaskQuery.ORDER_BY_ID_DESC:
			criteria.addDescendingOrderByColumn(TaskPeer.TASK_ID);
			break;
		case TaskQuery.ORDER_BY_DATE_ASC:
			criteria.addAscendingOrderByColumn(TaskPeer.UPDATE_DATE);
			break;
		case TaskQuery.ORDER_BY_DATE_DESC:
			criteria.addDescendingOrderByColumn(TaskPeer.UPDATE_DATE);
			break;
		case TaskQuery.ORDER_BY_MAN_HOUR_ASC:
			criteria.addAscendingOrderByColumn(TaskPeer.ESTIMATED_MAN_HOUR);
			break;
		case TaskQuery.ORDER_BY_MAN_HOUR_DESC:
			criteria.addDescendingOrderByColumn(TaskPeer.ESTIMATED_MAN_HOUR);
			break;
		}
		criteria.addAscendingOrderByColumn(StatusPeer.SORT_ORDER);
		criteria.addAscendingOrderByColumn(TaskPeer.TASK_ID);
		criteria.addAscendingOrderByColumn(TaskPeer.UPDATE_DATE);
		criteria.addAscendingOrderByColumn(TaskPeer.ESTIMATED_MAN_HOUR);
	}

	private List<TaskBean> toBeanList(List<Task> taskList) {
		List<TaskBean> beanList = new ArrayList<TaskBean>();
		for (Task task : taskList) {
			beanList.add(task.getBean());
		}
		return beanList;
	}
	
	public List<TaskBean> searchDetail(TaskQuery query, Pager pager) throws TorqueException {
		List<TaskBean> beanList = search(query, pager);
		if (beanList.size() == 0) return beanList;
		
		List<Integer> taskIdList = new ArrayList<Integer>();
		Map<Integer, TaskBean> taskMap = new HashMap<Integer, TaskBean>();
		for (TaskBean task : beanList) {
			taskIdList.add(task.getTaskId());
			taskMap.put(task.getTaskId(), task);
			if (task.getTaskCommentBeans() == null) {
				task.setTaskCommentBeans(new ArrayList<TaskCommentBean>());
			}
		}
		
		Criteria criteria = new Criteria();
		criteria.addIn(TaskCommentPeer.TASK_ID, taskIdList);
		criteria.addAscendingOrderByColumn(TaskCommentPeer.TASK_ID);
		criteria.addAscendingOrderByColumn(TaskCommentPeer.UPDATE_DATE);
		
		List<TaskComment> commentList = TaskCommentPeer.doSelect(criteria, con);
		for (TaskComment comment : commentList) {
			TaskCommentBean bean = comment.getBean();
			taskMap.get(bean.getTaskId()).getTaskCommentBeans().add(bean);
		}
		
		return beanList;
	}
	
	private void setSearchCriteria(Criteria criteria, TaskQuery query) throws TorqueException {
		criteria.addJoin(TaskPeer.STATUS_ID, StatusPeer.STATUS_ID);
		if (query.hasUnlinkedTaskId()) {
			List<Integer> idList = getLinkedTaskIdList(query.getUnlinkedTaskId());
			idList.add(query.getUnlinkedTaskId());
			criteria.addNotIn(TaskPeer.TASK_ID, idList);
		}
		if (query.hasStatus()) {
			criteria.addIn(StatusPeer.STATUS_ID, query.getStatus());
		}
		if (query.hasKeyword()) {
			List<String> keywordList = query.getKeywordAsList(); 
			for (String keyword : keywordList) {
				String likeKeyword = "%" + keyword + "%";
				Criteria.Criterion	criterion = criteria.getNewCriterion(TaskPeer.CONTENTS, likeKeyword, Criteria.LIKE);
				criterion.or(criteria.getNewCriterion(TaskPeer.TAG_LIST, likeKeyword, Criteria.LIKE));
				if (NumberUtils.isInteger(keyword))
					criterion.or(criteria.getNewCriterion(TaskPeer.TASK_ID, Integer.parseInt(keyword), Criteria.EQUAL));

				Criteria.Criterion exist = criteria.getCriterion(TaskPeer.CONTENTS);
				if (exist == null) {
					criteria.add(criterion);
				} else {
					exist.and(criterion);
				}
			}
		}
		if (query.hasTag()) {
			for (String tag : query.getTag()) {
				tag = "%" + Tag.format(tag) + "%";
				Criteria.Criterion criterion = criteria.getNewCriterion(TaskPeer.TAG_LIST, tag, Criteria.LIKE);
				Criteria.Criterion exist = criteria.getCriterion(TaskPeer.TAG_LIST);
				if (exist == null) {
					criteria.add(criterion);
				} else {
					exist.and(criterion);
				}
			}
		}
		if (query.hasExcludeTag()) {
			Criteria.Criterion excludeCriterion = null;
			for (String tag : query.getExcludeTag()) {
				tag = "%" + Tag.format(tag) + "%";
				Criteria.Criterion criterion = criteria.getNewCriterion(TaskPeer.TAG_LIST, tag, Criteria.NOT_LIKE);
				
				if (excludeCriterion == null) {
					excludeCriterion = criterion;
				} else {
					excludeCriterion.and(criterion);
				}
			}
			excludeCriterion.or(criteria.getNewCriterion(TaskPeer.TAG_LIST, null, Criteria.EQUAL));
			Criteria.Criterion exist = criteria.getCriterion(TaskPeer.TAG_LIST);
			if (exist == null) {
				criteria.add(excludeCriterion);
			} else {
				exist.and(excludeCriterion);
			}
		}
		if (query.hasDateFrom()) {
			criteria.add(TaskPeer.UPDATE_DATE, query.getDateFrom(), Criteria.GREATER_EQUAL);
		}
	}
	
	public TaskBean add(TaskBean task, FileData file) throws TorqueException, NotFoundException, IOException {
		TaskBean taskBean = add(task);
		if (file != null) new FileManager(con).add(taskBean.getTaskId(), file);
		return taskBean;
	}
	public TaskBean add(TaskBean task) throws TorqueException {
		task.setStatusId(getValidStatusId(task.getStatusId()));
		
		Task dbTask = new Task();
		dbTask.setBean(task);
		dbTask.setUpdateDate(new Date());
		
		TaskPeer.doInsert(dbTask, con);
		
		new TagManager(con).add(task.getTagAsList());
		
		return dbTask.getBean();
	}
	public TaskBean addComment(int taskId, TaskCommentBean comment) throws TorqueException, NotFoundException, IOException {
		TaskBean task = update(getBean(taskId));
		if (comment != null) {
			comment.setTaskId(task.getTaskId());
			new TaskCommentManager(con).add(comment);
		}
		return task;
	}
	public TaskBean addFile(int taskId, FileData file) throws TorqueException, NotFoundException, IOException {
		TaskBean task = update(getBean(taskId));
		if (file != null) new FileManager(con).add(task.getTaskId(), file);
		return task;
	}
	public TaskBean addLinkedTask(int parentTaskId, TaskBean task) throws NotFoundException, TorqueException {
		TaskBean result = add(task);
		try {
			link(parentTaskId, result.getTaskId());
		} catch (NotFoundException e) {
		}
		return result;
	}
	
	public void link(int parentTaskId, int taskId) throws NotFoundException, TorqueException {
		if (parentTaskId == taskId) return;
		List<Integer> idList = getLinkedTaskIdList(parentTaskId);
		if (idList.contains(taskId)) return;
		
		Task src = getDBModel(parentTaskId);
		Task dest = getDBModel(taskId);
		
		TaskRelation relation = new TaskRelation();
		relation.setSrcTaskId(src.getTaskId());
		relation.setDestTaskId(dest.getTaskId());
		TaskRelationPeer.doInsert(relation, con);
	}
	
	public void unlink(int parentTaskId, int taskId) throws TorqueException {
		Criteria criteria = new Criteria();
		criteria.add(TaskRelationPeer.SRC_TASK_ID, parentTaskId);
		criteria.add(TaskRelationPeer.DEST_TASK_ID, taskId);
		TaskRelationPeer.doDelete(criteria, con);
		
		criteria.clear();
		criteria.add(TaskRelationPeer.SRC_TASK_ID, taskId);
		criteria.add(TaskRelationPeer.DEST_TASK_ID, parentTaskId);
		TaskRelationPeer.doDelete(criteria, con);
	}

	public TaskBean update(TaskBean task, TaskCommentBean comment, FileData file) throws TorqueException, NotFoundException, IOException {
		TaskBean taskBean = null;

		taskBean = update(task);
		if (comment != null) {
			comment.setTaskId(taskBean.getTaskId());
			new TaskCommentManager(con).add(comment);
		}
		if (file != null) new FileManager(con).add(taskBean.getTaskId(), file);
			
		return taskBean;
	}
	public TaskBean update(TaskBean task) throws NotFoundException, TorqueException {
		int updateStatusId = task.getStatusId();
		task.setStatusId(getValidStatusId(task.getStatusId()));
		
		Task dbTask = getDBModel(task.getTaskId());
		if (updateStatusId != task.getStatusId()) {
			task.setStatusId(dbTask.getStatusId());
		}
		dbTask.setBean(task);
		dbTask.setUpdateDate(new Date());
		
		TaskPeer.doUpdate(dbTask, con);
		
		new TagManager(con).add(task.getTagAsList());
		
		return dbTask.getBean();
	}
	
	public TaskBean updateTagList(int taskId, String tagList) throws NotFoundException, TorqueException {
		TaskBean task = getBean(taskId);
		task.setTagList(tagList);
		return update(task);
	}
	public TaskBean updateEstimatedManHour(int taskId, double manHour) throws NotFoundException, TorqueException {
		TaskBean task = getBean(taskId);
		task.setEstimatedManHour(manHour);
		return update(task);
	}
	public TaskBean updateContents(int taskId, String contents) throws NotFoundException, TorqueException {
		TaskBean task = getBean(taskId);
		task.setContents(contents);
		return update(task);
	}
	
	protected int getValidStatusId(int statusId) throws TorqueException {
		try {
			StatusPeer.retrieveByPK(statusId, con);
			return statusId;
		} catch (NoRowsException e) {
			return Status.UNKNOWN;
		}
	}
	public TaskBean get(int taskId) throws TorqueException, NotFoundException {
		Task task = getDBModel(taskId);
		task.getStatus(con);
		task.getTaskComments(con);
		task.getAttachedFiles(con);
		TaskBean bean = task.getBean();
		bean.setLinkedTaskList(getLinkedTaskList(taskId));
		return bean;
	}
	
	private List<TaskBean> getLinkedTaskList(int taskId) throws TorqueException {
		List<Integer> idList = getLinkedTaskIdList(taskId);
		if (idList.size() == 0) return new ArrayList<TaskBean>();
		
		Criteria criteria = new Criteria();
		criteria.addJoin(TaskPeer.STATUS_ID, StatusPeer.STATUS_ID);
		criteria.addIn(TaskPeer.TASK_ID, idList);
		criteria.addAscendingOrderByColumn(TaskPeer.TASK_ID);
		return toBeanList(TaskPeer.doSelectJoinStatus(criteria, con));
	}
	private List<Integer> getLinkedTaskIdList(int taskId) throws TorqueException {
		Criteria criteria = new Criteria();
		Criteria.Criterion criterion = criteria.getNewCriterion(TaskRelationPeer.SRC_TASK_ID, taskId, Criteria.EQUAL);
		criterion.or(criteria.getNewCriterion(TaskRelationPeer.DEST_TASK_ID, taskId, Criteria.EQUAL));
		criteria.add(criterion);
		
		List<TaskRelation> relationList = TaskRelationPeer.doSelect(criteria, con);
		List<Integer> result = new ArrayList<Integer>();
		for (TaskRelation relation : relationList) {
			if (relation.getSrcTaskId() != taskId) result.add(relation.getSrcTaskId());
			if (relation.getDestTaskId() != taskId) result.add(relation.getDestTaskId());
		}
		return result;
	}
	protected Task getDBModel(int taskId) throws TorqueException, NotFoundException {
		try {
			return TaskPeer.retrieveByPK(taskId, con);
		} catch (NoRowsException e) {
			throw new NotFoundException("指定のタスク("+taskId+")");
		}
	}
	protected TaskBean getBean(int taskId) throws TorqueException, NotFoundException {
		return getDBModel(taskId).getBean();
	}
	
	public void delete(int taskId) throws TorqueException, NotFoundException {
		deleteAttachedFile(taskId);
		deleteComment(taskId);
		deleteLinkedTask(taskId);
		
		Criteria criteria = new Criteria();
		criteria.add(TaskPeer.TASK_ID, taskId);
		TaskPeer.doDelete(criteria, con);
	}

	private void deleteComment(int taskId) throws TorqueException {
		Criteria criteria = new Criteria();
		criteria.add(TaskCommentPeer.TASK_ID, taskId);
		TaskCommentPeer.doDelete(criteria, con);
	}

	private void deleteAttachedFile(int taskId) throws TorqueException {
		Criteria criteria = new Criteria();
		criteria.add(AttachedFilePeer.TASK_ID, taskId);
		new FileManager(con).deleteAttachedFile(AttachedFilePeer.doSelect(criteria, con));
	}
	
	private void deleteLinkedTask(int taskId) throws TorqueException {
		Criteria criteria = new Criteria();
		criteria.add(TaskRelationPeer.SRC_TASK_ID, taskId);
		TaskRelationPeer.doDelete(criteria, con);
		
		criteria.clear();
		criteria.add(TaskRelationPeer.DEST_TASK_ID, taskId);
		TaskRelationPeer.doDelete(criteria, con);
	}
	
	public void updateStatus(int taskId, int statusId) throws TorqueException, NotFoundException {
		int validStatusId = getValidStatusId(statusId);
		if (validStatusId == statusId) {
			Task task = getDBModel(taskId);
			task.setStatusId(statusId);
			task.setUpdateDate(new Date());
			TaskPeer.doUpdate(task, con);
		}
	}
	
	public void addTag(int taskId, String tag) throws TorqueException, NotFoundException {
		if (StringUtils.isBlank(tag)) return;
		Task task = getDBModel(taskId);
		task.setTagList(task.getTagList() + Tag.format(tag));
		task.setUpdateDate(new Date());
		TaskPeer.doUpdate(task, con);
		
		new TagManager(con).addIfNotExist(tag);
	}
	public void deleteTag(int taskId, String tag) throws TorqueException, NotFoundException {
		Task task = getDBModel(taskId);
		task.setTagList(task.getTagList().replaceAll(Tag.formatRegExp(tag), ""));
		task.setUpdateDate(new Date());
		TaskPeer.doUpdate(task, con);
	}
}
