package Controller;

import java.util.LinkedList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import Model.Meaning;
import Model.Pair;
import Model.Situation;
import Model.Word;


public class LearningAlgorithm implements Algorithm{
	
	//the list of situations to be processed by the algorithm
	private List<Situation> situationList;
	
	//a small smoothing factor
	private double lamda;

	//the expected number of symbol types
	private double beta;
	
	//a dummy word which is added to each utterance to correct for false learning
	private Word dummy;
	
 	
	
	public LearningAlgorithm(List<Situation> situationList){
		this.situationList = situationList;
		this.beta = computeBeta(situationList);
		this.lamda = (1 / beta) / 10 ;
		this.dummy = new Word("dummy_01");
	}

	@Override
	public void run() {
		for (int i = 0; i < situationList.size(); i++){
			Situation situation = situationList.get(i);
			
			//add dummy word to utterance
			situation.getUtterance().add(dummy);
			
			//perform one iteration. first iteration has iteration nr 1.
			step(situation, i + 1);
			
			//delete dummy word from utterance
			situation.getUtterance().remove(dummy);
			
		}
		
	}

	@Override
	public void step(Situation situation, int iteration) {
		List<HashMap<Meaning, Double>> stateList = new LinkedList<HashMap<Meaning, Double>>();
	
		List<Word> utterance = situation.getUtterance();
		for (Word w: utterance){
			w.setFrequency(w.getFrequency() + 1);
			HashMap<Meaning, Double> newProp = new HashMap<Meaning, Double>();
			
			for (Meaning m: situation.getScene()){
				
				//compute association probability of word w with meaning m (1)
				double pPrev = w.getLastState() != null ? getValue(w.getLastState().getSecond(), w, m) : 0.0;
			
				double norm = 0.0;
				for (Word w_: utterance){
					if (!w.equals(w_)){
						norm += w_.getLastState() != null ? getValue(w_.getLastState().getSecond(), w_, m) : 0.0;
					}
				}
				
				//compute association score (2)
				if (norm == 0.0){
					norm = 1.0;// if no words have been processed so far (1st utterance)
				}
				double a_wm = pPrev / norm;
				double oldScore = w.getLastAssociationScore(m);
				double newScore = oldScore + a_wm;
				
				w.setAssocSum(w.getAssocSum() - oldScore + newScore);
				w.setAssociationScore(m, newScore);
				
				
			}
				
			
			for (Meaning m: situation.getScene()){
				
				//compute probability of word w to be matched to meaning m (3)
				double norm = w.getAssocSum() + beta * lamda;
				double pNew = (w.getLastAssociationScore(m) + lamda) / norm;
				assert(pNew >= 0 && pNew <=1);
				
				//Note: the new probability for each meaning m for a word w can only be set
				//AFTER the probabilities of other meanings m_ with word w in the situation have been newly computed.
				newProp.put(m, pNew);
			}
			
			//save probability maps for word w
			//Note: the new state for a word can only be set AFTER the other words in the utterance have been processed
			stateList.add(newProp);
			
		}
		//set the state and the associationScore of each Word after the whole utterance has been processed
		for (int i = 0; i < utterance.size(); i++){
			Word w = utterance.get(i);
			w.setState(iteration, stateList.get(i));
		}


	}


	/**
	 * 
	 * @param map
	 * @param w
	 * @param m
	 * @return the value, with which meaning m is associated with word w in the map of states/associations. If its not associated, 0 is returned.
	 */
	private double getValue(Map<Meaning, Double> map, Word w, Meaning m){
		
		if (map == null){
			return 0.0;
		}
		double res;
		if (map.containsKey(m)){
			res = map.get(m).doubleValue();
		} else {
			res = 0.0;
		}
		return res;
	}
	
	
	
	/**
	 * 
	 * @param situationList
	 * @return the cardinality of the set of meanings in all utterances
	 */
	public double computeBeta(List<Situation> situationList){
		HashSet<Meaning> exp = new HashSet<Meaning>();
		for (Situation s: situationList){
			exp.addAll(s.getScene());
		}
		return exp.size();
	}
}