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.BitMap;

import java.util.*;

import static cz.muni.disa.adamiss.se.helper.Helper.*;
/**
 * Implementation of Spam algorithm for frequent sequence mining https://core.ac.uk/download/pdf/205383828.pdf
 * @author Lukáš Suchánek, 433654
 *
 */


public class Spam implements Operator {

    private Map<List<AdamissObject>, Integer> results = new HashMap<>();
    private int maxLength = 0;

    @Override
    public TransactionFile apply(TransactionFile transactionFile, Map<String, Object> params) {
        int oldSize = 0;
        int minSup = (Integer) params.get(ParameterName.miningThreshold.toString());
        int max = 0;
        for (Transaction t: transactionFile.getTransactions()){
            if(t.getData().size() > max){
                max = t.getData().size();
            }
        }
        maxLength = max;
        BitMap map = new BitMap();
        map.apply(transactionFile, minSup);
        int numOfSeq = transactionFile.getTransactions().size();
        Map<List<AdamissObject>, BitSet> bitMap = map.getBitMap();
        countSup(bitMap, minSup, numOfSeq);
        oldSize = results.size();
        Map<List<AdamissObject>, BitSet> generated = generate(bitMap, minSup, numOfSeq);
        while (oldSize < results.size()){
            oldSize = results.size();
            generated = generate(generated, minSup, numOfSeq);
        }

        return getTransactionFileFromResults(results);
    }

    /**
     * Generate candidate k+1-sequences from k-sequences
     * @param current set of k-sequences
     * @param minSup mining threshold
     * @param numOfSeq number of sequences in database - used for counting support
     * @return set of candidate k+1-sequences
     */
    private Map<List<AdamissObject>, BitSet> generate(Map<List<AdamissObject>, BitSet> current, int minSup, int numOfSeq){
        Map<List<AdamissObject>, BitSet> generated = new HashMap<>();
        for (Map.Entry<List<AdamissObject>, BitSet> curEntry: current.entrySet()){
            for(Map.Entry<List<AdamissObject>, BitSet> baseEntry: current.entrySet()){
                List<AdamissObject> seq1 = new ArrayList<>(curEntry.getKey());
                List<AdamissObject> seq2 = new ArrayList<>(baseEntry.getKey());
                List<AdamissObject> seq1copy = new ArrayList<>(seq1);
                seq1copy.remove(0);
                List<AdamissObject> seq2copy = new ArrayList<>(seq2);
                seq2copy.remove(seq2.size()-1);
                if(seq1copy.equals(seq2copy)){
                    List<AdamissObject> candidate = new ArrayList<>(seq1);
                    candidate.add(seq2.get(seq2.size() - 1));
                    BitSet shifted = curEntry.getValue().get(1, curEntry.getValue().length());
                    shifted.and(baseEntry.getValue());
                    if(!shifted.isEmpty()){
                        int s = singleCountSup(shifted, numOfSeq);
                        if(s>=minSup){
                            generated.put(candidate, shifted);
                            List<AdamissObject> pattern = new ArrayList<>(candidate);
                            Collections.reverse(pattern);
                            results.put(pattern, s);
                        }
                    }
                }
            }
        }
//        countSup(generated, minSup, numOfSeq);
        return generated;
    }

    /**
     * Count support for single sequence
     * @param bitSet bitmap of sequence
     * @param numOfSeq number of sequences in database
     * @return support for sequence
     */
    private int singleCountSup(BitSet bitSet, int numOfSeq){
        int max = maxLength;
        int support = 0;
        int start = 0;
        int end = max;
        for(int i = 0; i< numOfSeq; i++){
            BitSet sequence = bitSet.get(start, end);
            if(!sequence.isEmpty()){
                support++;
            }
            start += max + 1;
            end += max + 1;
        }
        return support;
    }

    /**
     * Cont support for set of candidate k-sequences
     * @param bitMap set of candidate sequences with their respective bitmap
     * @param minSup mining threshold
     * @param numOfSeq
     */
    private void countSup(Map<List<AdamissObject>, BitSet> bitMap, int minSup, int numOfSeq){
        List<List<AdamissObject>> toRemove = new ArrayList<>();
        int max = maxLength;
        for (Map.Entry<List<AdamissObject>, BitSet> entry: bitMap.entrySet()){
            BitSet row = entry.getValue();
            int support=0;
            int start = 0;
            int end = max;

            for(int i = 0; i< numOfSeq; i++){
                BitSet sequence = row.get(start, end);
                if(!sequence.isEmpty()){
                    support++;
                }
                start += max + 1;
                end += max + 1;
            }
            if(support >= minSup){
                List<AdamissObject> pattern = new ArrayList<>(entry.getKey());
                Collections.reverse(pattern);
                results.put(pattern, support);
            }else {
                toRemove.add(entry.getKey());
            }
        }

        for(List<AdamissObject> o: toRemove){
            List<AdamissObject> pattern = new ArrayList<>(o);
            Collections.reverse(pattern);
            bitMap.remove(o);
        }

    }


}
