package com.casic.missiles.controller.flowable;

import com.alibaba.fastjson.JSONObject;
import com.casic.missiles.dto.ReturnUtil;
import com.casic.missiles.utils.SnowflakeUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.swagger.annotations.Api;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @Description: 此Controller已废弃，已整合到流程管理中，此处仅作参考
 * @Author: wangpeng
 * @Date: 2022/12/4 16:28
 */
@Api("此Controller已废弃")
@RestController
@RequestMapping("/vue3")
public class JsonToBpmnController {

    @Resource
    private SnowflakeUtil snowflakeUtil;

    /**
     * 保存流程定义，流程实例中才要传业务表id
     */
    @PostMapping("/jsonToBpmn")
    public Object saveForm(@RequestBody JSONObject jsonObject) throws InvocationTargetException, IllegalAccessException {
        System.err.println(jsonObject.toJSONString());
        //bpmn对象（可与xml相互转化）
        BpmnModel bpmnModel = new BpmnModel();
        //流程定义对象
        Process process = new Process();
        //添加流程定义到bpmn对象
        bpmnModel.addProcess(process);
        //流程中连线
        List<SequenceFlow> sequenceFlows = Lists.newArrayList();
        //流程定义-添加流程id
        process.setId("flowableV2_" + snowflakeUtil.nextId());
        //启动事件
        StartEvent startEvent = createStartEvent();
        //流程定义-添加启动事件
        process.addFlowElement(startEvent);
        //获取json实体
        JSONObject workFlowObj = jsonObject.getJSONObject("_value");
        String name = workFlowObj.getJSONObject("workFlowDef").getString("name");
        //流程定义-添加流程名称
        process.setName(name);
        //流程定义-添加扩展属性
        ExtensionAttribute extensionAttribute = new ExtensionAttribute();
        extensionAttribute.setName("Casic");
        extensionAttribute.setNamespace("http://flowable.org/bpmn");
        extensionAttribute.setValue(jsonObject.toJSONString());
        process.addAttribute(extensionAttribute);
        //流程定义-添加节点
        JSONObject processNodes = workFlowObj.getJSONObject("nodeConfig");
        String lastNode = create(startEvent.getId(), processNodes, bpmnModel, process, sequenceFlows);
        EndEvent endEvent = createEndEvent();
        process.addFlowElement(endEvent);
        process.addFlowElement(connect(lastNode, endEvent.getId(), sequenceFlows));

        new BpmnAutoLayout(bpmnModel).execute();
        System.err.println(new String(new BpmnXMLConverter().convertToXML(bpmnModel)));
        return ReturnUtil.success("保存成功");
    }

    /**
     * 方法功能：根据上个节点id及本节点信息生成本节点
     * @param fromId：启动事件id
     * @param flowNode：所有节点树形结构
     * @param model：bpmn对象（最终要得到的结果）
     * @param process：流程定义
     * @param sequenceFlows：连线列表
     */
    private static String create(String fromId, JSONObject flowNode, BpmnModel model, Process process, List<SequenceFlow> sequenceFlows) throws InvocationTargetException, IllegalAccessException {
        //获取节点类型，0 发起人 1审批 2抄送 3条件 4路由
        Integer nodeType = flowNode.getInteger("type");
        //节点类型-并行网关
        if (Type.PARALLEL.type.equals(nodeType)) {
            return createParallelGatewayBuilder(fromId, flowNode, model, process, sequenceFlows);
        }
        //节点类型-路由
        else if (Type.ROUTER.type.equals(nodeType)) {
            return createExclusiveGatewayBuilder(fromId, flowNode, model, process, sequenceFlows);
        }
        //节点类型-发起人（根节点）
        else if (Type.ROOT.type.equals(nodeType)) {
            //添加incoming属性（表示进来的，即上面流程节点的id列表），值为仅一个启动事件id的列表
            flowNode.put("incoming", Collections.singletonList(fromId));
            String id = createTask(flowNode, process, sequenceFlows);

            // 如果当前任务还有后续任务，则遍历创建后续任务
            JSONObject nextNode = flowNode.getJSONObject("childNode");
            if (Objects.nonNull(nextNode)) {
                FlowElement flowElement = model.getFlowElement(id);
                return create(id, nextNode, model, process, sequenceFlows);
            } else {
                return id;
            }
        }
        //节点类型-抄送任务
        else if (Type.CC.type.equals(nodeType)) {
            flowNode.put("incoming", Collections.singletonList(fromId));
            String id = createServiceTask(flowNode, process, sequenceFlows);

            // 如果当前任务还有后续任务，则遍历创建后续任务
            JSONObject nextNode = flowNode.getJSONObject("childNode");
            if (Objects.nonNull(nextNode)) {
                FlowElement flowElement = model.getFlowElement(id);
                return create(id, nextNode, model, process, sequenceFlows);
            } else {
                return id;
            }
        }
        //节点类型-用户审批任务
        else if (Type.USER_TASK.type.equals(nodeType)) {
            flowNode.put("incoming", Collections.singletonList(fromId));
            String id = createTask(flowNode, process, sequenceFlows);

            // 如果当前任务还有后续任务，则遍历创建后续任务
            JSONObject nextNode = flowNode.getJSONObject("childNode");
            if (Objects.nonNull(nextNode)) {
                FlowElement flowElement = model.getFlowElement(id);
                return create(id, nextNode, model, process, sequenceFlows);
            } else {
                return id;
            }
        } else {
            throw new RuntimeException("未知节点类型: nodeType=" + nodeType);
        }
    }

