package com.codvision.workflow.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;

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

/**
 * 工作流工具类
 * @author hxl
 */
public class WorkFlowUtil {

    /**
     * 根据节点获取入口连线
     * @param source 指定节点
     * @return 入口连线
     */
    public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = new ArrayList<>();
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getIncomingFlows();
        }
        return sequenceFlows;
    }


    /**
     * 根据节点获取出口连线
     * @param source 指定节点
     * @return 出口连线
     */
    public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = new ArrayList<>();
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getOutgoingFlows();
        }
        return sequenceFlows;
    }

    /**
     * 获取开始节点
     * @param model BpmnModel对象
     * @return 开始节点或者null
     */
    public static StartEvent getStartEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        FlowElement startElement = process.getInitialFlowElement();
        if (startElement instanceof StartEvent) {
            return (StartEvent) startElement;
        }
        return getStartEvent(process.getFlowElements());
    }

    private static StartEvent getStartEvent(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                return (StartEvent) flowElement;
            }
        }
        return null;
    }

    /**
     * 获取结束节点
     * @param model BpmnModel对象
     * @return 结束节点或者null
     */
    public static EndEvent getEndEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        return getEndEvent(process.getFlowElements());
    }

    private static EndEvent getEndEvent(Collection<FlowElement> flowElements) {
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof EndEvent) {
                return (EndEvent) flowElement;
            }
        }

        return null;
    }

    /**
     * 获取用户节点
     * @param model BpmnModel对象
     * @param taskKey 任务节点Key
     * @return 用户节点或者null
     */
    public static UserTask getUserTaskByKey(BpmnModel model, String taskKey) {
        Process process = model.getMainProcess();
        FlowElement flowElement = process.getFlowElement(taskKey);
        if (flowElement instanceof UserTask) {
            return (UserTask) flowElement;
        }

        return null;
    }

    /**
     * 判断是否是多实例
     * @param model BpmnModel对象
     * @param taskKey 任务节点Key
     * @return boolean
     */
    public static boolean isMultiInstance(BpmnModel model, String taskKey) {
        UserTask userTask = getUserTaskByKey(model, taskKey);
        if (ObjectUtil.isNotNull(userTask)) {
            return userTask.hasMultiInstanceLoopCharacteristics();
        }

        return false;
    }

    /**
     * 判断是否是多实例（并行会签）
     * @param model BpmnModel对象
     * @param taskKey 任务节点Key
     * @return boolean
     */
    public static boolean isParallelMultiInstance(BpmnModel model, String taskKey) {
        FlowNode flowNode = (FlowNode) model.getFlowElement(taskKey);
        if (flowNode == null) {
            return false;
        }

        if (flowNode.getBehavior() instanceof ParallelMultiInstanceBehavior) {
            return true;
        }

        return false;
    }

    /**
     * 获取所有用户任务节点
     * @param model bpmnModel对象
     * @return 用户任务节点列表
     */
    public static Collection<UserTask> getAllUserTaskEvent(BpmnModel model) {
        Process process = model.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        return getAllUserTaskEvent(flowElements, null);
    }

    public static Collection<UserTask> getAllUserTaskEvent(Collection<FlowElement> flowElements, Collection<UserTask> allElements) {
        allElements = allElements == null ? new ArrayList<>() : allElements;
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof UserTask) {
                allElements.add((UserTask) flowElement);
            }
            if (flowElement instanceof SubProcess) {
                // 继续深入子流程，进一步获取子流程
                allElements = getAllUserTaskEvent(((SubProcess) flowElement).getFlowElements(), allElements);
            }
        }
        return allElements;
    }

    /**
     * 获取下一级用户任务列表
     * @param source 指定节点
     * @return 用户任务列表
     */
    public static List<UserTask> findNextUserTasks(FlowElement source) {
        return findNextUserTasks(source, null, null);
    }

    public static List<UserTask> findNextUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = Optional.ofNullable(hasSequenceFlow).orElse(new HashSet<>());
        userTaskList = Optional.ofNullable(userTaskList).orElse(new ArrayList<>());
        // 获取出口连线
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
        if (CollectionUtils.isNotEmpty(sequenceFlows)) {
            for (SequenceFlow sequenceFlow : sequenceFlows) {
                // 如果发现连线重复，说明循环了，跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
                if (targetFlowElement instanceof UserTask) {
                    // 若节点为用户任务，加入到结果列表中
                    userTaskList.add((UserTask) targetFlowElement);
                } else {
                    // 若节点非用户任务，继续递归查找下一个节点
                    findNextUserTasks(targetFlowElement, hasSequenceFlow, userTaskList);
                }
            }
        }
        return userTaskList;
    }

    /**
     * 获取流程元素信息
     * @param model bpmnModel对象
     * @param flowElementId 元素ID
     * @return 元素信息
     */
    public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
        Process process = model.getMainProcess();
        return process.getFlowElement(flowElementId);
    }


    /**
     * 判断当前用户节点是否在并行网关内
     * @param model bpmnModel对象
     * @param taskKey 任务key
     * @return 布尔值
     */
    public static boolean isInParallelGateway(BpmnModel model, String taskKey) {
        List<ParallelGateway> parallelGateways = getParallelGateways(model);
        if (CollectionUtils.isEmpty(parallelGateways)) {
            return false;
        }

        for (ParallelGateway gateway : parallelGateways) {
            List<UserTask> userTasks = findNextUserTasks(gateway);
            List<String> userTaskKeys = userTasks.stream().map(UserTask::getId).collect(Collectors.toList());
            if (CollUtil.contains(userTaskKeys, taskKey)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 获取当前用户节点所属的并行网关
     * @param model bpmnModel对象
     * @param taskKey 任务key
     * @return 网关
     */
    public static ParallelGateway getParallelGateway(BpmnModel model, String taskKey) {
        List<ParallelGateway> parallelGateways = getParallelGateways(model);
        if (CollectionUtils.isNotEmpty(parallelGateways)) {
            for (ParallelGateway gateway : parallelGateways) {
                List<UserTask> userTasks = findNextUserTasks(gateway);
                List<String> userTaskKeys = userTasks.stream().map(UserTask::getId).collect(Collectors.toList());
                if (CollUtil.contains(userTaskKeys, taskKey)) {
                    return gateway;
                }
            }
        }

        return null;
    }

    private static List<ParallelGateway> getParallelGateways(BpmnModel model) {
        Process process = model.getMainProcess();
        List<ParallelGateway> gateways = new ArrayList<>();
        List<FlowElement> flowElements = (List<FlowElement>) process.getFlowElements();
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof ParallelGateway) {
                gateways.add((ParallelGateway) flowElement);
            }
        }

        return gateways;
    }


    /**
     * 判断当前用户节点是否在包容网关内
     * @param model bpmnModel对象
     * @param taskKey 任务key
     * @return 布尔值
     */
    public static boolean isInclusiveGateway(BpmnModel model, String taskKey) {
        List<InclusiveGateway> inclusiveGateways = getInclusiveGateways(model);
        if (CollectionUtils.isEmpty(inclusiveGateways)) {
            return false;
        }

        for (InclusiveGateway gateway : inclusiveGateways) {
            List<UserTask> userTasks = findNextUserTasks(gateway);
            List<String> userTaskKeys = userTasks.stream().map(UserTask::getId).collect(Collectors.toList());
            if (CollUtil.contains(userTaskKeys, taskKey)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 获取当前用户节点所属的包容网关
     * @param model bpmnModel对象
     * @param taskKey 任务key
     * @return 网关
     */
    public static InclusiveGateway getInclusiveGateway(BpmnModel model, String taskKey) {
        List<InclusiveGateway> inclusiveGateways = getInclusiveGateways(model);
        if (CollectionUtils.isNotEmpty(inclusiveGateways)) {
            for (InclusiveGateway inclusiveGateway : inclusiveGateways) {
                List<UserTask> userTasks = findNextUserTasks(inclusiveGateway);
                List<String> userTaskKeys = userTasks.stream().map(UserTask::getId).collect(Collectors.toList());
                if (CollUtil.contains(userTaskKeys, taskKey)) {
                    return inclusiveGateway;
                }
            }
        }
        return null;
    }

    private static List<InclusiveGateway> getInclusiveGateways(BpmnModel model) {
        List<InclusiveGateway> gateways = new ArrayList<>();
        Process process = model.getMainProcess();
        List<FlowElement> flowElements = (List<FlowElement>) process.getFlowElements();
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof InclusiveGateway) {
                gateways.add((InclusiveGateway) flowElement);
            }
        }
        return gateways;
    }


    /**
     * 迭代获取父级任务节点列表，向前找
     * @param source 起始节点
     * @param hasSequenceFlow 已经经过的连线的 ID，用于判断线路是否重复
     * @param userTaskList 已找到的用户任务节点
     * @return
     */
    public static List<UserTask> iteratorFindParentUserTasks(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;

        // 根据类型，获取入口连线
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
        if (sequenceFlows != null) {
            // 循环找到目标元素
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // 如果发现连线重复，说明循环了，跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                // 类型为用户节点，则新增父级节点
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
                    continue;
                }

                // 继续迭代
                userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }


    /**
     * 根据正在运行的任务节点，迭代获取子级任务节点列表，向后找
     * @param source 起始节点
     * @param runTaskKeyList 正在运行的任务 Key，用于校验任务节点是否是正在运行的节点
     * @param hasSequenceFlow 已经经过的连线的 ID，用于判断线路是否重复
     * @param userTaskList 需要撤回的用户任务列表
     * @return
     */
    public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
        userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;

        // 如果该节点为开始节点，且存在上级子节点，则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
        }

        // 根据类型，获取出口连线
        List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);

        if (sequenceFlows != null) {
            // 循环找到目标元素
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // 如果发现连线重复，说明循环了，跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                // 如果为用户任务类型，且任务节点的 Key 正在运行的任务中存在，添加
                if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
                    userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
                    continue;
                }
                // 如果节点为子流程节点情况，则从节点中的第一个节点开始获取
                if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
                    List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
                    // 如果找到节点，则说明该线路找到节点，不继续向下找，反之继续
                    if (CollectionUtils.isNotEmpty(childUserTaskList)) {
                        userTaskList.addAll(childUserTaskList);
                        continue;
                    }
                }
                // 继续迭代
                userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
            }
        }
        return userTaskList;
    }


    public static List<ExtensionElement> getExtensionElements(FlowElement flowElement, String key) {
        Map<String, List<ExtensionElement>> extensionElements = flowElement.getExtensionElements();
        if (ObjectUtil.isEmpty(extensionElements)) {
            return Collections.emptyList();
        }

        return extensionElements.get(key);
    }

    public static ExtensionElement getExtensionElement(FlowElement flowElement, String key) {
        List<ExtensionElement> extensionElements = getExtensionElements(flowElement, key);
        if (CollectionUtils.isEmpty(extensionElements)) {
            return null;
        }

        return extensionElements.get(0);
    }
}
