package com.huigou.explorer.converters;

import com.huigou.explorer.converters.bpmn.ElementConverter;
import com.huigou.explorer.converters.mxgraph.MxElementConverter;
import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.util.mxDomUtils;
import com.mxgraph.util.mxXmlUtils;
import com.mxgraph.view.mxGraph;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @author yonghuan
 */
public class DefaultWorkflowModeConverter implements WorkflowModeConverter {

    private final static Logger LOG = LoggerFactory.getLogger(DefaultWorkflowModeConverter.class);
    private List<ElementConverter> elementConverters = Collections.emptyList();
    private List<MxElementConverter> mxElementConverters = Collections.emptyList();

    public DefaultWorkflowModeConverter() {
    }

    public void setElementConverters(List<ElementConverter> elementConverters) {
        this.elementConverters = elementConverters;
    }

    public void setMxElementConverters(List<MxElementConverter> mxElementConverters) {
        this.mxElementConverters = mxElementConverters;
    }

    @Override
    public BpmnModel convertToBpmnModel(byte[] mxgraphXml, Charset charset) throws IOException {
        String xml = new String(mxgraphXml, charset);
        Document document = mxXmlUtils.parseXml(xml);
        mxCodec codec = new mxCodec(document);
        mxGraphModel graphModel = (mxGraphModel) codec.decode(document.getDocumentElement());
        BpmnModel bpmnModel = new BpmnModel();

        // 解析流程
        elementConverters.stream()
                .filter(elementConverter -> elementConverter.supports(graphModel))
                .findAny()
                .ifPresent(elementConverter -> elementConverter.convert(bpmnModel, graphModel, graphModel));
        // 解析流程元素
        graphModel.getCells().values()
                .stream()
                .map(mxCell.class::cast)
                .sorted(Comparator.comparingInt(cell -> cell.isEdge() ? 2 : 1))
                .forEach(cell -> elementConverters.stream()
                        .filter(elementConverter -> elementConverter.supports(cell))
                        .findAny()
                        .ifPresent(elementConverter -> elementConverter.convert(bpmnModel, graphModel, cell)));

        return bpmnModel;
    }

    @Override
    public byte[] convertToXML(BpmnModel bpmnModel, Charset charset) {
        Process process = bpmnModel.getProcesses().get(0);
        final mxGraph graph = new mxGraph();

        // 转换流程
        mxElementConverters.stream()
                .filter(mxElementConverter -> mxElementConverter.supports(process))
                .findAny()
                .ifPresent(mxElementConverter -> mxElementConverter.convert(bpmnModel, process, graph, process));
        // 转换流程元素
        process.getFlowElements()
                .stream()
                .sorted(Comparator.comparingInt(fe -> fe.getClass() != SequenceFlow.class ? 1 : 2))
                .forEach(fe -> mxElementConverters.stream()
                        .filter(mxElementConverter -> mxElementConverter.supports(fe))
                        .findAny()
                        .ifPresent(mxElementConverter -> mxElementConverter.convert(bpmnModel, process, graph, fe)));

        mxCodec codec = new mxCodec(mxDomUtils.createDocument());
        Element element = (Element) codec.encode(graph.getModel());
        String xml = mxXmlUtils.getXml(element);
        if (LOG.isInfoEnabled()) {
            LOG.info("mxgraph xml: {}", xml);
        }
        return xml.getBytes();
    }
}