    /**
     * 排他网关分支上有条件设置
     * @param formId
     * @param flowNode
     * @param model
     * @param process
     * @param sequenceFlows
     * @return 返回创建的节点的id标识
     */
    private static String createExclusiveGatewayBuilder(String formId, JSONObject flowNode, BpmnModel model, Process process, List<SequenceFlow> sequenceFlows) throws InvocationTargetException, IllegalAccessException {
        String name = flowNode.getString("nodeName");
        String exclusiveGatewayId = id("exclusiveGateway");
        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
        exclusiveGateway.setId(exclusiveGatewayId);
        exclusiveGateway.setName(name);
        process.addFlowElement(exclusiveGateway);
        process.addFlowElement(connect(formId, exclusiveGatewayId, sequenceFlows));

        if (Objects.isNull(flowNode.getJSONArray("conditionNodes")) && Objects.isNull(flowNode.getJSONObject("childNode"))) {
            return exclusiveGatewayId;
        }
        List<JSONObject> flowNodes = Optional.ofNullable(flowNode.getJSONArray("conditionNodes")).map(e -> e.toJavaList(JSONObject.class)).orElse(Collections.emptyList());
        List<String> incoming = Lists.newArrayListWithCapacity(flowNodes.size());

        List<JSONObject> conditions = Lists.newCopyOnWriteArrayList();
        ///为每个排他网关的条件节点的子节点添加后续节点（相比于并行网关多了连线的条件表达式设置）
        for (JSONObject element : flowNodes) {
            //一个分支的子节点
            JSONObject childNode = element.getJSONObject("childNode");
            //一个分支的名字和表达式
            String nodeName = element.getString("nodeName");
            String expression = element.getString("conditionExpression");

            if (Objects.isNull(childNode)) {
                //条件节点下无子节点，设置后续节点的fromId为exclusiveGatewayId（即排他网关的id，多个分支的排他网关id相同，名字和表达式不同）
                incoming.add(exclusiveGatewayId);
                JSONObject condition = new JSONObject();
                condition.fluentPut("nodeName", nodeName)
                        .fluentPut("expression", expression);
                conditions.add(condition);
                continue;
            }
            // 只生成一个任务，同时设置当前任务的条件
            childNode.put("incoming", Collections.singletonList(exclusiveGatewayId));
            String identifier = create(exclusiveGatewayId, childNode, model, process, sequenceFlows);
            //连线列表中前一个节点是exclusiveGatewayId的连线，给每个连线设置名称和表达式
            //使用forEach是因为前一个节点是exclusiveGatewayId的节点有很多，需要遍历为没赋值过的赋值
            List<SequenceFlow> flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(exclusiveGatewayId, flow.getSourceRef()))
                    .collect(Collectors.toList());
            flows.stream().forEach(
                    e -> {
                        //设置连线名为条件节点名
                        if (StringUtils.isBlank(e.getName()) && StringUtils.isNotBlank(nodeName)) {
                            e.setName(nodeName);
                        }
                        // 设置条件表达式
                        if (Objects.isNull(e.getConditionExpression()) && StringUtils.isNotBlank(expression)) {
                            e.setConditionExpression(expression);
                        }
                    }
            );
            if (Objects.nonNull(identifier)) {
                incoming.add(identifier);
            }
        }
        //当前网关节点的子节点（整个排他网关的子节点）
        JSONObject childNode = flowNode.getJSONObject("childNode");

