package com.mossle.bpm.support;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import com.mossle.api.form.FormDTO;
import com.mossle.api.model.ModelConnector;
import com.mossle.api.model.ModelInfoDTO;

import com.mossle.bpm.cmd.CheckWithdrawTaskCmd;
import com.mossle.bpm.cmd.CompleteTaskWithCommentCmd;
import com.mossle.bpm.cmd.DeleteTaskWithCommentCmd;
import com.mossle.bpm.cmd.ExecuteExpressionCmd;
import com.mossle.bpm.cmd.FindTaskDefinitionsCmd;
import com.mossle.bpm.cmd.RollbackTaskCmd;
import com.mossle.bpm.cmd.SignalStartEventCmd;
import com.mossle.bpm.cmd.WithdrawTaskCmd;
import com.mossle.bpm.persistence.domain.BpmConfForm;
import com.mossle.bpm.persistence.domain.BpmConfListener;
import com.mossle.bpm.persistence.domain.BpmConfOperation;
import com.mossle.bpm.persistence.domain.BpmConfUser;
import com.mossle.bpm.persistence.domain.BpmTaskConf;
import com.mossle.bpm.persistence.manager.BpmConfFormManager;
import com.mossle.bpm.persistence.manager.BpmConfListenerManager;
import com.mossle.bpm.persistence.manager.BpmConfOperationManager;
import com.mossle.bpm.persistence.manager.BpmConfUserManager;
import com.mossle.bpm.persistence.manager.BpmTaskConfManager;
import com.mossle.bpm.support.MockVariableScope;

import com.mossle.spi.process.InternalProcessConnector;
import com.mossle.spi.process.ProcessTaskDefinition;

import org.activiti.engine.IdentityService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.cmd.GetDeploymentProcessDefinitionCmd;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.el.ExpressionManager;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.task.Task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 这是一个spi接口，因为HumanTask的配置还没有完全从bpm里抽象出去，所以还有不少配置需要从bpm里读取.
 */
