package de.ugoe.cs.quest.testgeneration;

import java.security.InvalidParameterException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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

/**
 * <p>
 * Generates a test suite by randomly walking an {@link IStochasticProcess}.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class RandomWalkGenerator {

	/**
	 * <p>
	 * Number of sequences in the test suite.
	 * </p>
	 */
	private final int numSequences;

	/**
	 * <p>
	 * Minimal length of a test sequence.
	 * </p>
	 */
	private final int minLength;

	/**
	 * <p>
	 * Maximal length of a test sequence.
	 * </p>
	 */
	private final int maxLength;

	/**
	 * <p>
	 * In case this member is true, only test cases that end in the global end
	 * event {@link Event#ENDEVENT} are generated. If it is false, the end event
	 * can be any event.
	 * </p>
	 */
	private final boolean validEnd;

	/**
	 * <p>
	 * Maximal number of random walks performed before aborting the test case
	 * generation and returning a test suite with less than
	 * {@link #numSequences} test cases. This can happen if too many generated
	 * random walks have to be discarded because their length is not between
	 * {@link #minLength} and {@link #maxLength}.
	 * </p>
	 */
	private final long maxIter;

	/**
	 * <p>
	 * Actual number of random walks performed to generate the test suite.
	 * </p>
	 */
	private long actualIter = -1;

	/**
	 * <p>
	 * Constructor. Creates a new RandomWalkGenerator and ensures the validity
	 * of the parameters:
	 * <ul>
	 * <li>numSequences must at least be 1
	 * <li>maxLength must at least be 1
	 * <li>minLength must be less than or equal to maxLength
	 * <li>maxIter must be greater than or equal to numSequences
	 * </ul>
	 * If one of these conditions is violated an
	 * {@link InvalidParameterException} is thrown.
	 * </p>
	 * 
	 * @param numSequences
	 *            number of sequences desired for the test suite
	 * @param minLength
	 *            minimal length of a test sequence
	 * @param maxLength
	 *            maximal length of a test sequence
	 * @param validEnd
	 *            defines if test cases have to end with the global end event
	 *            {@link Event#ENDEVENT} (see {@link #validEnd})
	 * @param maxIter
	 *            maximal number of random walks before aborting the test case
	 *            generation (see {@link #maxIter})
	 */
	public RandomWalkGenerator(int numSequences, int minLength, int maxLength,
			boolean validEnd, long maxIter) {
		// check validity of the parameters
		if (numSequences < 1) {
			throw new InvalidParameterException(
					"number of sequences must be at least 1 but is "
							+ numSequences);
		}
		if (maxLength < 1) {
			throw new InvalidParameterException(
					"maximal allowed length of test cases must be at least 1 but is "
							+ maxLength);
		}
		if (minLength > maxLength) {
			throw new InvalidParameterException(
					"minimal allowed length of test cases must be less than or equal to the maximal allowed length (min length: "
							+ minLength + " ; max length: " + maxLength + ")");
		}
		if (maxIter < numSequences) {
			throw new InvalidParameterException(
					"maximal number of iterations must greater than or equal to the number of sequences (number of sequences: "
							+ numSequences
							+ " ; max iterations: "
							+ maxIter
							+ ")");
		}
		this.numSequences = numSequences;
		this.minLength = minLength;
		this.maxLength = maxLength;
		this.validEnd = validEnd;
		this.maxIter = maxIter;
	}

	/**
	 * <p>
	 * Generates a test suite by repeatedly randomly walking a stochastic
	 * process.
	 * </p>
	 * 
	 * @param model
	 *            stochastic process which performs the random walks
	 * @return the test suite
	 */
	@SuppressWarnings("unchecked")
	public Collection<List<? extends Event<?>>> generateTestSuite(
			IStochasticProcess model) {
		if (model == null) {
			throw new InvalidParameterException("model must not be null!");
		}

		Set<List<? extends Event<?>>> sequences = new HashSet<List<? extends Event<?>>>(
				numSequences);
		actualIter = 0;
		while (sequences.size() < numSequences && actualIter < maxIter) {
			List<? extends Event<?>> generatedSequence = model.randomSequence(
					maxLength, validEnd);
			if (generatedSequence.size() >= minLength
					&& generatedSequence.size() <= maxLength) {
				((List<Event<?>>) generatedSequence).add(0, Event.STARTEVENT);
				if (validEnd) {
					((List<Event<?>>) generatedSequence).add(Event.ENDEVENT);
				}
				sequences.add(generatedSequence);
			}
			actualIter++;
		}

		return sequences;
	}

	/**
	 * <p>
	 * Returns the actual number of random walks performed during the last call
	 * of {@link #generateTestSuite(IStochasticProcess)} or -1 if
	 * {@link #generateTestSuite(IStochasticProcess)} has not been called yet.
	 * </p>
	 * 
	 * @return actual number of random walks or -1 if
	 *         {@link #generateTestSuite(IStochasticProcess)} has not been
	 *         called
	 */
	public long getActualIter() {
		return actualIter;
	}
}
