package cz.muni.disa.adamiss.se.operators;


import cz.muni.disa.adamiss.core.common.enums.ParameterName;
import cz.muni.disa.adamiss.core.objects.AdamissObject;
import cz.muni.disa.adamiss.core.objects.Operator;
import cz.muni.disa.adamiss.core.objects.Transaction;
import cz.muni.disa.adamiss.core.objects.TransactionFile;
import cz.muni.disa.adamiss.se.structures.VerticalDatabase;
import javafx.util.Pair;

import java.util.*;

import static cz.muni.disa.adamiss.se.helper.Helper.*;
/**
 * Implementation of Spade algorithm for frequent sequence mining https://philippe-fournier-viger.com/spmf/SPADE.pdf
 * @author Lukáš Suchánek, 433654
 *
 */

public class Spade implements Operator {
    private static Map<List<AdamissObject>, Integer> results = new HashMap<>();

    @Override
    public TransactionFile apply(TransactionFile transactionFile, Map<String, Object> params) {
        int minSup = (Integer) params.get(ParameterName.miningThreshold.toString());
        VerticalDatabase database = new VerticalDatabase(transactionFile);
        countSupport(database.getDatabase(), minSup);
        Map<List<AdamissObject>, List<Pair<Long, Long>>> vDatabase = database.getDatabase();
        removeInfrequentDatabase(vDatabase, minSup);
        Map<List<AdamissObject>, List<Pair<Long, Long>>> v2Database = getV2Database(vDatabase);
        removeInfrequentDatabase(v2Database, minSup);
        countSupport(v2Database, minSup);
        generateCandidates(v2Database, minSup);

        List<Transaction> transactions = new ArrayList<>();
        return getTransactionFileFromResults(results);
    }

    /**
     * Get vertical database of 2-sequences
     * @param vDatabase vertical database of 1-sequences
     * @return vertical database of 2-sequences
     */
    private static Map<List<AdamissObject>, List<Pair<Long, Long>>> getV2Database(Map<List<AdamissObject>, List<Pair<Long, Long>>> vDatabase){
        Map<List<AdamissObject>, List<Pair<Long, Long>>> v2Database = new HashMap<>();
        for(Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> e: vDatabase.entrySet()){
            for(Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> entry:vDatabase.entrySet()){
                for (Pair<Long,Long> p:e.getValue()){
                    for(Pair<Long,Long> pair: entry.getValue()){
                        if (p.getKey().equals(pair.getKey()) && p.getValue().equals(pair.getValue() -1 )){
                            List<AdamissObject> list = new ArrayList<>();
                            list.add(e.getKey().get(0));
                            list.add(entry.getKey().get(0));
                            Pair<Long, Long> resultPair = new Pair<>(p.getKey(), pair.getValue());
                            if(!v2Database.containsKey(list)) {
                                List<Pair<Long, Long>> resList = new ArrayList<>();
                                resList.add(resultPair);
                                v2Database.put(list, resList);
                            }else {
//                                Pair<Long, Long> resultPair = new Pair<>(p.getKey(), pair.getValue());
                                v2Database.get(list).add(resultPair);
                            }
                        }
                    }
                }
            }
        }
        return v2Database;
    }

    /**
     * Generate frequent k+1-sequences from k-sequences
     * @param vDatabase vertical database of k-sequences
     * @param minSup mining threshold
     */
    private static void generateCandidates(Map<List<AdamissObject>, List<Pair<Long, Long>>> vDatabase, int minSup){
        Map<List<AdamissObject>, List<Pair<Long, Long>>> generated = new HashMap<>();
        for(Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> e1: vDatabase.entrySet()){
            for(Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> e2:vDatabase.entrySet()){
                List<AdamissObject> seq1 = new ArrayList<>(e1.getKey());
                seq1.remove(0);
                List<AdamissObject> seq2 = new ArrayList<>(e2.getKey());
                seq2.remove(seq2.size()-1);
                if(seq1.equals(seq2)) {
                    for (Pair<Long, Long> p1 : e1.getValue()) {
                        for (Pair<Long, Long> p2 : e2.getValue()) {
                            if (p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue() - 1)) {
                                List<AdamissObject> list = new ArrayList<>(e1.getKey());
                                list.add(e2.getKey().get(e2.getKey().size() - 1));
                                Pair<Long, Long> resultPair = new Pair<>(p1.getKey(), p2.getValue());
                                if (!generated.containsKey(list)) {
                                    List<Pair<Long, Long>> resList = new ArrayList<>();
                                    resList.add(resultPair);
                                    generated.put(list, resList);
                                } else {
                                    generated.get(list).add(resultPair);
                                }
                            }
                        }
                    }
                }
            }
        }
        removeInfrequentDatabase(generated, minSup);
        if(generated.isEmpty()){
            return;
        }
        countSupport(generated, minSup);
        generateCandidates(generated, minSup);
    }

    /**
     * Remove infrequent sequences from database
     * @param vDatabase vertical sequence database
     * @param minSup mining threshold
     */
    private static void removeInfrequentDatabase(Map<List<AdamissObject>, List<Pair<Long, Long>>> vDatabase, int minSup){
        Iterator iterator = vDatabase.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> entry = (Map.Entry) iterator.next();
            if(entry.getValue().size() < minSup){
                iterator.remove();
            }
        }
    }

    /**
     * Count support for candidate k-sequences
     * @param data vertical database of candidate k-sequences
     * @param minSup mining threshold
     */
    private static void countSupport(Map<List<AdamissObject>, List<Pair<Long, Long>>> data, int minSup){
        for(Map.Entry<List<AdamissObject>, List<Pair<Long, Long>>> e: data.entrySet()){
            Set<Long> set = new HashSet<>();
            for(Pair<Long,Long> p: e.getValue()){
                set.add(p.getKey());
            }
            if(set.size() >= minSup){
                results.put(e.getKey(), set.size());
            }
        }

    }
}
