package com.pack.modeler.service;

import com.googlecode.jcsv.reader.CSVReader;
import com.googlecode.jcsv.reader.internal.CSVReaderBuilder;
import com.pack.modeler.manager.FileStorageManager;
import com.pack.modeler.structures.GenericParser;
import com.pack.modeler.structures.GenericTrace;
import com.pack.modeler.model.SessionData;
import com.pack.modeler.repository.SessionDataRepo;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.deckfour.xes.classification.XEventNameClassifier;
import org.deckfour.xes.extension.std.XConceptExtension;
import org.deckfour.xes.extension.std.XLifecycleExtension;
import org.deckfour.xes.extension.std.XOrganizationalExtension;
import org.deckfour.xes.extension.std.XTimeExtension;
import org.deckfour.xes.factory.XFactory;
import org.deckfour.xes.factory.XFactoryBufferedImpl;
import org.deckfour.xes.model.*;
import org.deckfour.xes.model.impl.XAttributeLiteralImpl;
import org.deckfour.xes.out.XesXmlSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ConverterService {

    @Autowired
    SessionDataRepo sessionDataRepo;

    public void convertCSVtoXES(String token, Map<String, String> map, String datefromat){
        SessionData sd = sessionDataRepo.findOneByToken(token);

        Reader logFile;
        try {
            logFile = new FileReader(sd.getFile());
            File fCsv = new File(sd.getFile());


            CSVParser parser = CSVFormat.RFC4180.withFirstRecordAsHeader().withDelimiter(sd.getCsvDelim().charAt(0)).parse(logFile);

            XLog log = convert(sd.getFile(), parser.getHeaderMap(), map, datefromat);

            if(log == null)
                return;

            String file = new File(sd.getFile()).getName().replace(".csv", ".xes");
            String pathStr = FileStorageManager.getInstance().getStoragePath() + file;

            XesXmlSerializer serializer = new XesXmlSerializer();
            serializer.serialize(log, new FileOutputStream(pathStr));

            sd.setFile(pathStr);

            sessionDataRepo.save(sd);

            fCsv.delete();
            logFile.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public XLog convert(String csv, Map<String, Integer> headerMap, Map<String, String> colDef, String dateformat) {
        XFactory xFactory = new XFactoryBufferedImpl();
        XLog xLog = xFactory.createLog();

        XTrace xTrace = null;
        XEvent xEvent = null;
        XConceptExtension concExt = XConceptExtension.instance();
        XLifecycleExtension lifeExtension = XLifecycleExtension.instance();
        XTimeExtension timeExtension = XTimeExtension.instance();
        XOrganizationalExtension orgExt = XOrganizationalExtension.instance();
        xLog.getExtensions().add(concExt);
        xLog.getExtensions().add(lifeExtension);
        xLog.getExtensions().add(timeExtension);
        xLog.getExtensions().add(orgExt);
        xLog.getClassifiers().add(new XEventNameClassifier());

        concExt.assignName(xLog, "Synthetic log");
        lifeExtension.assignModel(xLog, XLifecycleExtension.VALUE_MODEL_STANDARD);

        //Remember the last case Id seen
        String lastSeenTrace = "";//no case seen

        Map<String, Integer> colIndexes = colTypesToIndexes(colDef, headerMap);

        try {
            List<GenericTrace> traces = readGenericLog(csv, colIndexes, dateformat);

            for (GenericTrace trace : traces) {

                // When I find a new new UserId (representing the Case Id) I create a new trace
                if(!lastSeenTrace.equals(trace.getTraceColumn())){
                    xTrace = xFactory.createTrace();
                    //concExt.assign(xTrace, "Trace no. "+(++traceCounter));
                    concExt.assignName(xTrace, trace.getTraceColumn()+"");
                    lastSeenTrace = trace.getTraceColumn();
                    xLog.add(xTrace);
                }

                //Create the event within the current trace
                xEvent = xFactory.createEvent();
                concExt.assignName(xEvent, trace.getConceptNameColumn()+"");
                if(trace.getLifecycleColumn() != null)
                    lifeExtension.assignTransition(xEvent, trace.getLifecycleColumn());
                else
                    lifeExtension.assignStandardTransition(xEvent, XLifecycleExtension.StandardModel.COMPLETE);
                timeExtension.assignTimestamp(xEvent, Timestamp.valueOf(trace.getTimestampColumn()));
                orgExt.assignResource(xEvent, trace.getResourceColumn());

                Map<String, String> otherMap = trace.getOther();
                for (String key : otherMap.keySet()) {
                    XAttribute attr = new XAttributeLiteralImpl(key, otherMap.get(key));
                    xEvent.getAttributes().put(key, attr);
                }

                xTrace.add(xEvent);
            }//end loop

            return xLog;

        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    private Map<String, Integer> colTypesToIndexes(Map<String, String> colDef, Map<String, Integer> headerMap) {
        Map<String, String> newmap = new HashMap<>();
        for (Map.Entry<String, String> entry : colDef.entrySet())
        {
            String newKey = entry.getValue(); // column type
            String newValue = entry.getKey(); // column name

            if(newKey.equals("other"))
                newKey = newValue;

            newmap.put(newKey, newValue);
        }

        Map<String, Integer> numColMap = new HashMap<>();

        for (Map.Entry<String, String> entry : newmap.entrySet()) {
            String value = entry.getValue(); // column name
            String key = entry.getKey(); // column type

            numColMap.put(key, headerMap.get(value));
        }

        return numColMap;
    }

    private static List<GenericTrace> readGenericLog(String filename, Map<String, Integer> colIndexes, String dateformat) throws IOException {
        Reader reader = new FileReader(filename);
        GenericParser genericTraceParser = new GenericParser(colIndexes, dateformat);
        CSVReader<GenericTrace> csvTraceReader = new CSVReaderBuilder<GenericTrace>(reader).entryParser(genericTraceParser).build();

        csvTraceReader.readHeader(); // we don't need header
        List<GenericTrace> traces = new ArrayList<>();
        for (GenericTrace trace = csvTraceReader.readNext(); trace != null; trace = csvTraceReader.readNext())
            traces.add(trace);
        return traces;
    }

}
