package com.pack.modeler.service;

import com.pack.modeler.model.SessionData;
import com.pack.modeler.repository.SessionDataRepo;
import com.pack.modeler.structures.LogRow;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FilenameUtils;
import org.deckfour.xes.in.XesXmlParser;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
public class ParsingService {

    @Autowired
    SessionDataRepo sessionDataRepo;


    public List<LogRow> parseRows(String token, String sep, int amount) {
        SessionData sd = sessionDataRepo.findOneByToken(token);

        if(sd == null) return null;

        String path = sd.getFile();

        Reader logFile;
        try {
            logFile = new FileReader(path);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        String extension = FilenameUtils.getExtension(path).toLowerCase();

        switch (extension){
            case "csv":{
                if(sd.getCsvDelim().equals(""))
                    sd.setCsvDelim(sep);
                else
                    sep = sd.getCsvDelim();

                sessionDataRepo.save(sd);
                return parseCSVFile(logFile, sep.charAt(0), amount);
            }
            case "xes":{
                return parseXESFile(path, amount);
            }
        }
        return null;
    }

    private List<LogRow> parseXESFile(String logFile, int amount) {
        List<LogRow> logRows = new ArrayList<>();

        try {
            XesXmlParser xParser = new XesXmlParser();

            List<XLog> log = xParser.parse(new File(logFile)); // Note: supports only 1 log.
            if(log.size() == 0) return null;

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

            List<String> traceHeader = new ArrayList<>();
            log.get(0).getGlobalTraceAttributes().forEach(x -> traceHeader.add(x.getKey()));

            List<String> fullHeader = new ArrayList<>();
            fullHeader.add("concept:name");
            fullHeader.addAll(eventHeader);

            List<String> types = getColumnTypes(traceHeader, eventHeader);

            int traceCounter = 0;
            for(XTrace trace : log.get(0)){
                traceCounter++;
                List<String> rowValsTrace = new ArrayList<>();
                for(String head : traceHeader) {
                    if(head.equals("concept:name")) {
                        rowValsTrace.add(trace.getAttributes().get(head).toString());
                    }
                }
                if(rowValsTrace.size() < 1)
                    rowValsTrace.add(String.valueOf(traceCounter));

                for(XEvent event : trace){
                    List<String> rowValsEvent = new ArrayList<>();

                    for(String head : eventHeader) {
                        XAttribute attrib = event.getAttributes().get(head);
                        if(attrib != null) {
                            rowValsEvent.add(attrib.toString());
                        }
                    }

                    List<String> fullRowVals = new ArrayList<>();
                    fullRowVals.addAll(rowValsTrace);
                    fullRowVals.addAll(rowValsEvent);

                    logRows.add(new LogRow(fullHeader, types, fullRowVals));

                    if(logRows.size() >= amount) break;
                }
                if(logRows.size() >= amount) break;
            }

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

        return logRows;
    }

    private List<String> getColumnTypes(List<String> traceHeader, List<String> eventHeader) {
        List<String> types = new ArrayList<>();

        for(String feature : traceHeader){
            switch (feature){
                case "concept:name" :
                    types.add("trace"); break;
            }
        }


        for(String feature : eventHeader){
            switch (feature){
                case "concept:name" :
                    if(!types.contains("activity"))
                        types.add("activity");
                    break;

                case "org:resource" :
                    if(!types.contains("resource"))
                        types.add("resource");
                    break;

                case "time:timestamp" :
                    if(!types.contains("timestamp"))
                        types.add("timestamp");
                    break;

                case "lifecycle:transition" :
                    if(!types.contains("lifecycle"))
                        types.add("lifecycle");
                    break;

                default:
                    types.add("other"); break;
            }
        }
        return types;
    }

    private List<LogRow> parseCSVFile(Reader logFile, Character sep, int amount){
        List<LogRow> logRows = new ArrayList<>();
        try {
            CSVParser parser = CSVFormat.RFC4180.withFirstRecordAsHeader().withDelimiter(sep).parse(logFile);

            List<CSVRecord> records = parser.getRecords();
            Map<String, Integer> header = parser.getHeaderMap();

            int amountCounter = 0;
            for(CSVRecord row : records){
                LogRow logRow = new LogRow(new ArrayList<>(header.keySet()), null, row);
                logRows.add(logRow);

                if(amountCounter == amount)
                    break;
                amountCounter++;
            }
            logFile.close();
            return logRows;

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

    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;
        }
    }

    public Map<String,List<String>> getActivities(String token) {
        Map<String, List<String>> artifacts = new HashMap<>();

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

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

            for (XTrace trace : log) {
                for (XEvent event : trace) {
                    event.getAttributes().forEach((k,v) -> eventHeader.add(k));
                    for (String head : eventHeader) {
                        XAttribute attrib = event.getAttributes().get(head);
                        if (attrib != null) {
                            if (artifacts.get(head) != null) {
                                List<String> headArtifacts = artifacts.get(head);

                                if (!headArtifacts.contains(attrib.toString())) {
                                    headArtifacts.add(attrib.toString());
                                    artifacts.put(head, headArtifacts);
                                }
                            } else {
                                List<String> headArtifacts = new ArrayList<>();
                                headArtifacts.add(attrib.toString());
                                artifacts.put(head, headArtifacts);
                            }
                        }
                    }
                }
            }
        }

        return artifacts;
    }
}
