package com.pack.modeler.filter;

import com.pack.modeler.structures.DiscoveryMethod;
import org.deckfour.xes.classification.XEventClassifier;
import org.deckfour.xes.extension.XExtension;
import org.deckfour.xes.factory.XFactory;
import org.deckfour.xes.factory.XFactoryBufferedImpl;
import org.deckfour.xes.model.XAttribute;
import org.deckfour.xes.model.XEvent;
import org.deckfour.xes.model.XLog;
import org.deckfour.xes.model.XTrace;
import org.deckfour.xes.util.XsDateTimeConversion;

import java.util.*;

public class LogFilter {


    public List<XTrace> getFilteredLogs(String method, List<Map<String, ArtifactFilter>> artifactFilterMap, XLog log) {
        List<XTrace> logs = new ArrayList<>();

        switch (DiscoveryMethod.valueOf(method)){
            case FREQUENCY: {
                logs = filterLogsByFrequency(log); break;
            }
            case ARTIFACTS:{
                logs = filterLogsByArtifact(log, artifactFilterMap); break;
            }
            case LONG_TRACE: {
                logs = filterLogsByTraceLength(log, true); break;
            }
            case SHORT_TRACE: {
                logs = filterLogsByTraceLength(log, false); break;
            }
            case TIME_LONG: {
                logs = filterLogsByTraceTime(log, false); break;
            }
            case TIME_SHORT: {
                logs = filterLogsByTraceTime(log, true); break;
            }
        }

        return logs;
    }

    private List<XTrace> filterLogsByArtifact(XLog log, List<Map<String,ArtifactFilter>> artifactFilterMap) {
        Map<String,ArtifactFilter> filterMap = normalizeMap(artifactFilterMap);

        List<String> eventHeader = new ArrayList<>();
        log.getGlobalEventAttributes().forEach(x -> eventHeader.add(x.getKey()));

        Map<String, XTrace> traceMap = new HashMap<>();
        Map<String, Integer> strTraceMapIncludeCount = new HashMap<>();

        for(XTrace trace : log){
            String eventPath = "";
            int includedItemsCount = 0;
            for(XEvent event : trace){
                XAttribute attrib = event.getAttributes().get("concept:name");
                eventPath += "-" + attrib.toString();

                for (String head : eventHeader) {
                    XAttribute currAtr = event.getAttributes().get(head);
                    ArtifactFilter filterList = filterMap.get(head);
                    if(filterList.includeContains(currAtr.toString()))
                        includedItemsCount++;
                }
            }

            if(includedItemsCount > 1) {
                traceMap.put(eventPath, trace);
                strTraceMapIncludeCount.put(eventPath, includedItemsCount);
            }
        }
        Map<XTrace, Integer> traceIncludeCountMap = new HashMap<>();
        traceMap.forEach((k, v) -> traceIncludeCountMap.put(v, strTraceMapIncludeCount.get(k)));

        return sortTracesByAmount(traceIncludeCountMap, true);
    }

    private Map<String,ArtifactFilter> normalizeMap(List<Map<String,ArtifactFilter>> artifactFilterMap) {
        Map<String,ArtifactFilter> result = new HashMap<>();
        artifactFilterMap.forEach(l -> l.forEach((k,v) -> result.put(k,v)));

        return result;
    }

    private List<XTrace> filterLogsByTraceTime(XLog log, boolean isQuickest) {
        Map<String, XTrace> traceMap = new HashMap<>();
        Map<String, Long> strTraceTimeMap = new HashMap<>();

        for(XTrace trace : log){
            XsDateTimeConversion dateParser = new XsDateTimeConversion();
            Date start = dateParser.parseXsDateTime(trace.get(0).getAttributes().get("time:timestamp").toString());
            Date end = dateParser.parseXsDateTime(trace.get(trace.size()-1).getAttributes().get("time:timestamp").toString());

            String eventPath = "";
            for(XEvent event : trace){
                XAttribute attrib = event.getAttributes().get("concept:name");
                eventPath += "-" + attrib.toString();
            }

            traceMap.put(eventPath, trace);
            long diff = start.getTime() - end.getTime();
            strTraceTimeMap.put(eventPath, diff);
        }

        Map<XTrace, Long> traceSizeMap = new HashMap<>();
        traceMap.forEach((k, v) -> traceSizeMap.put(v, strTraceTimeMap.get(k)));

        return sortTracesByTime(traceSizeMap, isQuickest);
    }
    