        if (Objects.nonNull(childNode)) {
            //有条件分支但无条件节点对应的子节点
            if (incoming == null || incoming.isEmpty()) {
                return create(exclusiveGatewayId, childNode, model, process, sequenceFlows);
            } else {
                // 所有 service task 连接 end exclusive gateway
                //条件分支汇合到下面的childNode节点
                childNode.put("incoming", incoming);
                FlowElement flowElement = model.getFlowElement(incoming.get(0));
                // 1.0 先进行边连接, 暂存 nextNode
                JSONObject nextNode = childNode.getJSONObject("childNode");
                String endExId = id("exclusiveGateway") + "end";
                process.addFlowElement(createExclusiveGateWayEnd(endExId));
                childNode.put("childNode", null);
                String identifier = endExId;
                for (int i = 0; i < incoming.size(); i++) {
                    // TODO: 2022/12/5 此处用的connect第一个参数和并行网关的为何不一样
                    // TODO: 2022/12/5 为啥有后续节点这里却设置成了endExId
                    process.addFlowElement(connect(incoming.get(i), endExId, sequenceFlows));
                }

                //  针对 gateway 空任务分支 添加条件表达式，即条件节点下无子节点的情况
                if (!conditions.isEmpty()) {
                    FlowElement flowElement1 = model.getFlowElement(identifier);
                    // 获取从 gateway 到目标节点 未设置条件表达式的节点
                    List<SequenceFlow> flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(flowElement1.getId(), flow.getTargetRef()))
                            .filter(flow -> StringUtils.equals(flow.getSourceRef(), exclusiveGatewayId))
                            .collect(Collectors.toList());
                    //顺序无所谓，都是分支上一个条件的分支
                    flows.stream().forEach(sequenceFlow -> {
                        if (!conditions.isEmpty()) {
                            JSONObject condition = conditions.get(0);
                            String nodeName = condition.getString("nodeName");
                            String expression = condition.getString("expression");

                            if (StringUtils.isBlank(sequenceFlow.getName()) && StringUtils.isNotBlank(nodeName)) {
                                sequenceFlow.setName(nodeName);
                            }
                            // 设置条件表达式
                            if (Objects.isNull(sequenceFlow.getConditionExpression()) && StringUtils.isNotBlank(expression)) {
                                sequenceFlow.setConditionExpression(expression);
                            }

                            conditions.remove(0);
                        }
                    });

                }

                // 1.1 边连接完成后，在进行 nextNode 创建
                if (Objects.nonNull(childNode)) {
                    return create(endExId, childNode, model, process, sequenceFlows);
                } else {
                    return endExId;
                }
            }
        } else {
            // 所有 service task 连接 end exclusive gateway
            // 1.0 先进行边连接, 暂存 nextNode
            String endExId = id("exclusiveGateway") + "end";
            process.addFlowElement(createExclusiveGateWayEnd(endExId));
            String identifier = endExId;
            //其他条件分支和第一个条件分支连线的终点节点相同
            for (int i = 0; i < incoming.size(); i++) {
                process.addFlowElement(connect(incoming.get(i), endExId, sequenceFlows));
            }

            //  针对 gateway 空任务分支 添加条件表达式，即条件节点下无子节点的情况
            if (!conditions.isEmpty()) {
                FlowElement flowElement1 = model.getFlowElement(identifier);
                // 获取从 gateway 到目标节点 未设置条件表达式的节点
                List<SequenceFlow> flows = sequenceFlows.stream().filter(flow -> StringUtils.equals(flowElement1.getId(), flow.getTargetRef()))
                        .filter(flow -> StringUtils.equals(flow.getSourceRef(), exclusiveGatewayId))
                        .collect(Collectors.toList());
                flows.stream().forEach(sequenceFlow -> {
                    if (!conditions.isEmpty()) {
                        JSONObject condition = conditions.get(0);
                        String nodeName = condition.getString("nodeName");
                        String expression = condition.getString("expression");

                        if (StringUtils.isBlank(sequenceFlow.getName()) && StringUtils.isNotBlank(nodeName)) {
                            sequenceFlow.setName(nodeName);
                        }
                        // 设置条件表达式
                        if (Objects.isNull(sequenceFlow.getConditionExpression()) && StringUtils.isNotBlank(expression)) {
                            sequenceFlow.setConditionExpression(expression);
                        }

                        conditions.remove(0);
                    }
                });

            }
            return endExId;
        }
