package Controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observer;
import java.util.Set;
import java.util.TreeSet;

import Model.Meaning;
import Model.Model;
import Model.Parameters;
import Model.Situation;
import Model.Word;
import Model.WordFilter;

/**
 * Controller.
 * 
 * @author Stefan Helfrich <stefan.helfrich@googlemail.com>
 */
public class Controller {

	/* model with which this controller interacts */
	private Model model;

	/* logger */
	private LogWriter logger;

	/* iteration currently selected in the user interface */
	private int currentIteration = 1;

	/**
	 * Standard constructor, setting the according fields.
	 * 
	 * @param model
	 *            Model with which this controller will interact
	 */
	public Controller(Model model) {
		this.model = model;
	}

	/**
	 * This method is called from a view (in general the GUI) to set the
	 * according parameters in Model.
	 * 
	 * @param utterance
	 * @param imageDB
	 * @param log
	 * @param subtractiveNoise
	 * @param additiveNoise
	 * @param learningThreshold
	 */
	public void setParameters(File utterance, File imageDB, File log,
			double subtractiveNoise, double additiveNoise,
			double learningThreshold) {
		Parameters params = model.getParameters();
		WordFilter filter = model.getWordFilter();
		params.setUtteranceFile(utterance);
		params.setImageDB(imageDB);
		params.setLogFile(log);
		params.setSubtractiveNoise(subtractiveNoise);
		params.setAdditiveNoise(additiveNoise);
		filter.setLT(learningThreshold);
	}

	/**
	 * This method is called from a view and triggers the preprocessing.
	 * 
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	public void preprocess() throws FileNotFoundException, IOException {
		Preprocessor p = new Preprocessor();
		p.preprocess(model);

		// Logger cannot be instantiated before preprocessing is finished
		logger = new LogWriter(model.getParameters().getLogFile());
		logger.buildTree(model);

		// Since preprocessing is finished, register model as observer with
		// words
		registerObserverWithWords(model);
		registerObserverWithWords(logger);
	}

	/**
	 * This method triggers the processing of situations based on a call from a
	 * view.
	 */
	public void process() {
		List<Situation> situations = model.getSituations();
		Processor p = new Processor(situations);
		p.runAlgorithm();
	}

	/**
	 * This method is invoked from the view to load the default settings
	 */
	/*
	 * Not implemented in prototype public void loadDefaultSettings() {
	 * 
	 * }
	 */

	/**
	 * This method is invoked from the view to save the default settings
	 */
	/*
	 * Not implemented in prototype public void saveDefaultSettings() {
	 * 
	 * }
	 */

	/**
	 * This method is called from the view (a ScenePanel) to get the situation
	 * for the current iteration.
	 * 
	 * @return Situation The situation of the current iteration.
	 */
	public Situation getSituationData() {
		if (currentIteration > 0 && currentIteration <= getLastIteration())
			return model.getSituations().get(currentIteration - 1);
		else
			return null;
	}

	/**
	 * This method is called from a word comprehension score panel to get the
	 * comprehension score for a word.
	 * 
	 * comprehension_score(w) = p(correct_meaning for word | word)
	 * 
	 * @param w
	 *            The word for which the development of the comprehension score
	 *            has to be searched.
	 * @return A Map containing the iteration number and the according
	 *         comprehension score.
	 * @throws Exception
	 *             An exception is thrown if no correct meaning can be found for
	 *             the word w.
	 */
	public Map<Integer, Double> getComprehensionScore(Word w) throws Exception {
		HashMap<Integer, HashMap<Meaning, Double>> states = w.getStates();
		HashMap<Integer, Double> comprehensionScore = new HashMap<Integer, Double>();

		// Get correct meaning by checking for a meaning with the same string
		// representation as the word
		Meaning correctMeaning = model.getMeaningForString(w.getStringRep());
		if (correctMeaning == null) {
			throw new Exception("No correct meaning found.");
		}

		for (Map.Entry<Integer, HashMap<Meaning, Double>> e : states.entrySet()) {
			int iteration = e.getKey();
			HashMap<Meaning, Double> probability = e.getValue();

			Double score = probability.get(correctMeaning);
			// if score cannot be retrieved, set to 0.0
			if (score == null) {
				score = 0.0;
			}

			comprehensionScore.put(iteration, score);
		}

		return comprehensionScore;
	}

	/**
	 * Container class for passing the iteration number and the set of focused
	 * words.
	 */
	class WordComprehensionScoreData {
		public int currentIteration;
		public Set<Word> focusedWords;

		public WordComprehensionScoreData(int currentIteration,
				Set<Word> focusedWords) {
			this.currentIteration = currentIteration;
			this.focusedWords = focusedWords;
		}
	}

