package cn.axzo.workflow.core.common.utils;

import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonModel;
import cn.axzo.workflow.common.model.request.bpmn.BpmnJsonNode;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelCreateDTO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.converter.json.AbstractBpmnJsonConverter;
import cn.axzo.workflow.core.converter.json.EndEventJsonConverter;
import cn.axzo.workflow.core.converter.json.ExclusiveGatewayJsonConverter;
import cn.axzo.workflow.core.converter.json.NotSupportConverter;
import cn.axzo.workflow.core.converter.json.ParallelGatewayJsonConverter;
import cn.axzo.workflow.core.converter.json.ReceiveTaskJsonConverter;
import cn.axzo.workflow.core.converter.json.SequenceFlowJsonConverter;
import cn.axzo.workflow.core.converter.json.ServiceTaskJsonConverter;
import cn.axzo.workflow.core.converter.json.StartEventJsonConverter;
import cn.axzo.workflow.core.converter.json.UserTaskJsonConverter;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BaseElement;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.ExclusiveGateway;
import org.flowable.bpmn.model.ExtensionAttribute;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Gateway;
import org.flowable.bpmn.model.ParallelGateway;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.image.ProcessDiagramGenerator;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CHECKED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_KEY;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
import static cn.axzo.workflow.common.constant.BpmnConstants.END_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_NODE_JSON;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.SEQUENCE_FLOW_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.START_EVENT_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_CARBON_COPY_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CONDITION;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EMPTY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_EXCLUSIVE_GATEWAY;
import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_COMMON_ERROR;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getButtonConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getFieldConfig;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNoticeConfig;

/**
 * BPMN json 格式转换工具
 *
 * @author wangli
 * @since 2023/10/12 11:43
 */
public final class BpmnJsonConverterUtil {
    private BpmnJsonConverterUtil() {
    }

    protected static BpmnXMLConverter bpmnXmlConverter = new BpmnXMLConverter();
    protected static BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();

    private static final Logger log = LoggerFactory.getLogger(BpmnJsonConverterUtil.class);
    private static final Map<Class<? extends BaseElement>, AbstractBpmnJsonConverter<? extends BaseElement>> CONVERTERS =
            new HashMap<>();

    static {
        CONVERTERS.put(NotSupportConverter.NotSupportFlowElement.class, new NotSupportConverter());
        CONVERTERS.put(StartEvent.class, new StartEventJsonConverter());
        CONVERTERS.put(SequenceFlow.class, new SequenceFlowJsonConverter());
        CONVERTERS.put(EndEvent.class, new EndEventJsonConverter());

        CONVERTERS.put(ExclusiveGateway.class, new ExclusiveGatewayJsonConverter());
        CONVERTERS.put(ParallelGateway.class, new ParallelGatewayJsonConverter());
        CONVERTERS.put(UserTask.class, new UserTaskJsonConverter());
        CONVERTERS.put(ServiceTask.class, new ServiceTaskJsonConverter());
        CONVERTERS.put(ReceiveTask.class, new ReceiveTaskJsonConverter());
    }

    /**
     * 将 BpmnModel 转成 JSON 格式对应对象
     *
     * @param bpmnModel Flowable 标准模型
     * @return {@link BpmnJsonNode} json 格式对象,可以直接解析
     */
    public static BpmnJsonModel convertToJson(BpmnModel bpmnModel) {
        BpmnJsonModel model = new BpmnJsonModel();
        Process mainProcess = bpmnModel.getMainProcess();
        BpmnJsonNode bpmnJsonNode = JSON.parseObject(mainProcess.getAttributeValue(null, FLOW_NODE_JSON),
                BpmnJsonNode.class);
        model.setNode(bpmnJsonNode);
        getNoticeConfig(mainProcess).ifPresent(model::setNoticeConf);
        getButtonConfig(mainProcess).ifPresent(model::setButtonConf);
        getFieldConfig(mainProcess).ifPresent(model::setFieldConf);
        return model;
    }

