package com.junmp.junmpProcess.utils;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.junmp.junmpProcess.common.WorkFlowConstants;
import com.junmp.junmpProcess.exception.WorkFlowException;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.delegate.TaskListener;
import com.junmp.junmpProcess.dto.json.ChildNode;
import com.junmp.junmpProcess.dto.json.ConditionInfo;
import com.junmp.junmpProcess.dto.json.GroupsInfo;
import com.junmp.junmpProcess.dto.json.Properties;
import com.junmp.junmpProcess.enums.ModeEnums;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_CLASS;
import static org.flowable.bpmn.model.ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION;

/**
 * @author LoveMyOrange
 * @create 2022-10-10 17:47
 */
public class BpmnModelUtils {

    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;
    }

    public static SequenceFlow connect(String from, String to, List<SequenceFlow> sequenceFlows, Map<String, ChildNode> childNodeMap, Process process) {
        SequenceFlow flow = new SequenceFlow();
        String sequenceFlowId = id("sequenceFlow");
        if (process.getFlowElement(from) != null && process.getFlowElement(from) instanceof ExclusiveGateway) {
            ChildNode childNode = childNodeMap.get(to);
            if (childNode != null) {
                String parentId = childNode.getParentId();
                if (StringUtils.isNotBlank(parentId)) {
                    ChildNode parentNode = childNodeMap.get(parentId);
                    if (parentNode != null) {
                        if (Type.CONDITION.type.equals(parentNode.getType())) {
                            sequenceFlowId = parentNode.getId();
                            flow.setName(parentNode.getName());

                            if (Boolean.FALSE.equals(parentNode.getTypeElse())) {
                                //解析条件表达式
                                Properties props = parentNode.getProps();
                                String expression = props.getExpression();
                                List<GroupsInfo> groups = props.getGroups();
                                String groupsType = props.getGroupsType();
                                if (StringUtils.isNotBlank(expression)) {
                                    flow.setConditionExpression("${" + expression + "}");
                                } else {

                                    StringBuffer conditionExpression = new StringBuffer();
                                    conditionExpression.append("${ ");

                                    for (int i = 0; i < groups.size(); i++) {
                                        conditionExpression.append(" ( ");
                                        GroupsInfo group = groups.get(i);
                                        List<String> cids = group.getCids();
                                        String groupType = group.getGroupType();
                                        List<ConditionInfo> conditions = group.getConditions();
                                        for (int j = 0; j < conditions.size(); j++) {
                                            conditionExpression.append(" ");
                                            ConditionInfo condition = conditions.get(j);
                                            String compare = condition.getCompare();
                                            String id = condition.getId();
                                            String title = condition.getTitle();
                                            List<Object> value = condition.getValue();
                                            String valueType = condition.getValueType();
                                            if ("String".equals(valueType)) {
                                                if ("=".equals(compare)) {
                                                    String str = StringUtils.join(value, ",");
                                                    str = "'" + str + "'";
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "strEqualsMethod(" + id + "," + str + ") ");
                                                } else {
                                                    List<String> tempList = new ArrayList<>();
                                                    for (Object o : value) {
                                                        String s = o.toString();
                                                        s = "'" + s + "'";
                                                        tempList.add(s);
                                                    }
                                                    String str = StringUtils.join(tempList, ",");
//                                                String str = StringUtils.join(value, ",");
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "strContainsMethod(" + id + "," + str + ") ");
                                                }
                                            } else if ("Number".equals(valueType)) {
                                                String str = StringUtils.join(value, ",");
                                                if ("=".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberEquals(" + id + "," + str + ") ");
                                                } else if (">".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberGt(" + id + "," + str + ") ");
                                                } else if (">=".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberGtEquals(" + id + "," + str + ") ");
                                                } else if ("<".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberLt(" + id + "," + str + ") ");
                                                } else if ("<=".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberLtEquals(" + id + "," + str + ") ");
                                                } else if ("IN".equals(compare)) {
                                                    conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "numberContains(" + id + "," + str + ") ");
                                                } else if ("B".equals(compare)) {
                                                    conditionExpression.append("  " + WorkFlowConstants.EXPRESSION_CLASS + "b(" + id + "," + str + ") ");
                                                } else if ("AB".equals(compare)) {
                                                    conditionExpression.append("  " + WorkFlowConstants.EXPRESSION_CLASS + "ab(" + id + "," + str + ") ");
                                                } else if ("BA".equals(compare)) {
                                                    conditionExpression.append("  " + WorkFlowConstants.EXPRESSION_CLASS + "ba(" + id + "," + str + ") ");
                                                } else if ("ABA".equals(compare)) {
                                                    conditionExpression.append("  " + WorkFlowConstants.EXPRESSION_CLASS + "aba(" + id + "," + str + ") ");
                                                }
                                            } else if ("User".equals(valueType)) {
                                                List<String> userIds = new ArrayList<>();
                                                for (Object o : value) {
                                                    JSONObject obj = (JSONObject) o;
                                                    userIds.add(obj.getString("id"));
                                                }
                                                String str = StringUtils.join(userIds, ",");
                                                conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "userStrContainsMethod(" + id + "," + str + ") ");
                                            } else if ("Dept".equals(valueType)) {
                                                List<String> userIds = new ArrayList<>();
                                                for (Object o : value) {
                                                    JSONObject obj = (JSONObject) o;
                                                    userIds.add(obj.getString("id"));
                                                }
                                                String str = StringUtils.join(userIds, ",");
                                                conditionExpression.append(" " + WorkFlowConstants.EXPRESSION_CLASS + "deptStrContainsMethod(" + id + "," + str + ") ");
                                            } else {
                                                continue;
                                            }

                                            if (conditions.size() > 1 && j != (conditions.size() - 1)) {
                                                if ("OR".equals(groupType)) {
                                                    conditionExpression.append(" || ");
                                                } else {
                                                    conditionExpression.append(" && ");
                                                }
                                            }

                                            if (i == (conditions.size() - 1)) {
                                                conditionExpression.append(" ");
                                            }
                                        }


                                        conditionExpression.append(" ) ");

                                        if (groups.size() > 1 && i != (groups.size() - 1)) {
                                            if ("OR".equals(groupsType)) {
                                                conditionExpression.append(" || ");
                                            } else {
                                                conditionExpression.append(" && ");
                                            }
                                        }


                                    }
                                    conditionExpression.append("} ");
                                    flow.setConditionExpression(conditionExpression.toString());
                                }
                            }
                        }
                    }
                }
            }
        }
        flow.setId(sequenceFlowId);
        flow.setSourceRef(from);
        flow.setTargetRef(to);
        sequenceFlows.add(flow);
        return flow;
    }

    private static String stringEquals(ConditionInfo condition) {
        return null;
    }


    public static StartEvent createStartEvent() {
        StartEvent startEvent = new StartEvent();
        startEvent.setId(WorkFlowConstants.START_EVENT_ID);
        startEvent.setInitiator("applyUserId");
        return startEvent;
    }

    public static EndEvent createEndEvent() {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(WorkFlowConstants.END_EVENT_ID);
        return endEvent;
    }









    private enum Type {
        /**
         * 并行事件
         */
        CONCURRENTS("CONCURRENTS", ParallelGateway.class),
        CONCURRENT("CONCURRENT", SequenceFlow.class),
        /**
         * 排他事件
         */
        CONDITION("CONDITION", ExclusiveGateway.class),
        CONDITIONS("CONDITIONS", ExclusiveGateway.class),
        /**
         * 任务
         */
        USER_TASK("APPROVAL", UserTask.class),
        EMPTY("EMPTY", Object.class),
        ROOT("ROOT", UserTask.class),
        CC("CC", ServiceTask.class),
        TRIGGER("TRIGGER", ServiceTask.class),
        DELAY("DELAY", IntermediateCatchEvent.class);
        private String type;

        private Class<?> typeClass;

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

        public final static Map<String, 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);
        }

    }



    public static ChildNode getChildNode(ChildNode childNode, String nodeId) {
        Map<String, ChildNode> childNodeMap = new HashMap<>();
        if (StringUtils.isNotBlank(childNode.getId())) {
            getChildNode(childNode, childNodeMap);
        }

        Set<String> set = childNodeMap.keySet();
        for (String s : set) {
            if (StringUtils.isNotBlank(s)) {
                if (("Node_"+s).equals(nodeId)) {
                    return childNodeMap.get(s);
                }
            }
        }
        return null;
    }

    private static void getChildNode(ChildNode childNode, Map<String, ChildNode> childNodeMap) {
        childNodeMap.put(childNode.getId(), childNode);
        List<ChildNode> branchs = childNode.getConditionNodes();
        ChildNode children = childNode.getChildNode();
        if (branchs != null && branchs.size() > 0) {
            for (ChildNode branch : branchs) {
                if (StringUtils.isNotBlank(branch.getId())) {
                    childNodeMap.put(branch.getId(), branch);
                    getChildNode(branch, childNodeMap);
                }
            }
        }

        if (children != null) {
            childNodeMap.put(children.getId(), children);
            getChildNode(children, childNodeMap);
        }

    }

}