public class ActivitiInternalProcessConnector implements
        InternalProcessConnector {
    /** logger. */
    private static Logger logger = LoggerFactory
            .getLogger(ActivitiInternalProcessConnector.class);
    private ProcessEngine processEngine;
    private BpmConfOperationManager bpmConfOperationManager;
    private BpmConfFormManager bpmConfFormManager;
    private BpmTaskConfManager bpmTaskConfManager;
    private BpmConfUserManager bpmConfUserManager;
    private JdbcTemplate jdbcTemplate;
    private ModelConnector modelConnector;
    private BpmConfListenerManager bpmConfListenerManager;

    /**
     * 获得任务表单，不包含表单内容.
     */
    public FormDTO findTaskForm(String taskId) {
        if (taskId == null) {
            logger.error("taskId cannot be null");

            return null;
        }

        Task task = processEngine.getTaskService().createTaskQuery()
                .taskId(taskId).singleResult();

        if (task == null) {
            logger.error("cannot find task for {}", taskId);

            return null;
        }

        String processDefinitionId = task.getProcessDefinitionId();
        String activityId = task.getTaskDefinitionKey();
        FormDTO formDto = new FormDTO();
        formDto.setTaskId(taskId);

        List<BpmConfOperation> bpmConfOperations = bpmConfOperationManager
                .find("from BpmConfOperation where bpmConfNode.bpmConfBase.processDefinitionId=? and bpmConfNode.code=?",
                        processDefinitionId, activityId);

        for (BpmConfOperation bpmConfOperation : bpmConfOperations) {
            formDto.getButtons().add(bpmConfOperation.getValue());
        }

        formDto.setProcessDefinitionId(processDefinitionId);
        formDto.setActivityId(activityId);

        List<BpmConfForm> bpmConfForms = bpmConfFormManager
                .find("from BpmConfForm where bpmConfNode.bpmConfBase.processDefinitionId=? and bpmConfNode.code=?",
                        processDefinitionId, activityId);

        if (!bpmConfForms.isEmpty()) {
            BpmConfForm bpmConfForm = bpmConfForms.get(0);

            if (!Integer.valueOf(2).equals(bpmConfForm.getStatus())) {
                // 外部表单
                if (Integer.valueOf(1).equals(bpmConfForm.getType())) {
                    formDto.setRedirect(true);
                    formDto.setUrl(bpmConfForm.getValue());
                } else {
                    formDto.setCode(bpmConfForm.getValue());
                }
            }
        }

        return formDto;
    }

    /**
     * 获得任务定义.
     */
    public List<ProcessTaskDefinition> findTaskDefinitions(
            String processDefinitionId) {
        List<ProcessTaskDefinition> processTaskDefinitions = new ArrayList<ProcessTaskDefinition>();
        FindTaskDefinitionsCmd cmd = new FindTaskDefinitionsCmd(
                processDefinitionId);
        List<TaskDefinition> taskDefinitions = processEngine
                .getManagementService().executeCommand(cmd);

        for (TaskDefinition taskDefinition : taskDefinitions) {
            ProcessTaskDefinition processTaskDefinition = new ProcessTaskDefinition();
            processTaskDefinition.setKey(taskDefinition.getKey());

            if (taskDefinition.getNameExpression() != null) {
                processTaskDefinition.setName(taskDefinition
                        .getNameExpression().getExpressionText());
            }

            if (taskDefinition.getAssigneeExpression() != null) {
                processTaskDefinition.setAssignee(taskDefinition
                        .getAssigneeExpression().getExpressionText());
            }

            processTaskDefinitions.add(processTaskDefinition);
        }

        return processTaskDefinitions;
    }

    /**
     * 完成任务.
     */
    public void completeTask(String taskId, String userId,
            Map<String, Object> variables) {
        TaskService taskService = processEngine.getTaskService();
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

        if (task == null) {
            throw new IllegalStateException("任务不存在");
        }

        // 先设置登录用户
        IdentityService identityService = processEngine.getIdentityService();
        identityService.setAuthenticatedUserId(userId);

        // 处理子任务
        if ("subtask".equals(task.getCategory())) {
            processEngine.getManagementService().executeCommand(
                    new DeleteTaskWithCommentCmd(taskId, "完成"));

            int count = jdbcTemplate.queryForObject(
                    "select count(*) from ACT_RU_TASK where PARENT_TASK_ID_=?",
                    Integer.class, task.getParentTaskId());

            if (count > 1) {
                return;
            }

            taskId = task.getParentTaskId();
        }

        processEngine.getManagementService().executeCommand(
                new CompleteTaskWithCommentCmd(taskId, variables, "完成"));
    }

    /**
     * 转发任务.
     */
    public void transfer(String taskId, String assignee, String owner) {
        processEngine.getTaskService().setAssignee(taskId, assignee);
        processEngine.getTaskService().setOwner(taskId, owner);
    }

    /**
     * 撤销任务.
     */
    public void withdrawTask(String taskId) {
        Command<Integer> cmd = new WithdrawTaskCmd(taskId);

        processEngine.getManagementService().executeCommand(cmd);
    }

    /**
     * 回退指定节点，指定负责人. 如果activityId为null，就自动查询上一个节点.
     */
    public void rollback(String taskId, String activityId, String userId) {
        Command<Object> cmd = new RollbackTaskCmd(taskId, activityId, userId);

        processEngine.getManagementService().executeCommand(cmd);
    }

    /**
     * 回退自定节点，使用最后的负责人. 如果activityId为null，就自动查询上一个节点.
     */
    public void rollbackAuto(String taskId, String activityId) {
        Command<Object> cmd = new RollbackTaskCmd(taskId, activityId);
        processEngine.getManagementService().executeCommand(cmd);
    }

    /**
     * 协办任务.
     */
    public void delegateTask(String taskId, String userId) {
        processEngine.getTaskService().delegateTask(taskId, userId);
    }

    /**
     * 完成协办.
     */
    public void resolveTask(String taskId) {
        processEngine.getTaskService().resolveTask(taskId);
    }

    /**
     * 根据activityId找到任务定义.
     * 
     * TODO: 支持ExpressionManager，支持Expr
     */
    public ProcessTaskDefinition findTaskDefinition(String processDefinitionId,
            String taskDefinitionKey, String businessKey) {
        // 先从流程定义里读取设计配置的负责人
        List<BpmConfUser> bpmConfUsers = bpmConfUserManager
                .find("from BpmConfUser where bpmConfNode.bpmConfBase.processDefinitionId=? and bpmConfNode.code=?",
                        processDefinitionId, taskDefinitionKey);
        logger.debug("{}", bpmConfUsers);

        ProcessTaskDefinition processTaskDefinition = new ProcessTaskDefinition();

        try {
            for (BpmConfUser bpmConfUser : bpmConfUsers) {
                logger.debug("status : {}, type: {}", bpmConfUser.getStatus(),
                        bpmConfUser.getType());
                logger.debug("value : {}", bpmConfUser.getValue());

                String value = bpmConfUser.getValue();

                if (bpmConfUser.getStatus() == 1) {
                    if (bpmConfUser.getType() == 0) {
                        logger.debug("add assignee : {}", value);
                        processTaskDefinition.setAssignee(value);
                    } else if (bpmConfUser.getType() == 1) {
                        logger.debug("add candidate user : {}", value);
                        processTaskDefinition.addParticipantDefinition("user",
                                value, "add");
                    } else if (bpmConfUser.getType() == 2) {
                        logger.debug("add candidate group : {}", value);
                        processTaskDefinition.addParticipantDefinition("group",
                                value, "add");
                    }
                } else if (bpmConfUser.getStatus() == 2) {
                    if (bpmConfUser.getType() == 0) {
                        logger.debug("delete assignee : {}", value);
                        processTaskDefinition.setAssignee(null);
                    } else if (bpmConfUser.getType() == 1) {
                        logger.debug("delete candidate user : {}", value);
                        processTaskDefinition.addParticipantDefinition("user",
                                value, "delete");
                    } else if (bpmConfUser.getType() == 2) {
                        logger.debug("delete candidate group : {}", value);
                        processTaskDefinition.addParticipantDefinition("group",
                                value, "delete");
                    }
                }
            }
        } catch (Exception ex) {
            logger.info(ex.getMessage(), ex);
        }

        try {
            String hql = "from BpmTaskConf where businessKey=? and taskDefinitionKey=?";
            BpmTaskConf bpmTaskConf = bpmTaskConfManager.findUnique(hql,
                    businessKey, taskDefinitionKey);

            if (bpmTaskConf != null) {
                String assignee = bpmTaskConf.getAssignee();

                if ((assignee != null) && (!"".equals(assignee))) {
                    logger.debug("add assignee : {}", assignee);
                    processTaskDefinition.setAssignee(assignee);
                }
            } else {
                logger.info("cannot find BpmTaskConf {} {}", businessKey,
                        taskDefinitionKey);
            }
        } catch (Exception ex) {
            logger.info(ex.getMessage(), ex);
        }

        return processTaskDefinition;
    }

    /**
     * 获得流程发起人.
     */
    public String findInitiator(String processInstanceId) {
        String initiator = null;

        if (Context.getCommandContext() == null) {
            initiator = processEngine.getHistoryService()
                    .createHistoricProcessInstanceQuery()
                    .processInstanceId(processInstanceId).singleResult()
                    .getStartUserId();
        } else {
            initiator = Context.getCommandContext()
                    .getHistoricProcessInstanceEntityManager()
                    .findHistoricProcessInstance(processInstanceId)
                    .getStartUserId();
        }

        return initiator;
    }

    /**
     * 获得某个节点的历史负责人.
     */
    public String findAssigneeByActivityId(String processInstanceId,
            String activityId) {
        return null;
    }

    /**
     * 解析表达式.
     */
    public Object executeExpression(String taskId, String expressionText) {
        TaskEntity taskEntity = Context.getCommandContext()
                .getTaskEntityManager().findTaskById(taskId);
        ExpressionManager expressionManager = Context
                .getProcessEngineConfiguration().getExpressionManager();

        return expressionManager.createExpression(expressionText).getValue(
                taskEntity);
    }

    /**
     * 获得开始事件id.
     */
    public String findInitialActivityId(String processDefinitionId) {
        GetDeploymentProcessDefinitionCmd getDeploymentProcessDefinitionCmd = new GetDeploymentProcessDefinitionCmd(
                processDefinitionId);
        ProcessDefinitionEntity processDefinition = processEngine
                .getManagementService().executeCommand(
                        getDeploymentProcessDefinitionCmd);

        return processDefinition.getInitial().getId();
    }

    /**
     * 获得提交节点
     */
    public String findFirstUserTaskActivityId(String processDefinitionId,
            String initiator) {
        GetDeploymentProcessDefinitionCmd getDeploymentProcessDefinitionCmd = new GetDeploymentProcessDefinitionCmd(
                processDefinitionId);
        ProcessDefinitionEntity processDefinitionEntity = processEngine
                .getManagementService().executeCommand(
                        getDeploymentProcessDefinitionCmd);

        ActivityImpl startActivity = processDefinitionEntity.getInitial();

        if (startActivity.getOutgoingTransitions().size() != 1) {
            throw new IllegalStateException(
                    "start activity outgoing transitions cannot more than 1, now is : "
                            + startActivity.getOutgoingTransitions().size());
        }

        PvmTransition pvmTransition = startActivity.getOutgoingTransitions()
                .get(0);
        PvmActivity targetActivity = pvmTransition.getDestination();

        if (!"userTask".equals(targetActivity.getProperty("type"))) {
            logger.info("first activity is not userTask, just skip");

            return null;
        }

        return targetActivity.getId();
    }

    /**
     * 触发execution继续执行.
     */
    public void signalExecution(String executionId) {
        // processEngine.getRuntimeService().signal(executionId);
        processEngine.getManagementService().executeCommand(
                new SignalStartEventCmd(executionId));
    }

    public String findProcessDefinitionId(String processDefinitionKey) {
        ProcessDefinition processDefinition = processEngine
                .getRepositoryService().createProcessDefinitionQuery()
                .processDefinitionKey(processDefinitionKey).latestVersion()
                .singleResult();

        return processDefinition.getId();
    }

    public boolean checkWithdraw(String historyTaskId) {
        return processEngine.getManagementService().executeCommand(
                new CheckWithdrawTaskCmd(historyTaskId));
    }

    public void fireEvent(String eventName, String businessKey, String userId,
            String activityId, String activityName) {
        int eventCode = this.findEventCode(eventName);
        ModelInfoDTO modelInfo = this.modelConnector.findByCode(businessKey);

        if (modelInfo == null) {
            logger.info("cannot find model : {}", businessKey);

            return;
        }

        String processDefinitionId = modelInfo.getProcessId();
        logger.info("{} {} {}", processDefinitionId, activityId, eventCode);

        if ((eventCode == 21) || (eventCode == 22) || (eventCode == 23) || (eventCode == 24)) {
            activityId = "";
        }

        String hql = "from BpmConfListener where bpmConfNode.bpmConfBase.processDefinitionId=? and bpmConfNode.code=? and type=?";
        List<BpmConfListener> bpmConfListeners = bpmConfListenerManager.find(
                hql, processDefinitionId, activityId, eventCode);

        for (BpmConfListener bpmConfListener : bpmConfListeners) {
            String expressionText = bpmConfListener.getValue();

            if (Context.getProcessEngineConfiguration() == null) {
                processEngine.getManagementService().executeCommand(
                        new ExecuteExpressionCmd(expressionText, eventCode,
                                eventName, modelInfo, userId, activityId,
                                activityName));
            } else {
                new ExecuteExpressionCmd(expressionText, eventCode, eventName,
                        modelInfo, userId, activityId, activityName)
                        .execute(Context.getCommandContext());
            }
        }
    }

    public Integer findEventCode(String name) {
        if ("start".equals(name)) {
            return 0;
        } else if ("end".equals(name)) {
            return 1;
        } else if ("take".equals(name)) {
            return 2;
        } else if ("create".equals(name)) {
            return 3;
        } else if ("assign".equals(name)) {
            return 4;
        } else if ("complete".equals(name)) {
            return 5;
        } else if ("delete".equals(name)) {
            return 6;
        } else if ("approve".equals(name)) {
            return 11;
        } else if ("reject".equals(name)) {
            return 12;
        } else if ("process-draft".equals(name)) {
            return 21;
        } else if ("process-start".equals(name)) {
            return 22;
        } else if ("process-close".equals(name)) {
            return 23;
        } else if ("process-end".equals(name)) {
            return 24;
        }

        return -1;
    }

    // ~ ==================================================
    @Resource
    public void setProcessEngine(ProcessEngine processEngine) {
        this.processEngine = processEngine;
    }

    @Resource
    public void setBpmConfOperationManager(
            BpmConfOperationManager bpmConfOperationManager) {
        this.bpmConfOperationManager = bpmConfOperationManager;
    }

    @Resource
    public void setBpmConfFormManager(BpmConfFormManager bpmConfFormManager) {
        this.bpmConfFormManager = bpmConfFormManager;
    }

    @Resource
    public void setBpmTaskConfManager(BpmTaskConfManager bpmTaskConfManager) {
        this.bpmTaskConfManager = bpmTaskConfManager;
    }

    @Resource
    public void setBpmConfUserManager(BpmConfUserManager bpmConfUserManager) {
        this.bpmConfUserManager = bpmConfUserManager;
    }

    @Resource
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Resource
    public void setModelConnector(ModelConnector modelConnector) {
        this.modelConnector = modelConnector;
    }

    @Resource
    public void setBpmConfListenerManager(
            BpmConfListenerManager bpmConfListenerManager) {
        this.bpmConfListenerManager = bpmConfListenerManager;
    }
}