    /**
     * 将 json 格式数据转的 BpmnJsonNode 对象,转换成 Flowable 标准的模型 {@link  BpmnModel}
     *
     * @param bpmnJsonNode json 格式对象,可以直接解析
     * @param noticeConf
     * @param buttonConf
     * @param fieldConf
     * @return {@link BpmnModel}
     */
    public static BpmnModel convertToBpmn(BpmnJsonNode bpmnJsonNode, String id, String name, String documentation,
                                          BpmnNoticeConf noticeConf, BpmnButtonConf buttonConf,
                                          List<BpmnFieldConf> fieldConf,
                                          String serverVersionStr) {
        if (Objects.isNull(bpmnJsonNode)) {
            throw new WorkflowEngineException(CONVERTOR_COMMON_ERROR, "JSON 数据为空");
        }
        // 提交的 BpmnJsonNode 全部都是任务是可执行节点, 这里的转换是全局都会增加开始和结束节点. 因为前段的提交的数据是不包含开始和结束
        BpmnModel bpmnModel = new BpmnModel();

        ExtensionAttribute serverVersion = new ExtensionAttribute();
        serverVersion.setName(FLOW_SERVER_VERSION);
        serverVersion.setValue(serverVersionStr);
        ExtensionAttribute jsonMetaValue = new ExtensionAttribute();
        jsonMetaValue.setName(FLOW_NODE_JSON);
        jsonMetaValue.setValue(JSONUtil.toJsonStr(bpmnJsonNode));

        Process mainProcess = new Process();
        mainProcess.setId(id);
        mainProcess.setName(name);
        mainProcess.setDocumentation(documentation);
        mainProcess.addAttribute(serverVersion);
        mainProcess.addAttribute(jsonMetaValue);

        // 设置流程的通知管理配置
        setProcessNoticeConfig(noticeConf, mainProcess);
        // 设置流程的默认的按钮配置
        setProcessButtonConfig(buttonConf, mainProcess);
        // 设置流程的字段元数据配置
        setProcessFieldConfig(fieldConf, mainProcess);

        bpmnModel.addProcess(mainProcess);

        // 创建固定的开始节点
        mainProcess.addFlowElement(convertJsonToElement(StartEvent.class, mainProcess));

        // 创建固定的结束节点
        mainProcess.addFlowElement(convertJsonToElement(EndEvent.class, mainProcess));

        // 解析前端传入的模型设计 json
        List<String> lastNodeIds = create(bpmnJsonNode, mainProcess, bpmnModel, null, START_EVENT_ID);

        if (CollectionUtils.isEmpty(lastNodeIds)) {
            throw new WorkflowEngineException(CONVERTOR_COMMON_ERROR, "未找到链接结束节点的节点数据");
        }

        lastNodeIds.forEach(lastNodeId -> {
            SequenceFlow sequenceFlow = new SequenceFlow();
            sequenceFlow.setId(id(SEQUENCE_FLOW_ID));
            sequenceFlow.setSourceRef(lastNodeId);
            sequenceFlow.setTargetRef(END_EVENT_ID);
            mainProcess.addFlowElement(sequenceFlow);
        });
        new BpmnAutoLayout(bpmnModel).execute();
        return bpmnModel;
    }

    private static void setProcessFieldConfig(List<BpmnFieldConf> fieldConf, Process mainProcess) {
        if (CollectionUtils.isEmpty(fieldConf)) {
            return;
        }
        ExtensionElement fieldConfigElement = new ExtensionElement();
        fieldConfigElement.setName(CONFIG_FIELD);

        if (!CollectionUtils.isEmpty(fieldConf)) {
            fieldConf.forEach(i -> {
                ExtensionElement field = new ExtensionElement();
                field.setName(CONFIG_FIELD_META);

                ExtensionAttribute fieldDataType = new ExtensionAttribute();
                fieldDataType.setName(ELEMENT_ATTRIBUTE_TYPE);
                fieldDataType.setValue(i.getType());
                field.addAttribute(fieldDataType);

                ExtensionAttribute fieldName = new ExtensionAttribute();
                fieldName.setName(ELEMENT_ATTRIBUTE_NAME);
                fieldName.setValue(i.getName());
                field.addAttribute(fieldName);

                ExtensionAttribute fieldCode = new ExtensionAttribute();
                fieldCode.setName(ELEMENT_ATTRIBUTE_CODE);
                fieldCode.setValue(i.getCode());
                field.addAttribute(fieldCode);

                if (!CollectionUtils.isEmpty(i.getOptions())) {
                    i.getOptions().forEach(j -> {
                        ExtensionElement option = new ExtensionElement();
                        option.setName(CONFIG_FIELD_OPTION);

                        ExtensionAttribute optionName = new ExtensionAttribute();
                        optionName.setName(ELEMENT_ATTRIBUTE_NAME);
                        optionName.setValue(j.getName());
                        option.addAttribute(optionName);

                        ExtensionAttribute optionValue = new ExtensionAttribute();
                        optionValue.setName(ELEMENT_ATTRIBUTE_VALUE);
                        optionValue.setValue(j.getValue());
                        option.addAttribute(optionValue);

                        field.addChildElement(option);
                    });
                }
                fieldConfigElement.addChildElement(field);
            });
        }
        mainProcess.addExtensionElement(fieldConfigElement);
    }

