package cz.muni.disa.adamiss.se;

import com.sanityinc.jargs.CmdLineParser;
import com.sanityinc.jargs.CmdLineParser.Option;
import cz.muni.disa.adamiss.core.objects.TransactionFile;
import cz.muni.disa.adamiss.se.common.Algo;
import cz.muni.disa.adamiss.se.common.Dataset;
import cz.muni.disa.adamiss.se.generators.SubsetGenerator;
import cz.muni.disa.adamiss.se.loader.AllDatasetsLoaderImpl;
import cz.muni.disa.adamiss.se.loader.DatasetLoader;
import cz.muni.disa.adamiss.se.loader.SingleDatasetLoaderImpl;
import cz.muni.disa.adamiss.se.loader.SubsetDatasetLoaderImp;
import cz.muni.disa.adamiss.se.stats.collectors.DatabaseStatCollector;
import cz.muni.disa.adamiss.se.stats.objects.DBReport;
import cz.muni.disa.adamiss.se.stats.objects.ExperimentReport;
import cz.muni.disa.adamiss.se.stats.runners.ExperimentRunner;
import cz.muni.disa.adamiss.se.structures.DatasetContainer;
import javafx.util.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

import static cz.muni.disa.adamiss.se.loader.DataLoader.loadDataset;

public class Main {
    public static Logger logger = LogManager.getLogger(Main.class);

    private static void printUsage() {
        System.err.println(
                """
                        Usage: [-r,--runType {
                                        \"all\",
                                        \"single\",
                                        \"subset\"}] 
                               [-d,--datasets{
                                        \"BIBLE\",
                                        \"COVID\",
                                        \"GENERATED1\",
                                        \"GENERATED2\",
                                        \"GENERATED3\",
                                        \"KDDCUP\",
                                        \"LEVIATHAN\",
                                        \"SIGN\"}]
                               [-t,--thresholds] 
                               [-a,--algos{
                                        \"GSP\",
                                        \"PREFIX_SPAN\",
                                        \"SPAM\"
                               }]""");
    }

    public static void main(String[] args) {


        CmdLineParser parser = new CmdLineParser();
        Option<String> runTypeOption = parser.addStringOption('r',"runType");
        Option<String> datasetNameOption = parser.addStringOption('n', "datasetName");
        Option<Integer> amountOption = parser.addIntegerOption('a', "amount");
        Option<Long> seedOption = parser.addLongOption("seed");
        Option<Boolean> sizeOption = parser.addBooleanOption('s',"size");
        Option<Boolean> uniqueOption = parser.addBooleanOption('u',"unique");
        Option<Boolean> lengthOption = parser.addBooleanOption('l',"length");
        Option<Boolean> combinedOption = parser.addBooleanOption('c',"combined");


        try {
            parser.parse(args);
        } catch (CmdLineParser.OptionException e) {
            e.printStackTrace();
        }


        logger.debug("Loading datasets.");
        DatasetLoader loader = null;
        String runType = parser.getOptionValue(runTypeOption, "all");
        switch (runType) {
            case "all" -> {
                loader = new AllDatasetsLoaderImpl();
            }
            case "single" -> {
                var datasetName = parser.getOptionValue(datasetNameOption);
                Dataset dataset = Dataset.valueOf(datasetName);
                loader = new SingleDatasetLoaderImpl(dataset);
            }
            case "subset" -> {
                var datasetNames = parser.getOptionValue(datasetNameOption);
                List<Dataset> datasets = getDatasets(datasetNames);
                int amount = parser.getOptionValue(amountOption);
                var size = parser.getOptionValue(sizeOption, false);
                var unique = parser.getOptionValue(uniqueOption, false);
                var length = parser.getOptionValue(lengthOption, false);
                var combined = parser.getOptionValue(combinedOption, false);
                Long seed = parser.getOptionValue(seedOption);
                Random random = seed == null? new Random(): new Random(seed);
                loader = new SubsetDatasetLoaderImp(datasets, amount, size, unique, length, combined, random);
            }
            default -> {
                System.err.println("Unrecognised option for runType: " + runType);
                printUsage();
                System.exit(2);
            }
        }

        List<DBReport> reports = new ArrayList<>();
        List<ExperimentReport> eReports = new ArrayList<>();
        while (loader.hasNext()){
            var data = loader.next();

            DBReport report = getDatabaseStats(data.getKey(), data.getValue());
            reports.add(report);
            var eResult = ExperimentRunner.run(new DatasetContainer(data.getKey(), data.getValue(), report));
            storeEresult(eResult);
            eReports.addAll(eResult);
        }
        logger.debug("Saving dataset stats.");
        String dbReportCSV = DBReport.toCSVWithHeader(reports);
        saveAsCSV(dbReportCSV, "DatasetStats");

        logger.debug("Saving experiments data.");
        ExperimentReport.generateGNUPlotFiles(eReports);


    }

    public static List<Dataset> getDatasets() {
        return Arrays.asList(Dataset.values());
    }

    public static List<Dataset> getDatasets(String names) {
        List<Dataset> result = new ArrayList<>();
        for (Dataset dataset : Dataset.values()) {
            if (Arrays.asList(names.split(",")).contains(dataset.name())) {
                result.add(dataset);
            }
        }
        return result;
    }

    public static DBReport getDatabaseStats(String dataset, TransactionFile file) {
        DatabaseStatCollector collector = new DatabaseStatCollector();
        return collector.collect(dataset, file);
    }


    public static void saveAsCSV(String data, String name) {
        try (FileWriter report = new FileWriter(name + ".csv")){
            report.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void storeEresult(List<ExperimentReport> eResult){
        if(eResult.isEmpty()) {
            logger.error("Empty result");
            return;
        }
        try(PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("result_log.csv", true)))){
            for (var er : eResult){
                writer.println(er.toCSV());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        StringBuilder csvLine = new StringBuilder();
        Algo bestAlgo = eResult.stream().map((x)->new Pair<Algo,Double>(x.algorithm(),x.time()))
                .sorted(Comparator.comparing(Pair::getValue))
                .map(Pair::getKey)
                .findFirst().get();
        final String algoValue;
        switch(bestAlgo){
            case PREFIX_SPAN -> algoValue = "1";
            case SPAM -> algoValue = "2";
            default -> algoValue = "3"; //GSP
        }
        DBReport dbReport = eResult.stream().map(ExperimentReport::dbStats).distinct().findFirst().get();
        String dbReportCSV = dbReport.toCSV();
        csvLine.append(dbReportCSV).append(algoValue);

        try(PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("result.csv", true)))){
            writer.println(csvLine.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
