package com.pack.modeler.converter;

import org.camunda.bpm.model.bpmn.Bpmn;
import org.camunda.bpm.model.bpmn.builder.AbstractFlowNodeBuilder;
import org.camunda.bpm.model.xml.impl.util.IoUtil;
import org.processmining.models.graphbased.NodeID;
import org.processmining.models.graphbased.directed.bpmn.BPMNDiagram;
import org.processmining.models.graphbased.directed.bpmn.BPMNNode;
import org.processmining.models.graphbased.directed.bpmn.elements.Activity;
import org.processmining.models.graphbased.directed.bpmn.elements.Event;
import org.processmining.models.graphbased.directed.bpmn.elements.Flow;
import org.processmining.models.graphbased.directed.bpmn.elements.Gateway;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class XMLConverter {
    private String xml;
    private BPMNDiagram diagram;


    private List<NodeID> gatewayList = new ArrayList<>(); // used to move model pointer to last gateway
    private List<NodeID> nodeIDList = new ArrayList<>();

    public XMLConverter (BPMNDiagram diag){
        diagram = diag;
        xml = convertToXML();
    }

    public String getXml() {
        return xml;
    }

    private String convertToXML() {
        NodeID sourceID = getEventID(diagram.getEvents(), Event.EventType.START);
        Event start = getNodeByID(diagram.getEvents(), sourceID);

        String startLabel = "";
        if (start != null)
            startLabel += start.getLabel();

        AbstractFlowNodeBuilder model = Bpmn.createProcess()
                .name("")
                .startEvent()
                    .name(startLabel);

        model = addNode(model, sourceID);

        return IoUtil.convertXmlDocumentToString(model.done().getDocument());
    }

    private AbstractFlowNodeBuilder addNode(AbstractFlowNodeBuilder model, NodeID sourceID) {
        nodeIDList.add(sourceID);
        List<BPMNNode> nodes = getNextNodes(sourceID, diagram.getFlows());

        if(nodes.size() == 1){
            BPMNNode target = nodes.get(0);
            model = addTree(model, target);
        }
        else{
            if(nodes.size() != 0) {
                gatewayList.add(sourceID);
                for (BPMNNode target : nodes) {
                    model = addTree(model, target);
                    model = model.moveToNode(gatewayList.get(gatewayList.size()-1).toString());
                }
                gatewayList.remove(sourceID);
            }
        }

        return model;
    }

    private <T extends BPMNNode> T getNodeByID(Collection<T> collection, NodeID id) {
        for (T node: collection) {
            if(node.getId().compareTo(id) == 0) {
                return node;
            }
        }
        return null;
    }

    private AbstractFlowNodeBuilder addTree(AbstractFlowNodeBuilder model, BPMNNode target){
        if(nodeIDList.contains(target.getId())){                        // if node already exists, connect to it
            model = model.connectTo(target.getId().toString());
            //TODO Fix issue with buried arrows
        }
        else {                                                  // otherwise create new
            int type = getNodeType(target);
            switch (type) {
                case 0: { // if node is activity
                    model = model.userTask()
                            .name(target.getLabel())
                            .id(target.getId().toString());

                    model = addNode(model, target.getId());
                    break;
                }

                case 1: { // gateway
                    Gateway gateway = (Gateway) target;

                    switch (gateway.getGatewayType()) {
                        case PARALLEL: {
                            model = model.parallelGateway()
                                    .id(target.getId().toString());

                            model = addNode(model, gateway.getId());
                            break;
                        }
                        case DATABASED: {
                            model = model.exclusiveGateway()
                                    .id(target.getId().toString());

                            model = addNode(model, gateway.getId());
                            break;
                        }
                        case INCLUSIVE: {
                            model = model.inclusiveGateway()
                                    .id(target.getId().toString());

                            model = addNode(model, gateway.getId());
                            break;
                        }
                    }
                    break;
                }

                case 2: {
                    Event event = (Event) target;
                    switch (event.getEventType()) {
                        case END: {
                            model = model.endEvent()
                                    .id(target.getId().toString());

                            nodeIDList.add(target.getId());
                            break;
                        }
                    }
                }
            }
        }
        return model;
    }

    /*
        0 - Activity
        1 - Gateway
        2 - Event
     */
    private int getNodeType(BPMNNode target) {
        if(target instanceof Activity)
            return 0;

        if(target instanceof Gateway)
            return 1;

        if(target instanceof Event)
            return 2;

        return -1;
    }

    private List<BPMNNode> getNextNodes(NodeID sourceID, Collection<Flow> flows) {
        List<BPMNNode> resultList = new ArrayList<>();

        for (Flow flow: flows) {
            if(flow.getSource().getId().compareTo(sourceID) == 0){
                resultList.add(flow.getTarget());
            }
        }

        return resultList;
    }

    private NodeID getEventID(Collection<Event> events, Event.EventType type) {
        for (Event event: events) {
            if(event.getEventType() == type){
                return event.getId();
            }
        }
        return null;
    }
}
