//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.testgeneration;

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

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.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>
     * Maximal number of attempts for creating a valid sequence. If this value is reached, the
     * sequence generation will be aborted.
     * </p>
     */
    private final long maxIterValidSequence;

    /**
     * <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 IllegalArgumentException} 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})
     * @param maxIterValidSequence
     *            maximal number of attempts to create a valid sequence within the given restrictions
     */
    public RandomWalkGenerator(int numSequences,
                               int minLength,
                               int maxLength,
                               boolean validEnd,
                               long maxIter,
                               long maxIterValidSequence)
    {
        // check validity of the parameters
        if (numSequences < 1) {
            throw new IllegalArgumentException("number of sequences must be at least 1 but is " +
                numSequences);
        }
        if (maxLength < 1) {
            throw new IllegalArgumentException(
                                               "maximal allowed length of test cases must be at least 1 but is " +
                                                   maxLength);
        }
        if (minLength > maxLength) {
            throw new IllegalArgumentException(
                                               "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 IllegalArgumentException(
                                               "maximal number of iterations must greater than or equal to the number of sequences (number of sequences: " +
                                                   numSequences +
                                                   " ; max iterations: " +
                                                   maxIter +
                                                   ")");
        }
        if( maxIterValidSequence < 1) {
            throw new IllegalArgumentException("maximal number of attempts to get a valid sequence must be positive, but is: " + maxIterValidSequence);
        }
        this.numSequences = numSequences;
        this.minLength = minLength;
        this.maxLength = maxLength;
        this.validEnd = validEnd;
        this.maxIter = maxIter;
        this.maxIterValidSequence = maxIterValidSequence;
    }

    /**
     * <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
     */
    public Collection<List<Event>> generateTestSuite(IStochasticProcess model) {
        if (model == null) {
            throw new IllegalArgumentException("model must not be null!");
        }

        Set<List<Event>> sequences = new HashSet<List<Event>>(numSequences);
        actualIter = 0;
        while (sequences.size() < numSequences && actualIter < maxIter) {
            List<Event> generatedSequence = model.randomSequence(maxLength, validEnd, maxIterValidSequence);
            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;
    }
}