    private static void setProcessButtonConfig(BpmnButtonConf buttonConf, Process mainProcess) {
        if (Objects.isNull(buttonConf)) {
            return;
        }
        ExtensionElement buttonConfigElement = new ExtensionElement();
        buttonConfigElement.setName(CONFIG_BUTTON);

        buildMetaButton(buttonConf.getInitiator(), CONFIG_BUTTON_TYPE_INITIATOR, buttonConfigElement);
        buildMetaButton(buttonConf.getCurrent(), CONFIG_BUTTON_TYPE_CURRENT, buttonConfigElement);
        buildMetaButton(buttonConf.getHistory(), CONFIG_BUTTON_TYPE_HISTORY, buttonConfigElement);
        buildMetaButton(buttonConf.getCarbonCopy(), CONFIG_BUTTON_TYPE_CARBON_COPY, buttonConfigElement);

        mainProcess.addExtensionElement(buttonConfigElement);
    }

    public static void buildMetaButton(List<BpmnButtonMetaInfo> buttonMetaInfos, String buttonType,
                                       ExtensionElement buttonConfigElement) {
        if (!CollectionUtils.isEmpty(buttonMetaInfos)) {
            ExtensionElement initiator = new ExtensionElement();
            initiator.setName(buttonType);
            buttonMetaInfos.forEach(i -> {
                ExtensionElement button = new ExtensionElement();
                button.setName(CONFIG_BUTTON_META);

                ExtensionAttribute keyAttribute = new ExtensionAttribute();
                keyAttribute.setName(ELEMENT_ATTRIBUTE_KEY);
                keyAttribute.setValue(i.getBtnKey());
                button.addAttribute(keyAttribute);

                ExtensionAttribute nameAttribute = new ExtensionAttribute();
                nameAttribute.setName(ELEMENT_ATTRIBUTE_NAME);
                nameAttribute.setValue(i.getBtnName());
                button.addAttribute(nameAttribute);

                ExtensionAttribute valueAttribute = new ExtensionAttribute();
                valueAttribute.setName(ELEMENT_ATTRIBUTE_CHECKED);
                valueAttribute.setValue(String.valueOf(i.getChecked()));
                button.addAttribute(valueAttribute);

                ExtensionAttribute orderAttribute = new ExtensionAttribute();
                orderAttribute.setName(ELEMENT_ATTRIBUTE_ORDER);
                orderAttribute.setValue(String.valueOf(i.getOrder()));
                button.addAttribute(orderAttribute);

                ExtensionAttribute disabledAttribute = new ExtensionAttribute();
                disabledAttribute.setName(ELEMENT_ATTRIBUTE_DISABLED);
                disabledAttribute.setValue(String.valueOf(i.getDisabled()));
                button.addAttribute(disabledAttribute);
                initiator.addChildElement(button);
            });
            buttonConfigElement.addChildElement(initiator);
        }
    }

