package com.huigou.bpm.management.application.impl;

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.bpm.management.domain.query.ProcInstQueryRequest;
import com.huigou.bpm.management.mapper.ProcInstMapper;
import com.huigou.bpm.management.repository.ProcInstRepository;
import com.huigou.util.StringUtil;
import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.util.mxXmlUtils;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.IntermediateCatchEvent;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.runtime.Execution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yonghuan
 */
@Service
public class ProcInstApplicationImpl implements ProcInstApplication {

    private final static String ACTIVITI_INTERMEDIATE_CATCH_EVENT = "intermediateCatchEvent";
    private ProcInstRepository procInstRepository;
    private ProcInstMapper procInstMapper;
    private ProcessDeploymentApplication processDeploymentApplication;
    private RuntimeService runtimeService;
    private RepositoryService repositoryService;
    private HistoryService historyService;

    @Autowired
    public void setProcInstMapper(ProcInstMapper procInstMapper) {
        this.procInstMapper = procInstMapper;
    }

    @Autowired
    public void setProcInstRepository(ProcInstRepository procInstRepository) {
        this.procInstRepository = procInstRepository;
    }

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

    @Autowired
    public void setRuntimeService(RuntimeService runtimeService) {
        this.runtimeService = runtimeService;
    }

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

    @Autowired
    public void setHistoryService(HistoryService historyService) {
        this.historyService = historyService;
    }

    @Override
    public Page<Map<String, Object>> sliceQuery(ProcInstQueryRequest queryRequest) {
        return procInstMapper.sliceQuery(queryRequest);
    }

    @Override
    public ProcInst findById(String id) {
        return procInstRepository.getOne(id);
    }

    @Override
    public String findProcInstXml(String id) {
        ProcInst procInst = procInstRepository.getOne(id);
        ProcessDeployment procDeploy = processDeploymentApplication.findById(procInst.getProcDeployId());

        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDeploy.getActProcdefId());
        Document mxGraphXmlDocument = mxXmlUtils.parseXml(procDeploy.getXml());
        mxCodec codec = new mxCodec(mxGraphXmlDocument);
        mxGraphModel graphModel = (mxGraphModel) codec.decode(mxGraphXmlDocument.getDocumentElement());

        // 标记已完成活动
        List<String> historicServices = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(id)
                .list()
                .stream()
                .map(HistoricActivityInstance::getActivityId)
                .filter(Objects::nonNull)
                .map(activityId -> findServiceCodeByActivityId(bpmnModel, activityId))
                .collect(Collectors.toList());
        markServices(historicServices, graphModel, "blue");
        // 标记正在进行中的活动
        List<String> runningService = runtimeService.createExecutionQuery().processInstanceId(id).list()
                .stream()
                .map(Execution::getActivityId)
                .filter(Objects::nonNull)
                .map(activityId -> findServiceCodeByActivityId(bpmnModel, activityId))
                .collect(Collectors.toList());
        // 标记连线
        markServices(runningService, graphModel, "red");
        Set<String> services = Stream.concat(historicServices.stream(), runningService.stream())
                .collect(Collectors.toSet());
        markLinks(services, graphModel, "blue");
        return mxXmlUtils.getXml(codec.encode(graphModel));
    }

    /**
     * @param services    流程图中的服务编码。
     * @param graphModel
     * @param strokeColor mxCell 边框颜色
     */
    private void markServices(List<String> services, mxGraphModel graphModel, String strokeColor) {
        Map<String, Object> cells = graphModel.getCells();
        services.forEach(serviceCode ->
                cells.values().stream()
                        .map(mxCell.class::cast)
                        .filter(cell -> serviceCode.equals(cell.getAttribute("code")))
                        .findAny().ifPresent(cell -> setMxCellStyle(cell, "strokeColor", strokeColor)));
    }

    private void markLinks(Collection<String> services, mxGraphModel graphModel, String strokeColor) {
        Map<String, Object> cells = graphModel.getCells();
        services.forEach(serviceCode -> {
            // 根据code找到节点的id
            String mxCellId = cells.values().stream().map(mxCell.class::cast)
                    .filter(cell -> serviceCode.equals(cell.getAttribute("code")))
                    .map(cell -> cell.getId())
                    .findAny()
                    .orElse("");

            // 找出节点的所有连线
            cells.values().stream().map(mxCell.class::cast)
                    .filter(mxCell::isEdge)
                    .filter(edge -> edge.getSource() != null && edge.getTarget() != null)
                    .filter(edge -> edge.getTarget().getId().equals(mxCellId))
                    .filter(edge -> {
                        String sourceCode = ((mxCell) edge.getSource()).getAttribute("code");
                        return services.contains(sourceCode);
                    }).forEach(edge -> setMxCellStyle(edge, "strokeColor", strokeColor));
        });
    }

    private void setMxCellStyle(mxCell cell, String name, String value) {
        Map<String, String> styles = Arrays.stream(cell.getStyle().split(";"))
                .map(s -> s.split("="))
                .collect(Collectors.toMap(s -> s[0], s -> s[1], (u, v) -> {
                    throw new IllegalStateException(String.format("Duplicate key %s", u));
                }, TreeMap::new));
        styles.put(name, value);

        String styleValue = styles.entrySet().stream()
                .map(e -> String.join("=", e.getKey(), e.getValue()))
                .collect(Collectors.joining(";"));
        cell.setStyle(styleValue);
    }


    private String findServiceCodeByActivityId(BpmnModel bpmnModel, String activityId) throws RuntimeException {
        FlowElement flowElement = bpmnModel.getFlowElement(activityId);
        if (flowElement instanceof IntermediateCatchEvent) {
            IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowElement;
//            Optional<String> prevId = intermediateCatchEvent.getIncomingFlows().stream()
//                    .map(line -> bpmnModel.getFlowElement(line.getSourceRef()))
//                    .filter(fe -> !(fe instanceof IntermediateCatchEvent))
//                    .map(fe -> fe.getId())
//                    .findAny();
//            if (prevId.isPresent()) {
//                return prevId.get();
//            }
//            // 如果前一个节点还是intermediateCatchEvent
//            prevId = intermediateCatchEvent.getIncomingFlows().stream()
//                    .map(line -> bpmnModel.getFlowElement(line.getSourceRef()))
//                    .filter(fe -> (fe instanceof IntermediateCatchEvent))
//                    .map(fe -> fe.getId())
//                    .findAny();
//            if (!prevId.isPresent()) {
//                throw new IllegalArgumentException(String.format("无法根据activityId=%s 找到对应的ServiceCode", activityId));
//            }
//            String code = prevId.get();
            String code = intermediateCatchEvent.getId();
            if (code.startsWith("waitStart")) {
                return StringUtil.toLowerCamelCase(code.replaceFirst("waitStart", ""));
            }
            if (code.startsWith("waitEnd")) {
                return StringUtil.toLowerCamelCase(code.replaceFirst("waitEnd", ""));
            }
            throw new IllegalArgumentException(String.format("无法根据activityId=%s 找到对应的ServiceCode", activityId));
        }
        return flowElement.getId();
    }
}