    private List<XTrace> filterLogsByTraceLength(XLog log, boolean isLongest) {
        Map<String, XTrace> traceMap = new HashMap<>();
        Map<String, Integer> strTraceSizeMap = new HashMap<>();

        for(XTrace trace : log){
            String eventPath = "";
            int size = 0;
            for(XEvent event : trace){
                XAttribute attrib = event.getAttributes().get("concept:name");
                eventPath += "-" + attrib.toString();
                size++;
            }
            traceMap.put(eventPath, trace);
            strTraceSizeMap.put(eventPath, size);
        }

        Map<XTrace, Integer> traceSizeMap = new HashMap<>();
        traceMap.forEach((k, v) -> traceSizeMap.put(v, strTraceSizeMap.get(k)));

        return sortTracesByAmount(traceSizeMap, isLongest);
    }

    private List<XTrace> filterLogsByFrequency(XLog log) {
        Map<String, XTrace> traceMap = new HashMap<>();
        Map<String, Integer> strTraceMapCount = new HashMap<>();

        for(XTrace trace : log){
            String eventPath = "";
            for(XEvent event : trace){
                XAttribute attrib = event.getAttributes().get("concept:name");
                eventPath += "-" + attrib.toString();
            }
            if(!traceMap.containsKey(eventPath)) {
                traceMap.put(eventPath, trace);
                strTraceMapCount.put(eventPath, 1);
            }
            else{
                int count = strTraceMapCount.get(eventPath);
                strTraceMapCount.put(eventPath, count + 1);
            }
        }

        Map<XTrace, Integer> traceCountMap = new HashMap<>();
        traceMap.forEach((k, v) -> traceCountMap.put(v, strTraceMapCount.get(k)));

        return sortTracesByAmount(traceCountMap, true);
    }

    public List<XLog> generateLogs(XLog initialLog, List<XTrace> traces) {
        List<XLog> logList = new ArrayList<>();

        XFactory xFactory = new XFactoryBufferedImpl();
        for (XTrace trace : traces) {
            XLog tmpLog = xFactory.createLog();

            tmpLog.getExtensions().addAll(initialLog.getExtensions());
            tmpLog.getClassifiers().addAll(initialLog.getClassifiers());

            tmpLog.add(trace);

            logList.add(tmpLog);
        }

        return logList;
    }

    public XLog generateLog(Set<XExtension> extension, List<XEventClassifier> classifier, List<XTrace> traces) {
        XFactory xFactory = new XFactoryBufferedImpl();
        XLog log = xFactory.createLog();

        log.getExtensions().addAll(extension);
        log.getClassifiers().addAll(classifier);
        log.addAll(traces);

        return log;
    }

    private List<XTrace> sortTracesByAmount(Map<XTrace, Integer> traceMap, boolean isReverse) {
        List<XTrace> traces = new ArrayList<>();

        Object[] objArr = traceMap.entrySet().toArray();

        if(isReverse) {
            //Decreasing
            Arrays.sort(objArr, (Comparator) (o1, o2) ->
                    ((Map.Entry<XTrace, Integer>) o2).getValue()
                            .compareTo(((Map.Entry<XTrace, Integer>) o1).getValue())
            );
        }
        else{
            //Increasing
            Arrays.sort(objArr, (Comparator) (o1, o2) ->
                    ((Map.Entry<XTrace, Integer>) o1).getValue()
                            .compareTo(((Map.Entry<XTrace, Integer>) o2).getValue())
            );
        }

        for (Object e : objArr) {
            traces.add(((Map.Entry<XTrace, Integer>) e).getKey());
        }

        return traces;
    }

    private List<XTrace> sortTracesByTime(Map<XTrace, Long> traceMap, boolean isReverse) {
        List<XTrace> traces = new ArrayList<>();

        Object[] objArr = traceMap.entrySet().toArray();

        if(isReverse) {
            //Decreasing
            Arrays.sort(objArr, (Comparator) (o1, o2) ->
                    ((Map.Entry<XTrace, Long>) o2).getValue()
                            .compareTo(((Map.Entry<XTrace, Long>) o1).getValue())
            );
        }
        else{
            //Increasing
            Arrays.sort(objArr, (Comparator) (o1, o2) ->
                    ((Map.Entry<XTrace, Long>) o1).getValue()
                            .compareTo(((Map.Entry<XTrace, Long>) o2).getValue())
            );
        }

        for (Object e : objArr) {
            traces.add(((Map.Entry<XTrace, Long>) e).getKey());
        }

        return traces;
    }
}
