package com.pack.modeler.structures;

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.*;

public class StatisticalMeasures {
    private int traces;
    private Integer nodes;
    private Double density;
    private Double cohesion;
    private Double coupling;
    private Double cnc;

    public StatisticalMeasures(BPMNDiagram bpmn, int _traces){
        this.traces = _traces;
        this.nodes = this.calculateNodes(bpmn);
        this.density = this.calculateDensity(bpmn);
        this.cohesion = this.calculateCohesion(bpmn);
        this.coupling = this.calculateCoupling(bpmn);
        this.cnc = this.calculateCnc(bpmn);
    }

    public int getTraces() {
        return traces;
    }
    public Integer getNodes() {
        return nodes;
    }

    public Double getDensity() {
        return density;
    }

    public Double getCohesion() {
        return cohesion;
    }

    public Double getCoupling() {
        return coupling;
    }

    public Double getCnc() {
        return cnc;
    }


    private Integer calculateNodes(BPMNDiagram bpmn){
        return bpmn.getActivities().size() + bpmn.getGateways().size() + bpmn.getEvents().size();
    }

    public Integer cmpNodes(int nodes){
        if(nodes > getNodes())
            return 1;

        if(nodes < getNodes())
            return -1;

        if(nodes == getNodes())
            return 0;

        return null;
    }


    private Double calculateDensity(BPMNDiagram bpmn){
        double actualEdges = bpmn.getFlows().size();
        double possibleEdges = (calculateNodes(bpmn) * (calculateNodes(bpmn) - 1)) / 2;
        return (double) (actualEdges / possibleEdges);
    }

    public Integer cmpDensity(double density){
        if(density > getDensity())
            return 1;

        if(density < getDensity())
            return -1;

        if(density == getDensity())
            return 0;

        return null;
    }


    private Double calculateCohesion(BPMNDiagram bpmn){
        double infoCohesion = 0;
        for (Activity activity : bpmn.getActivities()) {
            boolean isSource = false,
                    isTarget = false;
            for (Flow flow : bpmn.getFlows()) {
                if(flow.getSource() instanceof Event
                || flow.getTarget() instanceof Event)
                    continue;

                if(flow.getSource().equals(activity))
                    isSource = true;
                if(flow.getTarget().equals(activity))
                    isTarget = true;
                if(isSource && isTarget) {
                    infoCohesion++;
                    break;
                }
            }
        }

        infoCohesion /= bpmn.getActivities().size();

        int overlayCount = 0;

        Map<BPMNNode, List<BPMNNode>> operations = new HashMap<>();

        for (Activity activity: bpmn.getActivities()) {
            List<BPMNNode> subOperations = new ArrayList<>();
            for (Flow flow : bpmn.getFlows()) {
                if(flow.getTarget().equals(activity)){
                    if(flow.getSource() instanceof Activity){
                        subOperations.add(flow.getSource());
                    }
                    else{
                        if(flow.getSource() instanceof Gateway){
                            try {
                                subOperations.addAll(getPrevActivities(flow.getTarget().getId(), bpmn.getFlows()));
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
            if(subOperations.size() > 0)
                operations.put(activity, subOperations);
        }

        for (BPMNNode activity : operations.keySet()) {
            for (List<BPMNNode> subOperation : operations.values()) {
                if(subOperation.contains(activity))
                    overlayCount++;
            }
        }

        double relCohesion = overlayCount / (double)(operations.size() * (operations.size() - 1));

        return relCohesion * infoCohesion;
    }

    public Integer cmpCohesion(double cohesion){
        if(cohesion > getCohesion())
            return 1;

        if(cohesion < getCohesion())
            return -1;

        if(cohesion == getCohesion())
            return 0;

        return null;
    }

    // count nodes that connected 2-way
    private Double calculateCoupling(BPMNDiagram bpmn){
        double coupling = 0;
        for (Flow flow:bpmn.getFlows()) {
            BPMNNode source = flow.getSource();
            BPMNNode target = flow.getTarget();

            for (Flow flow2:bpmn.getFlows())
                if(flow2.getSource().equals(target) && flow2.getTarget().equals(source))
                    coupling += 1;
        }

        double possiblePairs = (double)((calculateNodes(bpmn) * (calculateNodes(bpmn) - 1)) / 2);
        return (coupling / 2) / possiblePairs;
    }

    public Integer cmpCoupling(double coupling){
        if(coupling > getCoupling())
            return 1;

        if(coupling < getCoupling())
            return -1;

        if(coupling == getCoupling())
            return 0;

        return null;
    }


    private Double calculateCnc(BPMNDiagram bpmn){
        return (bpmn.getFlows().size() / (double)calculateNodes(bpmn));
    }

    public Integer cmpCnc(double cnc){
        if(cnc > getCnc())
            return 1;

        if(cnc < getCnc())
            return -1;

        if(cnc == getCnc())
            return 0;

        return null;
    }

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

        for (Flow flow: flows) {
            if(flow.getTarget().getId().compareTo(sourceID) == 0){
                if(flow.getSource() instanceof Activity)
                    resultList.add(flow.getSource());
                if(flow.getSource() instanceof Gateway)
                    resultList.addAll(getPrevActivities(flow.getSource().getId(), flows));
            }
        }

        return resultList;
    }

}