    private static void setProcessNoticeConfig(BpmnNoticeConf noticeConf, Process mainProcess) {
        if (Objects.isNull(noticeConf)) {
            return;
        }
        ExtensionElement noticeConfigElement = new ExtensionElement();
        noticeConfigElement.setName(CONFIG_NOTICE);

        // 通知消息模板配置
        if (Objects.nonNull(noticeConf.getNotice()) && Objects.nonNull(noticeConf.getNotice().getNoticeMessageId())) {
            ExtensionElement noticeMessage = new ExtensionElement();
            noticeMessage.setName(TEMPLATE_NOTICE_MESSAGE_ID);
            ExtensionAttribute noticeMessageAttribute = new ExtensionAttribute();
            noticeMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
            noticeMessageAttribute.setValue(noticeConf.getNotice().getNoticeMessageId());
            noticeMessage.addAttribute(noticeMessageAttribute);
            noticeMessage.setElementText(StringUtils.hasText(noticeConf.getNotice().getViewJson()) ?
                    noticeConf.getNotice().getViewJson() : "");
            noticeConfigElement.addChildElement(noticeMessage);
        }

        // 代办消息模板配置
        if (Objects.nonNull(noticeConf.getPending()) && Objects.nonNull(noticeConf.getPending().getPendingMessageId())) {
            ExtensionElement pendingMessage = new ExtensionElement();
            pendingMessage.setName(TEMPLATE_PENDING_MESSAGE_ID);
            ExtensionAttribute pendingMessageAttribute = new ExtensionAttribute();
            pendingMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
            pendingMessageAttribute.setValue(noticeConf.getPending().getPendingMessageId());
            pendingMessage.addAttribute(pendingMessageAttribute);
            pendingMessage.setElementText(StringUtils.hasText(noticeConf.getPending().getViewJson()) ?
                    noticeConf.getPending().getViewJson() : "");
            noticeConfigElement.addChildElement(pendingMessage);
        }

        // 抄送模板配置
        if (Objects.nonNull(noticeConf.getCarbonCopy()) && Objects.nonNull(noticeConf.getCarbonCopy().getCarbonCopyMessageId())) {
            ExtensionElement carbonCopyMessage = new ExtensionElement();
            carbonCopyMessage.setName(TEMPLATE_CARBON_COPY_MESSAGE_ID);
            ExtensionAttribute carbonCopyMessageAttribute = new ExtensionAttribute();
            carbonCopyMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
            carbonCopyMessageAttribute.setValue(noticeConf.getCarbonCopy().getCarbonCopyMessageId());
            carbonCopyMessage.addAttribute(carbonCopyMessageAttribute);
            carbonCopyMessage.setElementText(StringUtils.hasText(noticeConf.getCarbonCopy().getViewJson()) ?
                    noticeConf.getCarbonCopy().getViewJson() : "");
            noticeConfigElement.addChildElement(carbonCopyMessage);
        }

        // 短信模板配置
        if (Objects.nonNull(noticeConf.getSms()) && Objects.nonNull(noticeConf.getSms().getSmsId())) {
            ExtensionElement smsMessage = new ExtensionElement();
            smsMessage.setName(TEMPLATE_SMS_MESSAGE_ID);
            ExtensionAttribute smsMessageAttribute = new ExtensionAttribute();
            smsMessageAttribute.setName(ELEMENT_ATTRIBUTE_VALUE);
            smsMessageAttribute.setValue(noticeConf.getSms().getSmsId());
            smsMessage.addAttribute(smsMessageAttribute);
            smsMessage.setElementText(StringUtils.hasText(noticeConf.getSms().getViewJson()) ?
                    noticeConf.getSms().getViewJson() : "");
            noticeConfigElement.addChildElement(smsMessage);
        }
        mainProcess.addExtensionElement(noticeConfigElement);
    }


    public static byte[] transformBytes(BpmnModel bpmnModel) {
        return new BpmnXMLConverter().convertToXML(bpmnModel);
    }

    /**
     * 设置 BpmnModel 的 TargetNamespace
     *
     * @param bpmnModel 被设置的模型
     * @param category  category 的值
     */
    public static void setCategory(BpmnModel bpmnModel, String category) {
        bpmnModel.setTargetNamespace(category);
    }

