package ink.wgink.module.activiti.service.activiti.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.SearchException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.module.activiti.service.activiti.IActivitiModelService;
import ink.wgink.util.ResourceUtil;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.editor.constants.ModelDataJsonConstants;
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.history.HistoricProcessInstance;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * When you feel like quitting. Think about why you started
 * 当你想要放弃的时候，想想当初你为何开始
 *
 * @ClassName: ActivitiModelServiceImpl
 * @Description: 流程模型
 * @Author: WangGeng
 * @Date: 2021/7/12 22:01
 * @Version: 1.0
 **/
@Service
public class ActivitiModelServiceImpl extends DefaultBaseService implements IActivitiModelService, ModelDataJsonConstants {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private HistoryService historyService;

    @Override
    public void saveModel(String modelId, String name, String description, String json_xml, String svg_xml) throws Exception {
        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new SearchException("模型不存在");
        }

        JSONObject modelObject = JSONObject.parseObject(model.getMetaInfo());
        modelObject.put(MODEL_NAME, name);
        modelObject.put(MODEL_DESCRIPTION, description);
        model.setMetaInfo(modelObject.toString());
        model.setName(name);
        model.setVersion(model.getVersion() + 1);
        repositoryService.saveModel(model);
        repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes(ISystemConstant.CHARSET_UTF8));

        InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes(ISystemConstant.CHARSET_UTF8));
        TranscoderInput input = new TranscoderInput(svgStream);

        PNGTranscoder transcoder = new PNGTranscoder();
        // Setup output
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        TranscoderOutput output = new TranscoderOutput(outStream);

        // Do the transformation
        transcoder.transcode(input, output);
        final byte[] result = outStream.toByteArray();
        repositoryService.addModelEditorSourceExtra(model.getId(), result);
        outStream.close();

    }

    @Override
    public JSONObject getEditorJson(String modelId) throws UnsupportedEncodingException {
        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new SearchException("模型不存在");
        }
        JSONObject result;
        if (StringUtils.isNotEmpty(model.getMetaInfo())) {
            result = JSONObject.parseObject(model.getMetaInfo());
        } else {
            result = new JSONObject();
            result.put(MODEL_NAME, model.getName());
        }
        result.put(MODEL_ID, model.getId());
        String readTree = new String(repositoryService.getModelEditorSource(model.getId()), ISystemConstant.CHARSET_UTF8);
        JSONObject editorJson = JSONObject.parseObject(readTree);
        result.put("model", editorJson);
        return result;
    }

    @Override
    public JSONObject getStencilset() throws IOException {
        String content;
        try (InputStream jarResourceInputStream = ResourceUtil.getJarResourceInputStream("static/stencilset.json");
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(jarResourceInputStream));) {
            content = bufferedReader.lines().collect(Collectors.joining("\n"));
        } catch (IOException e) {
            throw e;
        }
        JSONObject jsonObject = JSONObject.parseObject(content);
        // 设置默认且唯一的信息
        JSONArray propertyPackagesJsonArray = jsonObject.getJSONArray("propertyPackages");

        JSONObject processIdPackageJsonObject = propertyPackagesJsonArray.getJSONObject(0);
        JSONArray processIdPackagePropertiesJsonArray = processIdPackageJsonObject.getJSONArray("properties");
        JSONObject processIdJsonObject = processIdPackagePropertiesJsonArray.getJSONObject(0);
        processIdJsonObject.put("value", "P" + System.currentTimeMillis());
        return jsonObject;
    }

    @Override
    public void getProcessImage(HttpServletResponse response, String deploymentId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
        if (processDefinition == null) {
            throw new SearchException("流程定义不存在，请检查流程部署情况");
        }
        String fontName = "宋体";
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        CustomProcessDiagramGenerator customProcessDiagramGenerator = new CustomProcessDiagramGenerator();
        response.setContentType("image/png");
        try (InputStream inputStream = customProcessDiagramGenerator.generateDiagram(bpmnModel, "png", new ArrayList<>(), new ArrayList<>(), fontName, fontName, fontName, null, 1.0D);
             OutputStream outputStream = response.getOutputStream();
        ) {
            byte[] buf = new byte[1024];
            for (int size; (size = inputStream.read(buf)) != -1; ) {
                outputStream.write(buf, 0, size);
            }
            outputStream.flush();
        } catch (IOException e) {
            throw new SystemException("流程图生成错误", e);
        }
    }

    @Override
    public void getRuntimeProcessImage(HttpServletResponse response, String processInstanceId) {
        response.setContentType("image/png");
        try (InputStream inputStream = getRuntimeProcessImageInputStream(processInstanceId);
             OutputStream outputStream = response.getOutputStream();
        ) {
            byte[] buf = new byte[1024];
            for (int size; (size = inputStream.read(buf)) != -1; ) {
                outputStream.write(buf, 0, size);
            }
            outputStream.flush();
        } catch (IOException e) {
            throw new SystemException("流程图生成错误", e);
        }
    }

    /**
     * 获取运行流程图输入流
     *
     * @param processInstanceId
     * @return
     */
    public InputStream getRuntimeProcessImageInputStream(String processInstanceId) {
        //获得流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        HistoricProcessInstance processInstanceHistory;
        String processDefinitionId;
        if (processInstance == null) {
            //查询已经结束的流程实例
            processInstanceHistory = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (processInstanceHistory == null) {
                return null;
            } else {
                processDefinitionId = processInstanceHistory.getProcessDefinitionId();
            }
        } else {
            processDefinitionId = processInstance.getProcessDefinitionId();
        }

        //使用宋体
        String fontName = "宋体";
        //获取BPMN模型对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

        //获取流程实例当前的节点，需要高亮显示
        List<String> highLightedActivitiIds = new ArrayList<>();
        if (processInstance != null) {
            // 正在运行的流程
            List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstance.getId());
            // 先添加历史节点
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
            if (!historicActivityInstances.isEmpty()) {
                for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                    if (isActiveActivityId(historicActivityInstance.getActivityId(), activeActivityIds)) {
                        continue;
                    }
                    highLightedActivitiIds.add(historicActivityInstance.getActivityId());
                }
            }
            // 添加现有节点
            for (String activeActivityId : activeActivityIds) {
                highLightedActivitiIds.add("#" + activeActivityId);
            }
        } else {
            // 历史流程
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
            if (!historicActivityInstances.isEmpty()) {
                for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                    highLightedActivitiIds.add(historicActivityInstance.getActivityId());
                }
            }
        }
        List<String> listSequenceFlows = listSequenceFlows(bpmnModel, highLightedActivitiIds);
        CustomProcessDiagramGenerator customProcessDiagramGenerator = new CustomProcessDiagramGenerator();
        return customProcessDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitiIds, listSequenceFlows,
                fontName, fontName, fontName, null, 1D);
    }

    /**
     * 判断历史节点是否是激活节点
     *
     * @param historyActivityId
     * @param activeActivityIds
     * @return
     */
    private boolean isActiveActivityId(String historyActivityId, List<String> activeActivityIds) {
        if (activeActivityIds == null || activeActivityIds.isEmpty()) {
            return false;
        }
        for (String activeActivityId : activeActivityIds) {
            if (StringUtils.equals(historyActivityId, activeActivityId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 序列流列表
     *
     * @param bpmnModel              模型
     * @param highLightedActivitiIds 高亮的活动ID
     * @return
     */
    private List<String> listSequenceFlows(BpmnModel bpmnModel, List<String> highLightedActivitiIds) {
        Set<String> flowSet = new HashSet<>();
        // 获取有关的点
        List<FlowNode> flowElementsOfType = bpmnModel.getProcesses().get(0).findFlowElementsOfType(FlowNode.class);
        List<FlowNode> activitiFlowNode = new ArrayList<>();
        // 加载高亮的节点
        for (String highLightedActivitiId : highLightedActivitiIds) {
            String activitiId = highLightedActivitiId;
            if (activitiId.startsWith("#")) {
                activitiId = highLightedActivitiId.substring(1);
            }
            for (FlowNode flowNode : flowElementsOfType) {
                if (StringUtils.equals(flowNode.getId(), activitiId)) {
                    activitiFlowNode.add(flowNode);
                    break;
                }
            }
        }
        // 找出高亮节点之间的连线
        for (int i = 0; i < activitiFlowNode.size() - 1; i++) {
            FlowNode flowNode = activitiFlowNode.get(i);
            List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();

            FlowNode nextFlowNode = activitiFlowNode.get(i + 1);
            List<SequenceFlow> nextIncomingFlows = nextFlowNode.getIncomingFlows();
            // 前节点的出与后节点的入相同，则有关系
            for (SequenceFlow outgoingFlow : outgoingFlows) {
                for (SequenceFlow nextIncomingFlow : nextIncomingFlows) {
                    if (StringUtils.equals(outgoingFlow.getId(), nextIncomingFlow.getId())) {
                        flowSet.add(outgoingFlow.getId());
                    }
                }
            }
        }
        return new ArrayList<>(flowSet);
    }


}
