package com.huigou.bpm.engine.delegate;

import com.huigou.bpm.engine.application.BizTaskApplication;
import com.huigou.bpm.engine.domain.model.BizTask;
import com.huigou.bpm.management.application.ProcInstApplication;
import com.huigou.bpm.management.application.ProcessDeploymentApplication;
import com.huigou.bpm.management.domain.model.ProcInst;
import com.huigou.bpm.management.domain.model.ProcessDeployment;
import com.huigou.util.StringUtil;
import com.mxgraph.model.mxCell;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.IntermediateCatchEvent;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.ExecutionListener;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author yonghuan
 */
public class BizTaskExecutionListener implements ExecutionListener {

    private final static Pattern INTERMEDIATE_CATCH_EVENT_ID_PATTERN = Pattern.compile("(waitStart|waitEnd)(.+)");
    private RepositoryService repositoryService;
    private BizTaskApplication bizTaskApplication;
    private BizTaskListener taskListener;
    private ProcInstApplication procInstApplication;
    private ProcessDeploymentApplication processDeploymentApplication;

    @Autowired
    public void setRepositoryService(RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }

    @Autowired
    public void setBizTaskApplication(BizTaskApplication bizTaskApplication) {
        this.bizTaskApplication = bizTaskApplication;
    }

    @Autowired
    public void setTaskListener(BizTaskListener taskListener) {
        this.taskListener = taskListener;
    }

    @Autowired
    public void setProcInstApplication(ProcInstApplication procInstApplication) {
        this.procInstApplication = procInstApplication;
    }

    @Autowired
    public void setProcessDeploymentApplication(ProcessDeploymentApplication processDeploymentApplication) {
        this.processDeploymentApplication = processDeploymentApplication;
    }

    @Override
    public void notify(DelegateExecution execution) throws Exception {
        String eventName = execution.getEventName();
        switch (eventName) {
            case EVENTNAME_START:
                onStart(execution);
                break;
            case EVENTNAME_TAKE:
                onTake(execution);
                break;
            case EVENTNAME_END:
                onEnd(execution);
                break;
            default:
                break;
        }
    }


    private void onStart(DelegateExecution execution) {
        // 记录业务开始时间
        String activityId = execution.getCurrentActivityId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(execution.getProcessDefinitionId());
        String serviceCode = parseServiceCode(bpmnModel.getFlowElement(activityId));
        Assert.notNull(serviceCode, String.format("无法解析业务节点的code, process:%s，activityId: ", execution.getProcessDefinitionId(), activityId));
        BizTask bizTask = new BizTask();
        bizTask.setStartDate(new Date());
        bizTask.setProcInstId(execution.getProcessInstanceId());
        bizTask.setServiceCode(serviceCode);
        bizTask.setStatus(BizTask.Status.START.id);

        ProcInst procInst = procInstApplication.findById(execution.getProcessInstanceId());
        ProcessDeployment processDeployment = processDeploymentApplication.findById(procInst.getProcDeployId());

        // 计算并设置业务节点任务的限制时间
        processDeployment.getGraphModel().ifPresent(mxGraphModel ->
                mxGraphModel.getCells().values()
                        .stream()
                        .map(mxCell.class::cast)
                        .filter(cell -> serviceCode.equals(cell.getAttribute("code")))
                        .map(cell -> cell.getAttribute("timeLimit"))
                        .filter(StringUtils::isNotBlank)
                        .map(Integer::valueOf)
                        .map(timeLimit -> DateUtils.addSeconds(bizTask.getStartDate(), timeLimit))
                        .findFirst()
                        .ifPresent(limitDate -> bizTask.setLimitDate(limitDate)));

        bizTaskApplication.add(bizTask);
        taskListener.notify(BizTaskListener.EVENTNAME_START, bizTask);
    }

    private String parseServiceCode(FlowElement activity) {
        String serviceCode = null;
        if (activity instanceof ServiceTask) {
            serviceCode = StringUtil.toLowerCamelCase(activity.getId());
        } else if (activity instanceof IntermediateCatchEvent) {
            Matcher matcher = INTERMEDIATE_CATCH_EVENT_ID_PATTERN.matcher(activity.getId());
            if (matcher.matches()) {
                serviceCode = StringUtil.toLowerCamelCase(matcher.group(2));
            }
        }
        return serviceCode;
    }

    private void onTake(DelegateExecution execution) {
    }

    private void onEnd(DelegateExecution execution) {
    }
}