    /**
     * 创建对应类型节点并保存在 bpmnModel 中
     *
     * @param preNodeIds   上级节点的 Id 结合,用于链接当前创建节点
     * @param bpmnJsonNode 前端传入节点数据,不是全数据,而是对应层级
     * @param mainProcess  主 Process 对象
     * @param bpmnModel    最终的 BPMN model
     * @return 创建的节点的 ID
     */
    private static List<String> create(BpmnJsonNode bpmnJsonNode, Process mainProcess,
                                       BpmnModel bpmnModel, BpmnJsonNode defaultConditionConnectNode, String... preNodeIds) {
        // 设置来源节点
        bpmnJsonNode.setIncoming(Lists.newArrayList(preNodeIds));

        Class<? extends BaseElement> clz = chooseNodeClass(bpmnJsonNode);

        // 根据 BpmnJsonNode 创建节点
        FlowElement flowElement = convertJsonToElement(clz, bpmnJsonNode, mainProcess);
        mainProcess.addFlowElement(flowElement);

        connectPreNodes(bpmnJsonNode, mainProcess, preNodeIds);

        // 只有网关才会涉及到 branch
        List<String> branchLastNodeIds = new ArrayList<>();
        if (!CollectionUtils.isEmpty(bpmnJsonNode.getBranches())) {
            Gateway gateway = (Gateway) flowElement;
            for (BpmnJsonNode branch : bpmnJsonNode.getBranches()) {
                // branch == node_condition
                BpmnJsonNode nextJsonNode =
                        (Objects.isNull(branch.getChildren()) || Objects.isNull(branch.getChildren().getType())
                                || Objects.equals(NODE_EMPTY, branch.getChildren().getType()))
                                ? bpmnJsonNode.getChildren() : branch.getChildren();

                if (Objects.isNull(nextJsonNode) || Objects.isNull(nextJsonNode.getId()) ||
                        (Objects.equals(NODE_EMPTY, nextJsonNode.getType()) &&
                                (Objects.isNull(nextJsonNode.getChildren()) || Objects.isNull(nextJsonNode.getChildren().getId())))) {
                    BpmnJsonNode tempEndNode = new BpmnJsonNode();
                    tempEndNode.setIncoming(Lists.newArrayList(gateway.getId()));
                    if (Objects.isNull(defaultConditionConnectNode)) {
                        tempEndNode.setId(END_EVENT_ID);
                    } else {
                        if (Objects.equals(defaultConditionConnectNode.getType(), NODE_EMPTY) && Objects.nonNull(defaultConditionConnectNode.getChildren())) {
                            if (StringUtils.hasText(defaultConditionConnectNode.getChildren().getId())) {
                                tempEndNode.setId(defaultConditionConnectNode.getChildren().getId());
                            } else {
                                tempEndNode.setId(END_EVENT_ID);
                            }
                        } else {
                            tempEndNode.setId(defaultConditionConnectNode.getId());
                        }
                        defaultConditionConnectNode = null;
                    }
                    nextJsonNode = tempEndNode;
                } else {
                    if (Objects.equals(NODE_EMPTY, nextJsonNode.getType()) && Objects.nonNull(nextJsonNode.getChildren())) {
                        nextJsonNode = nextJsonNode.getChildren();
                    }
                    nextJsonNode.setIncoming(Lists.newArrayList(gateway.getId()));
                    // 将 node_condition 放入计算节点的上级属性中，方便顺序流转换器对条件进行处理
                    nextJsonNode.setPreJsonNode(branch);
                }
                SequenceFlow sequenceFlow = (SequenceFlow) convertJsonToElement(SequenceFlow.class, nextJsonNode,
                        mainProcess);
                mainProcess.addFlowElement(sequenceFlow);

                // 设置网关默认流
                if (Objects.nonNull(branch.getProperty()) && Boolean.TRUE.equals(branch.getProperty().getDefaultBranch())) {
                    // 如果是默认流, 以防止前端输入错误, 强制置空
                    sequenceFlow.setConditionExpression(null);
                    gateway.setDefaultFlow(sequenceFlow.getId());
                }

                if (Objects.nonNull(branch.getChildren()) && StringUtils.hasLength(branch.getChildren().getId())
                        && !Objects.equals(NODE_EMPTY, branch.getChildren().getType())) {
                    branchLastNodeIds.addAll(create(branch.getChildren(), mainProcess, bpmnModel,
                            Objects.isNull(defaultConditionConnectNode) ? bpmnJsonNode.getChildren() : defaultConditionConnectNode));
                }

            }
        }

        // 开始处理下级节点
        BpmnJsonNode children = bpmnJsonNode.getChildren();
        if (Objects.isNull(children)
                || (Objects.equals(NODE_EMPTY, children.getType()) && (Objects.isNull(children.getChildren()) || Objects.isNull(children.getChildren().getId())))
                || !StringUtils.hasLength(children.getId())) {
            if (CollectionUtils.isEmpty(branchLastNodeIds)) {
                return Lists.newArrayList(flowElement.getId());
            } else {
                return branchLastNodeIds;
            }
        } else {
            if (Objects.equals(NODE_EMPTY, children.getType())
                    && Objects.nonNull(children.getChildren())
                    && Objects.nonNull(children.getChildren().getId())) {
                children = children.getChildren();
            }
            if (CollectionUtils.isEmpty(branchLastNodeIds)) {
                //网关分支条件连接到下一节点，children节点不再连接前继网关节点
                if (bpmnJsonNode.getType() == NODE_EXCLUSIVE_GATEWAY) {
                    return create(children, mainProcess, bpmnModel, defaultConditionConnectNode);
                }
                return create(children, mainProcess, bpmnModel, defaultConditionConnectNode, flowElement.getId());
            }
            return create(children, mainProcess, bpmnModel, defaultConditionConnectNode, branchLastNodeIds.toArray(new String[0]));
        }
    }