	/**
	 * This method is called from the words learned over time view to retrieve
	 * the data.
	 * 
	 * @return A map containing the total number (accumulated) of learned words
	 *         for a specific iteration
	 */
	public Map<Integer, Integer> getWordsLearnedOverTimeData() {
		return model.getWordsLearnedOverTime();
	}

	/**
	 * This method retrieves the table of words from the model.
	 * 
	 * @return a Set containing all words.
	 */
	public Set<Word> getWordTableData() {
		return model.getWordTable();
	}

	/**
	 * This method adds a word the list of focused words.
	 * 
	 * @param word
	 *            A word that is to be focused
	 */
	public void addWordToFocusedPanel(Word word) {
		model.getFocusedWords().add(word);
	}

	/**
	 * This method removes a word from the list of focused words.
	 * 
	 * @param word
	 *            A word that is to be removed
	 */
	public void removeWordFromFocusedPanel(Word word) {
		model.getFocusedWords().remove(word);
	}

	/**
	 * This method gets the list of focused words from the model.
	 * 
	 * @return A set of words containing all focused words.
	 */
	public Set<Word> getFocusedWords() {
		return model.getFocusedWords();
	}

	// Navigation

	/**
	 * A convenience method to change the currently shown iteration.
	 * 
	 * @param stepSize
	 *            Number of steps to go forward
	 */
	public void goForward(int stepSize) {
		int lastStep = getLastIteration();
		// check if the new iteration number exceeds the range
		if (currentIteration + stepSize > lastStep) {
			// if that is the case, set currentIteration to the last step
			currentIteration = lastStep;
			return;
		}

		currentIteration += stepSize;
	}

	/**
	 * A convenience method to change the currently shown iteration.
	 * 
	 * @param stepSize
	 *            Number of steps to go backward
	 */
	public void goBackward(int stepSize) {
		// check if the new iteration number exceeds the range
		if (currentIteration - stepSize < 1) {
			// if that is the case set currentIteration to first iteration
			currentIteration = 1;
			return;
		}

		currentIteration -= stepSize;
	}

	/**
	 * This method changes currently shown iteration to the first iteration.
	 */
	public void goToFirstIteration() {
		currentIteration = 1;
	}

	/**
	 * This method changes currently shown iteration to the last available
	 * iteration.
	 */
	public void goToLastIteration() {
		currentIteration = getLastIteration();
	}

	public void goToStep(int stepNumber) {
		assert stepNumber>0;
		assert stepNumber<=getLastIteration();
		currentIteration = stepNumber;
	}

	// Exporting of figures / tables
	/*
	 * Not implemented in prototype
	 * 
	 * public void exportImage() {
	 * 
	 * }
	 */

	// Loading and saving
	/*
	 * Not implemented in prototype
	 * 
	 * public void savePreprocessedInput() {
	 * 
	 * }
	 * 
	 * public void loadPreprocessedInput() {
	 * 
	 * }
	 */

	/**
	 * This method is called from the view to save generated results.
	 */
	public void saveCompleteResults() {
		try {
			logger.flush();
		} catch (IOException e) {
		}
	}

	/**
	 * This method is called from the view to load generated results.
	 */
	/*
	 * Not implemented in prototype public void loadCompleteResults() {
	 * 
	 * Logger.readLog(); }
	 */

	// Miscellaneous
	/**
	 * Getter for the last available iteration.
	 * 
	 * @return The number of the last available iteration
	 */
	public int getLastIteration() {
		// TODO The calculation of lastStep has to be adapted if the
		// computations are done by a separate task. This is due to the fact
		// that the last available iteration can differ from the number of
		// situations.
		return model.getSituations().size();
	}

	/**
	 * Getter for the currently shown iteration number.
	 * 
	 * @return The number of the currently shown iteration
	 */
	public int getCurrentIteration() {
		return currentIteration;
	}

	/**
	 * Convenience method to register an observer with all words.
	 * 
	 * @param o
	 *            An observer that is to be registered with the word objects.
	 */
	public void registerObserverWithWords(Observer o) {
		Iterator<Word> wordIterator = model.getWordTable().iterator();
		while (wordIterator.hasNext()) {
			wordIterator.next().addObserver(o);
		}
	}
	
	/**
	 * Method to get the Amount of appeared words, for the scaling of the Words Learned over Time graph.
	 * 
	 * @param iteration
	 * 				The iteration number until which should be searched.
	 * @return
	 * 				The amount of Words that appeared until the given iteration.
	 */
	
	public int getNumberOfAppearedWords(int iteration){
		Set<Word> words=new TreeSet();
		
		for(int i=0;i<iteration;i++){
			Situation sit=model.getSituations().get(i);
			for(Word w:sit.getUtterance()){
				words.add(w);
			}
		}		
		
		return words.size();
	}
}