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 java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static cz.muni.disa.adamiss.se.helper.Helper.*;

/**
 * Implementation of GSP algorithm for frequent sequence mining https://www.philippe-fournier-viger.com/spmf/GSP96.pdf
 * @author Lukáš Suchánek, 433654
 *
 */

public class GSP implements Operator {

    private 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());
        Map<List<AdamissObject>, Integer> result = this.get1set(transactionFile, minSup);
        final AtomicInteger k = new AtomicInteger(1);
        while (result.keySet().stream().anyMatch((x) -> x.size() == k.get())){
            k.incrementAndGet();
            Set<List<AdamissObject>> generatedCandidates = getCandidateSetK(result.keySet(),k.get());
            for(List<AdamissObject> p: generatedCandidates){
                if(p.size() >= k.get()){
                    countSupport(p,transactionFile,result);
                }
            }
            removeInfrequent(result, minSup);
        }
//        printPatterns(result);
        results = result;
        List<Transaction> transactions = new ArrayList<>();
        return getTransactionFileFromResults(results);
    }

    /**
     * Function to find all frequent 1sequence
     * @param database TransactionFile with loaded transactions
     * @param minSup minimal minig treshold for support
     * @return Map of frequent sequencies and their support
     */
    public Map<List<AdamissObject>, Integer> get1set(TransactionFile database, Integer minSup){
        final Map<List<AdamissObject>, Integer> result = new HashMap<>();
        Iterator<Transaction> iterator = database.getIterator();
        while (iterator.hasNext()) {
            Transaction transaction = iterator.next();
            List<AdamissObject> objects = transaction.getData();
            List<AdamissObject> added = new ArrayList<>();
            for (AdamissObject o : objects) {
                if(added.contains(o)) continue;
                added.add(o);
                List<AdamissObject> key = new ArrayList<>();
                key.add(o);
                increaseSup(result, key);
            }
        }
        removeInfrequent(result, minSup);
//        printPatterns(result);
        return result;
    }

    /**
     * Function to create candidate set of ksequencies
     * @param minedSeq set of already mined sequencies up k-1sequencies
     * @param k length of sequence
     * @return set of new generated ksequencies
     */
    private Set<List<AdamissObject>> getCandidateSetK(Set<List<AdamissObject>> minedSeq, int k){
        Set<List<AdamissObject>> generatedSeq = new HashSet<>();
        for(List<AdamissObject> seq1: minedSeq){
            if(seq1.size()==k-1){
                List<AdamissObject> seq1copy = new ArrayList<>(seq1);
                seq1copy.remove(0);
                for (List<AdamissObject> seq2: minedSeq){
                    if(seq2.size() == k-1) {
                        List<AdamissObject> seq2copy = new ArrayList<>(seq2);
                        seq2copy.remove(seq2copy.size() - 1);
                        if (seq2copy.equals(seq1copy)) {
                            List<AdamissObject> generatedCandidateSeq = new ArrayList<>(seq1);
                            generatedCandidateSeq.add(seq2.get(seq2.size() - 1));
                            generatedSeq.add(generatedCandidateSeq);
                        }
                    }
                }
            }
        }
        return generatedSeq;
    }

    /**
     * count support for sequence
     * @param seq sequence which support is to be counted
     * @param database database where to look for sequence
     * @param result map of frequent sequencies and their support
     */
    private void countSupport(List<AdamissObject> seq, TransactionFile database, Map<List<AdamissObject>, Integer> result){
        Iterator<Transaction> iterator = database.getIterator();
        while (iterator.hasNext()) {
            Transaction transaction = iterator.next();
            List<AdamissObject> t = transaction.getData();
            if(Collections.indexOfSubList(t,seq) != -1){
                increaseSup(result, seq);
            }
        }
    }

    public Map<List<AdamissObject>, Integer> getResults() {
        return results;
    }
}