    private static void connectPreNodes(BpmnJsonNode bpmnJsonNode, Process mainProcess, String[] preNodeIds) {
        // 连接当前节点与前一个节点
        if (Lists.newArrayList(preNodeIds).isEmpty()) {
            // first time entrance, do nothing.
        } else if (Lists.newArrayList(preNodeIds).size() == 1 && !NODE_CONDITION.equals(bpmnJsonNode.getType())) {
            bpmnJsonNode.setPreJsonNode(null);
            mainProcess.addFlowElement(convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess));
        } else {
            // 将网关分支的最末级节点 ID 关联到网关的 children 节点上，网关协议转换才算闭环
            Arrays.stream(preNodeIds).forEach(income -> {
                bpmnJsonNode.setIncoming(Lists.newArrayList(income));
                bpmnJsonNode.setPreJsonNode(null);
                FlowElement sequenceFlow = convertJsonToElement(SequenceFlow.class, bpmnJsonNode, mainProcess);
                mainProcess.addFlowElement(sequenceFlow);
            });
        }
    }

    private static Class<? extends BaseElement> chooseNodeClass(BpmnJsonNode bpmnJsonNode) {
        Class<? extends BaseElement> clz;
        switch (bpmnJsonNode.getType()) {
            case NODE_STARTER:
                // 该节点主要解决仿钉钉的设计器里面无法设置标准开始和结束节点,而是特殊的一个发起人节点
            case NODE_TASK:
                clz = UserTask.class;
                break;
            case NODE_EXCLUSIVE_GATEWAY:
                clz = ExclusiveGateway.class;
                break;
            case NODE_PARALLEL_GATEWAY:
                clz = ParallelGateway.class;
                break;
            case NODE_CONDITION:
                // 这个节点非常特殊,整个协议转换完全不会走到这里
                clz = SequenceFlow.class;
                break;
            case NODE_BUSINESS:
                // "业务节点"是很特殊包装节点,因为它在前端有两种配法:
                // 一种是: 可以不配置人, 当运行到该节点时, 需要由外部发送信号来触发(这个信号的发送方通常是程序应用), 流程继续运行.
                // 另外一种是: 可以配置人, 当运行到该节点时, 有需要人为来处理. 这种情况下, 该节点就是一个普通的任务节点.
                clz = ReceiveTask.class;
                if (!Objects.equals(ApprovalMethodEnum.nobody, bpmnJsonNode.getProperty().getApprovalMethod())) {
                    clz = UserTask.class;
                }
                break;
            case NODE_TRIGGER:
                // 这个类型目前暂不支持
            case NODE_CARBON_COPY:
                // 这里可以细化, 待后续有实际场景了,再处理, 现目前只有 "抄送" 功能可能会用到
                clz = ServiceTask.class;
                break;
            default:
                clz = NotSupportConverter.NotSupportFlowElement.class;
                break;
        }
        return clz;
    }

    private static FlowElement convertJsonToElement(Class<? extends BaseElement> clz, Process process) {
        return convertJsonToElement(clz, null, process);
    }

    private static FlowElement convertJsonToElement(Class<? extends BaseElement> clz, BpmnJsonNode bpmnJsonNode,
                                                    Process process) {
        AbstractBpmnJsonConverter converter = CONVERTERS.getOrDefault(clz, new NotSupportConverter());
        FlowElement flowElement = converter.convertJsonToElement(bpmnJsonNode, process);
        if (Objects.nonNull(bpmnJsonNode)) {
            converter.addNodeTypeAttribute(flowElement, copy(bpmnJsonNode));
            converter.addJsonValueAttribute(flowElement, copy(bpmnJsonNode));
        }
        return flowElement;
    }

    private static BpmnJsonNode copy(BpmnJsonNode source) {
        if (Objects.isNull(source)) {
            return null;
        }
        BpmnJsonNode target = new BpmnJsonNode();
        target.setId(source.getId());
        target.setParentId(source.getParentId());
        target.setType(source.getType());
        target.setName(source.getName());
        target.setProperty(source.getProperty());
        target.setIncoming(source.getIncoming());
        return target;
    }

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

    public static void main(String[] args) throws Exception {
        converterBPMN();
        checkXml();
    }

    private static void converterBPMN() throws Exception {
        //        String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
        //                "/resources/配置台模型.json";
        //        String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
        //                "/resources/权限点模型.json";
        String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
                "/resources/request.json";
        byte[] bytes = Files.readAllBytes(Paths.get(fileName));
        String content = new String(bytes, StandardCharsets.UTF_8);

        BpmnModelCreateDTO model = JSON.parseObject(content, BpmnModelCreateDTO.class);
        BpmnModel bpmnModel = convertToBpmn(model.getJsonModel().getNode(), "id", "测试", "remark",
                model.getJsonModel().getNoticeConf(),
                model.getJsonModel().getButtonConf(), model.getJsonModel().getFieldConf(),
                "1.3.1-SNAPSHOT");

        convertToJson(bpmnModel);

        //        ServiceTask serviceTask = (ServiceTask) bpmnModel.getFlowElement("node_946990365785");
        //        Optional<List<BpmnCarbonCopyConf>> carbonCopyConfigs = BpmnMetaParserHelper.getCarbonCopyConfigs
        //        (serviceTask);

//        generateImage(bpmnModel);

        byte[] xml = transformBytes(bpmnModel);
        String xmlFileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
                "/resources/convertorXML.bpmn";
        Path path = Paths.get(xmlFileName);
        Files.write(path, xml);
    }

    public static void generateImage(BpmnModel bpmnModel) {
        String pngName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main" +
                "/resources/xmlImage.png";
        ProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();

        // 设置字体渲染选项
        System.setProperty("java.awt.headless", "false");
        System.setProperty("awt.useSystemAAFontSettings", "on");
        System.setProperty("swing.aatext", "true");
        System.setProperty("sun.java2d.xrender", "true");

        try {
            // 创建输出流
            OutputStream outputStream = Files.newOutputStream(new File(pngName).toPath());
            // 生成图像并写入输出流
            InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", new ArrayList<>(), 1.0d, true);

            // 将图像流写入输出流
            try {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = imageStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            } catch (Exception e) {
                // 处理异常
            } finally {
                try {
                    imageStream.close();
                    outputStream.close();
                } catch (Exception e) {
                    // 处理异常
                }
            }
            // 关闭输出流
            outputStream.close();
        } catch (Exception e) {
            // 处理异常
        }
    }

    public static void checkXml() throws Exception {
        String fileName = "/Users/wangli/work/company/yizhi/workflow-engine/workflow-engine-server/src/main/resources/convertorXML.bpmn";
        Path path = Paths.get(fileName);
        XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();
        InputStreamReader xmlIn = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8);
        XMLStreamReader xtr = xif.createXMLStreamReader(xmlIn);
        BpmnModel bpmnModel = bpmnXmlConverter.convertToBpmnModel(xtr);
        if (org.flowable.editor.language.json.converter.util.CollectionUtils.isEmpty(bpmnModel.getProcesses())) {
            System.out.println("No process found in definition " + fileName);
        }
        if (bpmnModel.getLocationMap().size() == 0) {
            BpmnAutoLayout bpmnLayout = new BpmnAutoLayout(bpmnModel);
            bpmnLayout.execute();
        }

        ObjectNode modelNode = bpmnJsonConverter.convertToJson(bpmnModel);

        Process process = bpmnModel.getMainProcess();
        String name = process.getId();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(process.getName())) {
            name = process.getName();
        }
        String description = process.getDocumentation();
    }
}
