package de.ugoe.cs.eventbench.commands;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import de.ugoe.cs.eventbench.CommandHelpers;
import de.ugoe.cs.eventbench.data.Event;
import de.ugoe.cs.eventbench.data.GlobalDataContainer;
import de.ugoe.cs.eventbench.models.IStochasticProcess;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Command to generate sequences of a given length.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class CMDgenerateHybrid implements Command {

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.ugoe.cs.util.console.Command#run(java.util.List)
	 */
	@Override
	public void run(List<Object> parameters) {
		String modelname;
		String sequencesName;
		int length;
		int maxLengthAll;
		int numSequences;
		boolean validEnd = true;
		try {
			modelname = (String) parameters.get(0);
			sequencesName = (String) parameters.get(1);
			length = Integer.parseInt((String) parameters.get(2));
			maxLengthAll = Integer.parseInt((String) parameters.get(3));
			numSequences = Integer.parseInt((String) parameters.get(4));
			if (parameters.size() >= 6) {
				validEnd = Boolean.parseBoolean((String) parameters.get(5));
			}
		} catch (Exception e) {
			throw new InvalidParameterException();
		}

		if (length <= maxLengthAll) {
			// indirectly call command generateFixedLengthSequences
			List<Object> parameters2 = new LinkedList<Object>();
			parameters2.add(modelname);
			parameters2.add(sequencesName);
			parameters2.add(Integer.toString(length));
			parameters2.add(Integer.toString(length));
			parameters2.add(Boolean.toString(false));
			parameters2.add(Integer.toString(numSequences));
			parameters2.add(Boolean.toString(validEnd));
			CMDgenerateFixedLengthSequences cmd = new CMDgenerateFixedLengthSequences();
			cmd.run(parameters2);
			return;
		}

		IStochasticProcess model = null;
		Object dataObject = GlobalDataContainer.getInstance()
				.getData(modelname);
		if (dataObject == null) {
			CommandHelpers.objectNotFoundMessage(modelname);
			return;
		} else if (!(dataObject instanceof IStochasticProcess)) {
			CommandHelpers.objectNotType(modelname, "IStochasticProcess");
			return;
		}
		model = (IStochasticProcess) dataObject;
		Collection<List<? extends Event<?>>> sequences = new LinkedHashSet<List<? extends Event<?>>>();

		List<List<? extends Event<?>>> seqsTmp = new ArrayList<List<? extends Event<?>>>(
				model.generateSequences(maxLengthAll + 1, true));

		Console.traceln("" + seqsTmp.size() + " of length " + maxLengthAll
				+ " possible");
		List<Double> probabilities = new ArrayList<Double>(seqsTmp.size());
		double probSum = 0.0;
		for (List<? extends Event<?>> sequence : seqsTmp) {
			double prob = model.getProbability(sequence);
			probabilities.add(prob);
			probSum += prob;
		}

		Random r = new Random();
		int j = 0;
		while (sequences.size() < numSequences && j <= numSequences * 100) {
			j++;
			double randVal = r.nextDouble() * probSum;
			double sum = 0.0d;
			int index = -1;
			while (sum < randVal) {
				index++;
				double currentProb = probabilities.get(index);
				sum += currentProb;
			}
			List<? extends Event<?>> seqTmp = seqsTmp.get(index);
			if (!Event.ENDEVENT.equals(seqTmp.get(seqTmp.size() - 1))) {
				List<? extends Event<?>> sequence;
				if (validEnd) {
					sequence = finishSequence(seqTmp, model, length + 2,
							validEnd);
				} else {
					sequence = finishSequence(seqTmp, model, length + 1,
							validEnd);
				}
				sequences.add(sequence);
			}
		}
		if (GlobalDataContainer.getInstance().addData(sequencesName, sequences)) {
			CommandHelpers.dataOverwritten(sequencesName);
		}
		Console.println("" + sequences.size() + " sequences generated");
	}

	public List<? extends Event<?>> finishSequence(
			List<? extends Event<?>> sequence, IStochasticProcess model,
			int maxLength, boolean validEnd) {
		Random r = new Random();
		boolean endFound = false;
		List<Event<?>> sequenceCopy = new LinkedList<Event<?>>(sequence);

		while (!endFound) {
			sequenceCopy = new LinkedList<Event<?>>(sequence);
			while (!endFound && sequenceCopy.size() <= maxLength) {
				double randVal = r.nextDouble();
				double probSum = 0.0;
				for (Event<?> symbol : model.getEvents()) {
					probSum += model.getProbability(sequenceCopy, symbol);
					if (probSum >= randVal) {
						if (!(Event.STARTEVENT.equals(symbol) || Event.ENDEVENT.equals(symbol))) {
							// only add the symbol the sequence if it is not
							// START
							// or END
							sequenceCopy.add(symbol);
						}
						endFound = Event.ENDEVENT.equals(symbol)
								|| (!validEnd && sequenceCopy.size() == maxLength);
						break;
					}
				}
			}
		}
		return sequenceCopy;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.ugoe.cs.util.console.Command#help()
	 */
	@Override
	public void help() {
		Console.println("Usage: generateFixedLengthSequences <modelname> <sequencesName> <lenght> <maxlengthAll> <numSequences> {<validEnd>}");
	}

}
