package de.ugoe.cs.eventbench.ppm;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;

import de.ugoe.cs.eventbench.data.Event;
import de.ugoe.cs.eventbench.markov.IncompleteMemory;
import de.ugoe.cs.util.console.Console;

public class PredictionByPartialMatch {
	
	private String initialSymbol = "GS";
	private String endSymbol = "GE";
	
	private int maxOrder = 3;
	
	private Trie<String> trie;
	
	private Set<String> knownSymbols;
	
	private double probEscape = 0.2d; // TODO getter/setter - steering parameter!
	
	private Random r = new Random(); // TODO should be defined in the constructor
	
	// the training is basically the generation of the trie
	public void train(List<List<Event<?>>> sequences) {
		trie = new Trie<String>();
		knownSymbols = new LinkedHashSet<String>();
		knownSymbols.add(initialSymbol);
		knownSymbols.add(endSymbol);
		
		for(List<Event<?>> sequence : sequences) {
			List<String> stringSequence = new LinkedList<String>();
			stringSequence.add(initialSymbol);
			for( Event<?> event : sequence ) {
				stringSequence.add(event.getStandardId());
			}
			stringSequence.add(endSymbol);
			
			trainStringTrie(stringSequence);
		}
	}
	
	private void trainStringTrie(List<String> sequence) {
		knownSymbols = new LinkedHashSet<String>();		
		IncompleteMemory<String> latestActions = new IncompleteMemory<String>(maxOrder);
		int i=0;
		for(String currentAction : sequence) {
			String currentId = currentAction;
			latestActions.add(currentId);
			knownSymbols.add(currentId);
			i++;
			if( i>=maxOrder ) {
				trie.add(latestActions.getLast(maxOrder));
			}
		}
		int sequenceLength = sequence.size();
		for( int j=maxOrder-1 ; j>0 ; j-- ) {
			trie.add(sequence.subList(sequenceLength-j, sequenceLength));
		}
	}
	
	// TODO needs to be changed from String to <? extends Event>
	public List<String> randomSequence() {
		List<String> sequence = new LinkedList<String>();
		
		IncompleteMemory<String> context = new IncompleteMemory<String>(maxOrder-1);
		context.add(initialSymbol);
		sequence.add(initialSymbol);
		
		String currentState = initialSymbol;
		
		Console.println(currentState);
		while(!endSymbol.equals(currentState)) {
			double randVal = r.nextDouble();
			double probSum = 0.0;
			List<String> currentContext = context.getLast(maxOrder);
			for( String symbol : knownSymbols ) {
				probSum += getProbability(currentContext, symbol);
				if( probSum>=randVal ) {
					context.add(symbol);
					currentState = symbol;
					sequence.add(currentState);
					break;
				}
			}
		}
		return sequence;
	}
		
	private double getProbability(List<String> context, String symbol) {
		double result = 0.0d;
		double resultCurrentContex = 0.0d;
		double resultShorterContex = 0.0d;
		
		List<String> contextCopy = new LinkedList<String>(context); // defensive copy

	
		List<String> followers = trie.getFollowingSymbols(contextCopy); // \Sigma'
		int sumCountFollowers = 0; // N(s\sigma')
		for( String follower : followers ) {
			sumCountFollowers += trie.getCount(contextCopy, follower);
		}
		
		int countSymbol = trie.getCount(contextCopy, symbol); // N(s\sigma)
		if( contextCopy.size()==0 ) {
			resultCurrentContex = ((double) countSymbol) / sumCountFollowers;
		} else {
			resultCurrentContex = ((double) countSymbol / sumCountFollowers)*(1-probEscape);
			contextCopy.remove(0); 
			double probSuffix = getProbability(contextCopy, symbol);
			if( followers.size()==0 ) {
				resultShorterContex = probSuffix;
			} else {
				resultShorterContex = probEscape*probSuffix;
			}
		}
		result = resultCurrentContex+resultShorterContex;
		
		return result;
	}
	
	@Override
	public String toString() {
		return trie.toString();
	}
	
	public void testStuff() {
		// basically an inline unit test without assertions but manual observation
		List<String> list = new ArrayList<String>();
		list.add(initialSymbol);
		list.add("a");
		list.add("b");
		list.add("r");
		list.add("a");
		list.add("c");
		list.add("a");
		list.add("d");
		list.add("a");
		list.add("b");
		list.add("r");
		list.add("a");
		list.add(endSymbol);
		
		PredictionByPartialMatch model = new PredictionByPartialMatch();
		model.trie = new Trie<String>();
		model.trainStringTrie(list);
		model.trie.display();
		Console.println("------------------------");
		model.randomSequence();/*
		Console.println("------------------------");
		model.randomSequence();
		Console.println("------------------------");
		model.randomSequence();
		Console.println("------------------------");*/
		
		List<String> context = new ArrayList<String>();
		String symbol = "a";
		// expected: 5
		Console.traceln(""+model.trie.getCount(context, symbol));
		
		// expected: 0
		context.add("b");
		Console.traceln(""+model.trie.getCount(context, symbol));
		
		// expected: 2
		context.add("r");
		Console.traceln(""+model.trie.getCount(context, symbol));
		
		// exptected: [b, r]
		context = new ArrayList<String>();
		context.add("a");
		context.add("b");
		context.add("r");
		Console.traceln(model.trie.getContextSuffix(context).toString());
		
		// exptected: []
		context = new ArrayList<String>();
		context.add("e");
		Console.traceln(model.trie.getContextSuffix(context).toString());
		
		// exptected: {a, b, c, d, r}
		context = new ArrayList<String>();
		Console.traceln(model.trie.getFollowingSymbols(context).toString());
		
		// exptected: {b, c, d}
		context = new ArrayList<String>();
		context.add("a");
		Console.traceln(model.trie.getFollowingSymbols(context).toString());
		
		// exptected: []
		context = new ArrayList<String>();
		context.add("a");
		context.add("b");
		context.add("r");
		Console.traceln(model.trie.getFollowingSymbols(context).toString());
		
		// exptected: 0.0d
		context = new ArrayList<String>();
		context.add("a");
		Console.traceln(""+model.getProbability(context, "z"));
	}
}
