package Controller;

import java.awt.Toolkit;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;

import Model.*;

/**
 * 
 *
 */
public class SituationFactory {

	/**
	 * this method creates all situations (utterance-scene-pairs) and adds them to the model
	 * @param utterances
	 * @param model
	 * @param imageMap
	 */
	public void createSituations(LinkedList<LinkedList<String>> utterances, LinkedList<String> originalSentences, Model model, HashMap<String, File> imageMap) {
		LinkedList<Situation> situationList = (LinkedList<Situation>) model.getSituations();
		
		// contains all created meanings for use in the add noise method
		ArrayList<Meaning> meaningList = new ArrayList<Meaning>();
		
		for (int iteration = 0; iteration < utterances.size(); iteration++) {
			LinkedList<String> line = utterances.get(iteration);
			Situation newSituation = new Situation();
			
			for (String wordStrRep : line) {
				Meaning newMeaning = createMeaning(wordStrRep, meaningList, imageMap);
				if (newMeaning ==  null) continue;
				Word newWord = createWord(wordStrRep, model.getWordTable());
				
				
				// only adds a word when the word is not already in the utterance (and there is an image that corresponds to this word)
				if (!newSituation.getUtterance().contains(newWord) && newMeaning != null) {
					//System.out.println("new word:"+word);
					newSituation.getUtterance().add(newWord);
				}
				// only adds a meaning when the meaning is not already in the scene or has no image available
				if (newMeaning != null && !newSituation.getScene().contains(newMeaning)) {
					newSituation.getScene().add(newMeaning);
				}
				//set original utterance
				newSituation.setOriginalUtterance(originalSentences.get(iteration));
			}
			if (!newSituation.getUtterance().isEmpty()) {
				situationList.add(newSituation);
			}
		}
		
		for (Situation situation : situationList) {
			addSubtractiveNoise(situation, model.getParameters().getSubtractiveNoise());
			addAdditiveNoise(situation, model.getParameters().getAdditiveNoise(), meaningList);
		}
	}
	
	/**
	 * this method creates a new word from the given string representation or returns the existing word
	 * @param stringRep
	 * @param wordTable
	 * @return the created word object
	 */
	private Word createWord(String stringRep, HashSet<Word>  wordTable) {
		Word word = null;
		
		// search in word-table for existing word
		Iterator<Word> wordIterator = wordTable.iterator();
		while (wordIterator.hasNext()) {
			Word wordInTable = wordIterator.next();
			if (wordInTable.getStringRep().equals(stringRep)) {
				word = wordInTable;
			}
		}
		// word not found in word-table - create a new one and add it to word-table
		if (word == null) {
			word = new Word(stringRep);
			wordTable.add(word);
		}
		
		return word;
	}
	
	/**
	 * this method creates a new meaning from the given string representation or returns the existing meaning
	 * @param stringRep
	 * @param meaningList
	 * @param imageMap
	 * @return the meaning object or null when no image available
	 */
	private Meaning createMeaning(String stringRep, ArrayList<Meaning> meaningList, HashMap<String, File> imageMap) {
		Meaning meaning = null;
		
		for (Meaning meaningInLst : meaningList) {
			if (meaningInLst.getStringRep().equals(stringRep)) {
				meaning = meaningInLst;
			}
		}
		// meaning not found in meaning-list - create a new one and add it to meaning-list
		if (meaning == null) {
			if (imageMap.containsKey(stringRep)) {
				meaning = new Meaning(stringRep, Toolkit.getDefaultToolkit().getImage(imageMap.get(stringRep).getAbsolutePath()));
				meaningList.add(meaning);
			}

		}
		
		return meaning;
	}
		
	/**
	 * this method generates subtractive noise, i.e. meanings are randomly removed from the scene
	 * up to the percentage selected in the parameters 
	 * @param situation
	 * @param subtractiveNoise
	 */
	private void addSubtractiveNoise(Situation situation, Double subtractiveNoise) {
		// noise is rounded up to the next integer value
		int subNoise = (int)Math.ceil(situation.getScene().size() * subtractiveNoise);
		for (int i=0; i < subNoise; i++) {
			int randomNumber = (int)((Math.random()) * situation.getScene().size());
			situation.getScene().remove(randomNumber);
		}
	}
	
	/**
	 * this method generates additive noise, i.e. meanings are randomly added to the scene
	 * up to the percentage selected in the parameters 
	 * @param situation
	 * @param additiveNoise
	 * @param meaningList
	 */
	private void addAdditiveNoise(Situation situation, Double additiveNoise, ArrayList<Meaning> meaningList) {
		// noise is rounded up to the next integer value 
		int addNoise = (int)Math.ceil(situation.getScene().size() * additiveNoise);
		for (int i=0; i < addNoise; i++) {
			int randomNumber = (int)((Math.random()) * meaningList.size());
			int j = 0;
			while (situation.getScene().contains(meaningList.get(randomNumber))) {
				// no appropriate meaning could be found in the meaning-list - so break the loop and add no noise
				if (j++ > meaningList.size()) { break; }
				randomNumber = (randomNumber++) % meaningList.size();
			}
			if (!situation.getScene().contains(meaningList.get(randomNumber))) {
				situation.getScene().add(meaningList.get(randomNumber));
			}
		}
	}
	
}
