package com.pack.modeler.service;

import com.pack.modeler.filter.ArtifactFilter;
import com.pack.modeler.filter.LogFilter;
import com.pack.modeler.miners.MinerDef;
import com.pack.modeler.structures.MethodParameters;
import com.pack.modeler.structures.StatisticalMeasures;
import javafx.util.Pair;
import org.deckfour.xes.classification.XEventClassifier;
import org.deckfour.xes.extension.XExtension;
import org.deckfour.xes.model.XEvent;
import org.deckfour.xes.model.XLog;
import org.deckfour.xes.model.XTrace;
import org.processmining.models.graphbased.directed.bpmn.BPMNDiagram;

import java.util.*;

public class ModelBuilder {

    private String token;
    private MinerDef miner;
    private MethodParameters methodParameters;
    private String filteringMethod;
    private List<Map<String,ArtifactFilter>> artifactFilterMap;
    private StatisticalMeasures measures;


    public ModelBuilder(String token, StatisticalMeasures measures, MinerDef miner, MethodParameters methodParameters, String filteringMethod, List<Map<String,ArtifactFilter>> artifactFilterMap) {
        this.token = token;
        this.miner = miner;
        this.methodParameters = methodParameters;
        this.filteringMethod = filteringMethod;
        this.artifactFilterMap = artifactFilterMap;
        this.measures = measures;
    }


    /*
        1 - measures of bpmn are bigger than given
        -1 - measures smaller
        0 - equals
     */
    public int cmpMeasures(StatisticalMeasures modelMeasures, StatisticalMeasures measures) {
        List<Integer> cmpResults = new ArrayList<>();

        if(measures.getNodes() != null)
            cmpResults.add(measures.cmpNodes(modelMeasures.getNodes()));

        if(measures.getDensity() != null)
            cmpResults.add(measures.cmpDensity(modelMeasures.getDensity()));

        if(measures.getCohesion() != null)
            cmpResults.add(measures.cmpCohesion(modelMeasures.getCohesion()));

        if(measures.getCoupling() != null)
            cmpResults.add(measures.cmpCoupling(modelMeasures.getCoupling()));

        if(measures.getCnc() != null)
            cmpResults.add(measures.cmpCnc(modelMeasures.getCnc()));

        if(cmpResults.contains(1))
            return 1;

        if(cmpResults.contains(0))
            return 0;

        return -1;
    }


    public BPMNDiagram processWholeLog(XLog baseLog) {
        LogFilter lf = new LogFilter();
        List<XTrace> traces = lf.getFilteredLogs(filteringMethod, artifactFilterMap, baseLog);
        int totalTraces = traces.size();

        XLog log = lf.generateLog(baseLog.getExtensions(), baseLog.getClassifiers(), traces.subList(0, totalTraces));

        XEventClassifier classifier = log.getClassifiers().get(0);
        BPMNDiagram bpmn = miner.generateBPMN(
                log,
                classifier,
                methodParameters.getPercentileFrequencyThreshold(),
                methodParameters.getParallelismsThreshold(),
                methodParameters.getFilterType(),
                methodParameters.isParallelismsFirst(),
                methodParameters.isReplaceIORs(),
                methodParameters.isRemoveLoopActivities(),
                methodParameters.getStructuringTime()
        );

        return bpmn;
    }

    public Pair<Integer, BPMNDiagram> processLog(List<XTrace> traces, Set<XExtension> extensions, List<XEventClassifier> classifiers){
        int biggestOkStats = 1, // The biggest number of traces with ok stats
            smallestNotOkStats = traces.size(),         // The smallest number of traces with not ok stats
            modelSearchCounter = 0;

        LogFilter lf = new LogFilter();
        for(int nTraces = (int)Math.ceil(traces.size() / 2.0); true;) {
            // take first n traces
            XLog log = lf.generateLog(extensions, classifiers, traces.subList(0, nTraces));

            XEventClassifier classifier = log.getClassifiers().get(0);
            BPMNDiagram bpmn = miner.generateBPMN(
                    log,
                    classifier,
                    methodParameters.getPercentileFrequencyThreshold(),
                    methodParameters.getParallelismsThreshold(),
                    methodParameters.getFilterType(),
                    methodParameters.isParallelismsFirst(),
                    methodParameters.isReplaceIORs(),
                    methodParameters.isRemoveLoopActivities(),
                    methodParameters.getStructuringTime()
            );

            // Compare created bpmn with measures
            StatisticalMeasures modelMeasures = new StatisticalMeasures(bpmn, log.size());
            int cmpMeasures = cmpMeasures(modelMeasures, measures);

            if (cmpMeasures == 0 || nTraces == 1 || nTraces == traces.size() || biggestOkStats == (smallestNotOkStats -1)){
                if(cmpMeasures < 1 || nTraces == 1) {
                    return new Pair<>(nTraces, bpmn);
                }
                else{
                    XLog logPrev = lf.generateLog(extensions, classifiers, traces.subList(0, biggestOkStats));
                    XEventClassifier classifierPrev = log.getClassifiers().get(0);
                    BPMNDiagram diagPrev = miner.generateBPMN(
                            logPrev,
                            classifierPrev,
                            methodParameters.getPercentileFrequencyThreshold(),
                            methodParameters.getParallelismsThreshold(),
                            methodParameters.getFilterType(),
                            methodParameters.isParallelismsFirst(),
                            methodParameters.isReplaceIORs(),
                            methodParameters.isRemoveLoopActivities(),
                            methodParameters.getStructuringTime()
                    );

                    return new Pair<>(nTraces, diagPrev);
                }
            }

            if(cmpMeasures > 0) { // measures bigger than given
                smallestNotOkStats = nTraces < smallestNotOkStats ? nTraces : smallestNotOkStats;
            }

            if(cmpMeasures < 0) {
                biggestOkStats = nTraces > biggestOkStats ? nTraces : biggestOkStats;
            }

            nTraces = (int) Math.ceil((smallestNotOkStats + biggestOkStats) / 2);

            if(modelSearchCounter > traces.size() / 2 && cmpMeasures < 1)
                return new Pair<>(nTraces, bpmn);
        }
    }

    //Calculates activities and their values
    public Map<String,Integer> calculateActivities(XLog log) {
        Map<String, Integer> map = new HashMap<>();

        for (XTrace trace : log) {
            for (XEvent event : trace){
                String activityKey = event.getAttributes().get("concept:name").toString();

                event.getAttributes().forEach((k,v) -> {
                    String fullKey = activityKey + "*|.&*|." + k + "*|.&*|." + v;
                    map.merge(fullKey, 1, (a, b) -> a + b);
                });
            }
        }

        return map;
    }


}
