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<? extends Event<?>>> sequences;

	/**
	 * <p>
	 * Observed sequences that are baseline for the coverage calculation.
	 * </p>
	 */
	private final Collection<List<? extends 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<? extends Event<?>>> subSeqsGenerated = null;

	/**
	 * <p>
	 * All subsequences of {@link #length} of {@link #observedSequences}.
	 * </p>
	 */
	private Collection<List<? extends 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<? extends Event<?>>> observedSequences,
			Collection<List<? extends 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<? extends Event<?>>> subSeqsObservedCopy = new LinkedHashSet<List<? extends 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<? extends Event<?>>, Double> weightMap = SequenceTools
				.generateWeights(process, subSeqsObserved);

		Collection<List<? extends Event<?>>> subSeqsObservedCopy = new LinkedHashSet<List<? extends Event<?>>>(
				subSeqsObserved);
		subSeqsObservedCopy.retainAll(subSeqsGenerated);
		double weight = 0.0d;
		for (List<? extends 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<? extends Event<?>>> subSeqsGeneratedCopy = new LinkedHashSet<List<? extends 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<? extends Event<?>>> subSeqsGeneratedCopy = new LinkedHashSet<List<? extends Event<?>>>(
				subSeqsGenerated);
		Collection<List<? extends 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<? extends Event<?>>> subSeqsGeneratedCopy = new LinkedHashSet<List<? extends Event<?>>>(
				subSeqsGenerated);
		Collection<List<? extends Event<?>>> subSeqsPossible = process
				.generateSequences(length);
		subSeqsGeneratedCopy.removeAll(subSeqsObserved);
		subSeqsPossible.removeAll(subSeqsObserved);
		Map<List<? extends Event<?>>, Double> weightMap = SequenceTools
				.generateWeights(process, subSeqsPossible);
		double weight = 0.0d;
		for (List<? extends 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<? extends Event<?>>> subSeqsGeneratedCopy = new LinkedHashSet<List<? extends 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);
		}
	}
}
