package com.pack.modeler.service;

import com.pack.modeler.converter.XMLConverter;
import com.pack.modeler.filter.ArtifactFilter;
import com.pack.modeler.filter.LogFilter;
import com.pack.modeler.manager.FileStorageManager;
import com.pack.modeler.miners.MinerDef;
import com.pack.modeler.model.ActivityData;
import com.pack.modeler.model.BPMNData;
import com.pack.modeler.model.DiagramData;
import com.pack.modeler.model.SessionData;
import com.pack.modeler.repository.ActivityDataRepo;
import com.pack.modeler.repository.BPMNDataRepo;
import com.pack.modeler.repository.DiagramDataRepo;
import com.pack.modeler.repository.SessionDataRepo;
import com.pack.modeler.structures.BPMNMiner;
import com.pack.modeler.structures.MethodParameters;
import com.pack.modeler.structures.StatisticalMeasures;
import javafx.util.Pair;
import org.deckfour.xes.in.XesXmlParser;
import org.deckfour.xes.model.XLog;
import org.deckfour.xes.model.XTrace;
import org.processmining.models.graphbased.directed.bpmn.BPMNDiagram;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

@Service
public class DiscoveryService {

    @Autowired
    SessionDataRepo sessionDataRepo;

    @Autowired
    BPMNDataRepo bpmnDataRepo;

    @Autowired
    DiagramDataRepo diagramDataRepo;

    @Autowired
    ActivityDataRepo activityDataRepo;

    @Async("asyncExecutor")
    public CompletableFuture<Boolean> discover(String token,
                                              String filteringMethod,
                                              String algorithm,
                                              StatisticalMeasures statisticalMeasures,
                                              MethodParameters methodParameters,
                                              List<Map<String, ArtifactFilter>> artifactFilterMap
    ){
        bpmnDataRepo.findAllByToken(token).forEach(x -> bpmnDataRepo.delete(x));
        diagramDataRepo.findAllByToken(token).forEach(x -> diagramDataRepo.delete(x));

        SessionData sd = sessionDataRepo.findOneByToken(token);
        XLog baseLog = getXLog(sd.getFile()); // Get file

        MinerDef miner = BPMNMiner.valueOf(algorithm).getMiner(); // Create miner

        ModelBuilder mb = new ModelBuilder(token, statisticalMeasures, miner, methodParameters, filteringMethod, artifactFilterMap);

        if(noMeasures(statisticalMeasures)){
            BPMNDiagram diag = mb.processWholeLog(baseLog);
            StatisticalMeasures modelMeasures = new StatisticalMeasures(diag, baseLog.size());
            Map<String, Integer> actMap = mb.calculateActivities(baseLog);

            addBPMNtoDB(token, diag, modelMeasures, actMap, 0);
        }
        else{
            LogFilter lf = new LogFilter();
            List<XTrace> traces = lf.getFilteredLogs(filteringMethod, artifactFilterMap, baseLog);

            int modelCounter = 0;
            while (traces.size() > 0) {
                Pair<Integer, BPMNDiagram> pair = mb.processLog(traces, baseLog.getExtensions(), baseLog.getClassifiers());

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

                StatisticalMeasures modelMeasures = new StatisticalMeasures(pair.getValue(), log.size());

                Map<String, Integer> actMap = mb.calculateActivities(log);

                addBPMNtoDB(token, pair.getValue(), modelMeasures, actMap, modelCounter);
                modelCounter++;

                if (traces.size() > pair.getKey())
                    traces = traces.subList(pair.getKey(), traces.size());
                else
                    break;
            }

        }

        return CompletableFuture.completedFuture(true);

    }

    private XLog getXLog(String file) {
        File logFile;
        try {
            logFile = new File(file);
            XesXmlParser xParser = new XesXmlParser();
            List<XLog> log = xParser.parse(logFile);

            return  log.get(0);

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private boolean noMeasures(StatisticalMeasures measures) {
        return measures.getCnc() == null
                && measures.getCohesion() == null
                && measures.getCoupling() == null
                && measures.getDensity() == null
                && measures.getNodes() == null;
    }


    private void addBPMNtoDB(String token, BPMNDiagram diagram, StatisticalMeasures modelMeasures, Map<String, Integer> activitiesStats, int modelCounter){
        XMLConverter xmlConverter = new XMLConverter(diagram);
        String xml = xmlConverter.getXml();

        String path = FileStorageManager.getInstance().getModelStoragePath();

        String fileName = StringUtils.cleanPath("/" + token + "number" + modelCounter + ".bpmn");

        try {
            Files.deleteIfExists(Paths.get(path + fileName));
            Files.createFile(Paths.get(path + fileName));
            Files.write(Paths.get(path + fileName), xml.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }

        BPMNData bpmnData = BPMNData.of(token, path + fileName);

        bpmnDataRepo.save(bpmnData);

        String fileUri = FileStorageManager.getInstance().getLinkPath() + bpmnData.getId().toString();

        diagramDataRepo.save(
                DiagramData.of(
                        token,
                        fileUri,
                        modelMeasures.getTraces(),
                        modelMeasures.getNodes(),
                        modelMeasures.getDensity(),
                        modelMeasures.getCohesion(),
                        modelMeasures.getCoupling(),
                        modelMeasures.getCnc())
        );

        if(activitiesStats != null) {
            for (Map.Entry<String, Integer> entry : activitiesStats.entrySet()) {
                activityDataRepo.save(ActivityData.of(
                        token,
                        fileUri,
                        entry.getKey(),
                        entry.getValue(),
                        "*|.&*|.")
                );
            }
        }
    }

}