//        return exclusiveGatewayId;
    }

    /**
     * 并行网关分支上无条件设置
     * @param formId
     * @param flowNode
     * @param model
     * @param process
     * @param sequenceFlows
     * @return 返回创建的节点的id标识
     */
    private static String createParallelGatewayBuilder(String formId, JSONObject flowNode, BpmnModel model, Process process, List<SequenceFlow> sequenceFlows) throws InvocationTargetException, IllegalAccessException {
        //并行网关节点名
        String name = flowNode.getString("nodeName");
        ParallelGateway parallelGateway = new ParallelGateway();
        String parallelGatewayId = id("parallelGateway");
        parallelGateway.setId(parallelGatewayId);
        parallelGateway.setName(name);
        process.addFlowElement(parallelGateway);
        //与上个节点的连线
        process.addFlowElement(connect(formId, parallelGatewayId, sequenceFlows));
        //1、无条件节点和子节点
        if (Objects.isNull(flowNode.getJSONArray("conditionNodes")) && Objects.isNull(flowNode.getJSONObject("childNode"))) {
            return parallelGatewayId;
        }
        //2、有条件节点或子节点
        //获取条件节点
        List<JSONObject> flowNodes = Optional.ofNullable(flowNode.getJSONArray("conditionNodes")).map(e -> e.toJavaList(JSONObject.class)).orElse(Collections.emptyList());
        //每个分支上的最后节点的标识id，用于给整个并行网关的子节点提供fromId
        List<String> incoming = Lists.newArrayListWithCapacity(flowNodes.size());
        //为每个并行网关的条件节点的子节点添加后续节点
        for (JSONObject element : flowNodes) {
            JSONObject childNode = element.getJSONObject("childNode");
            if (Objects.isNull(childNode)) {
                incoming.add(parallelGatewayId);
                continue;
            }
            String identifier = create(parallelGatewayId, childNode, model, process, sequenceFlows);
            if (Objects.nonNull(identifier)) {
                incoming.add(identifier);
            }
        }
        //当前网关节点的子节点（整个并行网关的子节点）
        JSONObject childNode = flowNode.getJSONObject("childNode");
        if (Objects.nonNull(childNode)) {
            // 普通结束网关
            //有并行分支但无条件节点对应的子节点
            if (CollectionUtils.isEmpty(incoming)) {
                return create(parallelGatewayId, childNode, model, process, sequenceFlows);
            } else {
                // 所有 service task 连接 end parallel gateway
                //并行分支汇合到下面的childNode节点
                childNode.put("incoming", incoming);
                FlowElement flowElement = model.getFlowElement(incoming.get(0));
                // 1.0 先进行线连接, 暂存 nextNode
                JSONObject nextNode = childNode.getJSONObject("childNode");
                childNode.put("childNode", null);
                String identifier = create(incoming.get(0), childNode, model, process, sequenceFlows);
                //其他并行分支和第一个并行分支连线的终点节点相同
                for (int i = 1; i < incoming.size(); i++) {
                    FlowElement flowElement1 = model.getFlowElement(incoming.get(i));
                    process.addFlowElement(connect(flowElement1.getId(), identifier, sequenceFlows));
                }
                // 1.1 线连接完成后，再进行 nextNode 创建
                if (Objects.nonNull(nextNode)) {
                    return create(identifier, nextNode, model, process, sequenceFlows);
                } else {
                    return identifier;
                }
            }
        }
        return parallelGatewayId;
    }

    //流转到服务任务(serviceTask)节点时会自动执行某些操作然后向下流转
    private static String createServiceTask(JSONObject flowNode, Process process, List<SequenceFlow> sequenceFlows) {
        List<String> incoming = flowNode.getJSONArray("incoming").toJavaList(String.class);
        // 自动生成id
        String id = id("serviceTask");
        if (incoming != null && !incoming.isEmpty()) {
            ServiceTask serviceTask = new ServiceTask();
            serviceTask.setName(flowNode.getString("nodeName"));
            serviceTask.setId(id);
            process.addFlowElement(serviceTask);
            process.addFlowElement(connect(incoming.get(0), id, sequenceFlows));
        }
        return id;
    }

    private static String createTask(JSONObject flowNode, Process process, List<SequenceFlow> sequenceFlows) {
        List<String> incoming = flowNode.getJSONArray("incoming").toJavaList(String.class);
        // 自动生成id
        String id = id("userTask");
        if (incoming != null && !incoming.isEmpty()) {
            UserTask userTask = new UserTask();
            userTask.setName(flowNode.getString("nodeName"));
            userTask.setId(id);
            process.addFlowElement(userTask);
            //根节点
            if (Type.ROOT.type.equals(flowNode.getInteger("type"))) {

            }
            //用户审批任务节点
            else {
                //多实例，在业务流程中，为特定步骤定义重复的方式
                MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = new MultiInstanceLoopCharacteristics();
                String examineMode = flowNode.getString("examineMode");
                // 审批人集合参数
                multiInstanceLoopCharacteristics.setInputDataItem(userTask.getId() + "assigneeList");
                // 迭代集合
                multiInstanceLoopCharacteristics.setElementVariable("assigneeName");
                // 并行
                multiInstanceLoopCharacteristics.setSequential(false);
                userTask.setAssignee("${assigneeName}");
                // 设置多实例属性
                userTask.setLoopCharacteristics(multiInstanceLoopCharacteristics);
                //多人审批时采用的审批方式 1依次会签 2并行会签 3或签
                //默认并行会签
                if ("1".equals(examineMode)) {
                    multiInstanceLoopCharacteristics.setSequential(true);
                } else if ("3".equals(examineMode)) {
                    //或签，即设置完成条件nrOfCompletedInstances/nrOfInstances > 0
                    //nrOfCompletedInstances:已完成的实例数量，nrOfInstances:实例总数
                    multiInstanceLoopCharacteristics.setCompletionCondition("${nrOfCompletedInstances/nrOfInstances > 0}");
                }
            }
            process.addFlowElement(connect(incoming.get(0), id, sequenceFlows));
        }
        return id;
    }

    private enum Type {
        //0 发起人 1审批 2抄送 3条件 4路由
        ROOT(0, UserTask.class),
        ROUTER(4, ExclusiveGateway.class),
        PARALLEL(5, ParallelGateway.class),

        /**
         * 排他事件
         */
//        EXCLUSIVE(4, ExclusiveGateway.class),

        /**
         * 任务
         */
        USER_TASK(1, UserTask.class),
        CC(2, ServiceTask.class);

        private Integer type;

        private Class<?> typeClass;

        Type(Integer type, Class<?> typeClass) {
            this.type = type;
            this.typeClass = typeClass;
        }

        public final static Map<Integer, Class<?>> TYPE_MAP = Maps.newHashMap();

        static {
            for (Type element : Type.values()) {
                TYPE_MAP.put(element.type, element.typeClass);
            }
        }

        public boolean isEqual(String type) {
            return this.type.equals(type);
        }

    }


    private static String id(String prefix) {
        return prefix + "_" + UUID.randomUUID().toString().replace("-", "").toLowerCase();
    }

    private static ServiceTask serviceTask(String name) {
        ServiceTask serviceTask = new ServiceTask();
        serviceTask.setName(name);
        return serviceTask;
    }

    /**
     * 新建连线实体加入连线列表
     */
    protected static SequenceFlow connect(String from, String to, List<SequenceFlow> sequenceFlows) {
        SequenceFlow flow = new SequenceFlow();
        flow.setId(id("sequenceFlow"));
        flow.setSourceRef(from);
        flow.setTargetRef(to);
        sequenceFlows.add(flow);
        return flow;
    }

    protected static StartEvent createStartEvent() {
        StartEvent startEvent = new StartEvent();
        startEvent.setId(id("start"));
        startEvent.setInitiator("applyUserId");
        return startEvent;
    }

    protected static EndEvent createEndEvent() {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(id("end"));
        return endEvent;
    }

    public static ExclusiveGateway createExclusiveGateWayEnd(String id) {
        ExclusiveGateway exclusiveGateway = new ExclusiveGateway();
        exclusiveGateway.setId(id);
        return exclusiveGateway;
    }

    private static ParallelGateway createParallelGateWayEnd(String id) {
        ParallelGateway parallelGateway = new ParallelGateway();
        parallelGateway.setId(id);
        return parallelGateway;
    }
}
