
package de.ugoe.cs.quest.coverage;

import java.security.InvalidParameterException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import de.ugoe.cs.quest.eventcore.Event;
import de.ugoe.cs.quest.usageprofiles.IStochasticProcess;

/**
 * <p>
 * This class calculates various types of sequence coverage in relation to a collection of observed
 * sequences.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class CoverageCalculatorObserved {

    /**
     * <p>
     * Sequences for which the coverage is calculated.
     * </p>
     */
    private final Collection<List<Event>> sequences;

    /**
     * <p>
     * Observed sequences that are baseline for the coverage calculation.
     * </p>
     */
    private final Collection<List<Event>> observedSequences;

    /**
     * <p>
     * Length of the subsequences in relation to which the covarage is calculated.
     * </p>
     */
    private final int length;

    /**
     * <p>
     * All subsequences of {@link #length} of {@link #sequences}.
     * </p>
     */
    private Collection<List<Event>> subSeqsGenerated = null;

    /**
     * <p>
     * All subsequences of {@link #length} of {@link #observedSequences}.
     * </p>
     */
    private Collection<List<Event>> subSeqsObserved = null;

    /**
     * <p>
     * Constructor. Creates a new CoverageCalculatorObserved for given collections of observed
     * sequences and generated sequences.
     * </p>
     * 
     * @param observedSequences
     *            observed sequences in relation to which the coverage is calculated; must not be
     *            null
     * @param sequences
     *            sequences for which the coverage is calculated; must not be null
     * @param length
     *            length of the subsequences for which the coverage is analyzed; must be >0
     * @throws InvalidParameterException
     *             thrown if observedSequences or sequences is null or length less than or equal to
     *             0
     */
    public CoverageCalculatorObserved(Collection<List<Event>> observedSequences,
                                      Collection<List<Event>> sequences,
                                      int length)
    {
        if (observedSequences == null) {
            throw new InvalidParameterException("observed sequences must not be null");
        }
        if (sequences == null) {
            throw new InvalidParameterException("sequences must not be null");
        }
        if (length <= 0) {
            throw new InvalidParameterException("length must be >0; actual value: " + length);
        }
        this.observedSequences = observedSequences;
        this.sequences = sequences;
        this.length = length;
    }

    /**
     * <p>
     * Calculates the percentage of subsequences of length k that occur, with reference to those
     * that were observed.
     * </p>
     * 
     * @return coverage percentage
     */
    public double getCoverageObserved() {
        createSubSeqs();
        Collection<List<Event>> subSeqsObservedCopy =
            new LinkedHashSet<List<Event>>(subSeqsObserved);
        subSeqsObservedCopy.retainAll(subSeqsGenerated);
        return ((double) subSeqsObservedCopy.size()) / subSeqsObserved.size();
    }

    /**
     * <p>
     * Calculates the weight of subsequences of length k that occur, with reference to those that
     * were observed.
     * </p>
     * 
     * @param process
     *            stochastic process in reference to which the weight is calculated
     * @return coverage percentage
     */

    public double getCoverageObservedWeigth(IStochasticProcess process) {
        createSubSeqs();
        Map<List<Event>, Double> weightMap =
            SequenceTools.generateWeights(process, subSeqsObserved);

        Collection<List<Event>> subSeqsObservedCopy =
            new LinkedHashSet<List<Event>>(subSeqsObserved);
        subSeqsObservedCopy.retainAll(subSeqsGenerated);
        double weight = 0.0d;
        for (List<Event> subSeq : subSeqsObservedCopy) {
            weight += weightMap.get(subSeq);
        }
        return weight;
    }

    /**
     * <p>
     * Calculates the percentage of generated subsequences of length k that occur and have not been
     * observed, with reference to all generated subsequences.
     * </p>
     * 
     * @return coverage percentage
     */
    public double getNewPercentage() {
        createSubSeqs();
        Collection<List<Event>> subSeqsGeneratedCopy =
            new LinkedHashSet<List<Event>>(subSeqsGenerated);
        subSeqsGeneratedCopy.removeAll(subSeqsObserved);
        return ((double) subSeqsGeneratedCopy.size()) / subSeqsGenerated.size();
    }

    /**
     * <p>
     * Calculates the percentage of generated subsequences of length k that occur and have not been
     * observed, with references to all possible new subsequences.
     * </p>
     * 
     * @param process
     *            stochastic process which is used to determine which subsequences are possible
     * @return coverage percentage
     * @throws InvalidParameterException
     *             thrown if process is null
     */
    public double getCoveragePossibleNew(IStochasticProcess process) {
        if (process == null) {
            throw new InvalidParameterException("process must not be null");
        }
        createSubSeqs();
        Collection<List<Event>> subSeqsGeneratedCopy =
            new LinkedHashSet<List<Event>>(subSeqsGenerated);
        Collection<List<Event>> subSeqsPossible = process.generateSequences(length);
        subSeqsGeneratedCopy.removeAll(subSeqsObserved);
        subSeqsPossible.removeAll(subSeqsObserved);
        int possibleSize = subSeqsPossible.size();
        subSeqsPossible.retainAll(subSeqsGeneratedCopy);
        return ((double) subSeqsPossible.size()) / possibleSize;
    }

    /**
     * <p>
     * Calculates the weight of generated subsequences of length k that occur and have not been
     * observed, with references to all possible new subsequences.
     * </p>
     * 
     * @param process
     *            stochastic process which is used to determine the weights and which subsequences
     *            are possible
     * @return coverage percentage
     * @throws InvalidParameterException
     *             thrown if process is null
     */
    public double getCoveragePossibleNewWeight(IStochasticProcess process) {
        if (process == null) {
            throw new InvalidParameterException("process must not be null");
        }
        createSubSeqs();
        Collection<List<Event>> subSeqsGeneratedCopy =
            new LinkedHashSet<List<Event>>(subSeqsGenerated);
        Collection<List<Event>> subSeqsPossible = process.generateSequences(length);
        subSeqsGeneratedCopy.removeAll(subSeqsObserved);
        subSeqsPossible.removeAll(subSeqsObserved);
        Map<List<Event>, Double> weightMap =
            SequenceTools.generateWeights(process, subSeqsPossible);
        double weight = 0.0d;
        for (List<Event> subSeq : subSeqsGeneratedCopy) {
            Double currentWeight = weightMap.get(subSeq);
            if (currentWeight != null) {
                weight += currentWeight;
            }
        }
        return weight;
    }

    /**
     * <p>
     * Returns the number of covered subsequences of length k.
     * </p>
     * 
     * @return number of covered subsequences
     */
    public int getNumObserved() {
        createSubSeqs();
        return subSeqsObserved.size();
    }

    /**
     * <p>
     * Returns the number of covered subsequences of length k.
     * </p>
     * 
     * @return number of covered subsequences
     */
    public int getNumCovered() {
        createSubSeqs();
        return subSeqsGenerated.size();
    }

    public int getNumNew() {
        createSubSeqs();
        Collection<List<Event>> subSeqsGeneratedCopy =
            new LinkedHashSet<List<Event>>(subSeqsGenerated);
        subSeqsGeneratedCopy.removeAll(subSeqsObserved);
        return subSeqsGeneratedCopy.size();
    }

    /**
     * <p>
     * Helper function that calcuates the subsequences of length k that have been observed and
     * generated.
     * </p>
     */
    private void createSubSeqs() {
        if (subSeqsObserved == null) {
            subSeqsObserved = SequenceTools.containedSubSequences(observedSequences, length);
        }
        if (subSeqsGenerated == null) {
            subSeqsGenerated = SequenceTools.containedSubSequences(sequences, length);
        }
    }
}
