package cz.muni.disa.adamiss.se.stats.runners;

import cz.muni.disa.adamiss.core.common.enums.ParameterName;
import cz.muni.disa.adamiss.core.objects.Operator;
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.helper.Helper;
import cz.muni.disa.adamiss.se.operators.GSP;
import cz.muni.disa.adamiss.se.operators.PrefixSpan;
import cz.muni.disa.adamiss.se.operators.Spam;
import cz.muni.disa.adamiss.se.stats.objects.ExperimentReport;
import cz.muni.disa.adamiss.se.structures.DatasetContainer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

    private static Map<String, Integer> cache = new HashMap<>();

    public static List<ExperimentReport> run(DatasetContainer container){
        List<ExperimentReport> result = new ArrayList<>();
        try{
            var thresholds = Dataset.valueOf(container.name()).getSupports();
            for (int threshold : thresholds) {
                for (Algo algo : Algo.values()) {
                    result.add(run(container.name() + "_" + algo + "_" + threshold, container, threshold, algo));
                }
            }
        }catch (IllegalArgumentException e){
            logger.warn("No threshold associated. Trying from the top to first found.");
            for (Algo algo : Algo.values()) {
                try{
                    result.add(run(container.name() + "_" + algo, container, algo));
                }catch(NoResultException ex){
                    logger.error(ex.getMessage());
                }
            }
        }

        return result;
    }

    private static ExperimentReport run(String name, DatasetContainer container, int threshold, Algo algo) {

        logger.debug("Running experiment: (" + container.name() + ", " + algo.toString() + ", " + threshold + ")");
        Map<String, Object> params = new HashMap<>();
        params.put(ParameterName.miningThreshold.toString(), threshold);
        Operator operator;
        switch (algo) {
            case GSP -> operator = new GSP();
            case PREFIX_SPAN -> operator = new PrefixSpan();
            case SPAM -> operator = new Spam();
            //case SPADE -> operator = new Spade();
            default -> throw new IllegalStateException("Unexpected value: " + algo);
        }

        long startTime = System.currentTimeMillis();
        operator.apply(container.dataset(), params);
        long time = System.currentTimeMillis() - startTime;


        logger.debug("Experiment ended in : " + time + "ms");
        return new ExperimentReport(name, container.dbReport(), threshold, time, algo);
    }

    public static List<ExperimentReport> runAll(List<DatasetContainer> containers) {
        List<ExperimentReport> result = new ArrayList<>();
        for (DatasetContainer container : containers) {
            result.addAll(run(container));
        }
        return result;
    }

    public static ExperimentReport run(String name, DatasetContainer container, Algo algo) throws NoResultException{

        logger.debug("Running experiment: (" + container.name() + ", " + algo.toString() + ")");
        long time = 0;
        TransactionFile result = new TransactionFile(new ArrayList<>());
        int threshold = cache.containsKey(container.name())? cache.get(container.name())+1 : Helper.getFrequency(container.dataset()).values().stream().max(Integer::compareTo).get();

        while(result.getTransactions().stream().filter((x->x.getData().size()>1)).collect(Collectors.toList()).isEmpty() && threshold > 0) {
            threshold--;
            Map<String, Object> params = new HashMap<>();
            params.put(ParameterName.miningThreshold.toString(), threshold);
            Operator operator;
            switch (algo) {
                case GSP -> operator = new GSP();
                case PREFIX_SPAN -> operator = new PrefixSpan();
                case SPAM -> operator = new Spam();
                //case SPADE -> operator = new Spade();
                default -> throw new IllegalStateException("Unexpected value: " + algo);
            }

            long startTime = System.currentTimeMillis();
            result = operator.apply(container.dataset(), params);
            time = System.currentTimeMillis() - startTime;
            logger.trace("tried: " + name + "[" + threshold + ", " + result.getTransactions().size() + "] - " + time);
        }

        logger.debug("saving: " + name + "[" + threshold + ", " + result.getTransactions().size() + "] - " + time);
        cache.put(container.name(), threshold);
        logger.debug("Experiment ended in : " + time + "ms");
        if(threshold<=0) throw new NoResultException(container.name() + " does not contain results.");
        return new ExperimentReport(name, container.dbReport(), threshold, time, algo);
    }

}
