package Model;

import java.util.*;

/**
 * This class represents a word in the LtMoW context. It is used for computation and representation in the GUI.
 *
 */
public class Word extends Observable implements Comparable{

	//Atributes
	private String stringRep_;
	private int frequency_;
	private HashMap<Integer, HashMap<Meaning,Double>> states_;
	
	//private variable used by the algorithm
	private HashMap<Meaning, Double> associationScore_;
	//private variable used by the algorithm
	private double assocSum;
	
	/**
	 * Explicit constructor
	 * 
	 * @param a
	 * 			String representation of the word
	 */
	public Word(String a){
		stringRep_ = a;
		states_ = new HashMap<Integer, HashMap<Meaning,Double>>();
		associationScore_ = new HashMap<Meaning,Double>();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object other){
		if ( other == null )
		{
			return false;
		}	
		else 
		{
			if ( other instanceof Word )
			{
				boolean str = stringRep_.equals(((Word)other).getStringRep());
				boolean fre = (frequency_ ==(((Word)other).getFrequency()));
				boolean sta = states_.equals(((Word)other).getStates());
				boolean ass = associationScore_.equals(((Word)other).getAssociationScore());
				return (str && fre && sta && ass);
			}	
			else
			{
				return false;
			}
		}
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode(java.lang.Object)
	 */
	public int hashCode() 
	{
	      int hashString = stringRep_ != null ? stringRep_.hashCode() : 0;
	      int hashStates = states_ != null ? states_.hashCode() : 0;
	      int hashAScore = associationScore_ != null ? associationScore_.hashCode() : 0;
	      
	      int hashFirst  = (hashString + frequency_) * frequency_ + hashString;
	      int hashSecond = (hashStates + hashAScore) * hashAScore + hashStates;

	      return (hashFirst + hashSecond) * hashSecond + hashFirst;
	}
	
	/**
	 * Method to get the "last" state of the states_ attribute
	 * 
	 * @return a pair of an integer representing the # of the last iteration and the corresponding HashMap from Meaning to their probabilities
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Pair<Integer,HashMap<Meaning, Double>> getLastState(){
		int size = states_.size();
		if (size == 0){
			return null;
		}
		Set<Integer> keySet = states_.keySet();
		Object[] keys = keySet.toArray(); 
		int k = (Integer)keys[0];
		// keys[0] muss es geben, da sonst null im ersten if zur�ckgegeben wird
		for (int i=0; i<size; i++){
			if (((Integer)keys[i])>=k){
				k=((Integer)keys[i]);
			}
		}
		return (new Pair(k,states_.get(k)));
	}
	
	/**
	 * Method to put a state with a specific key into states_. See documentation of HASHMAP.put
	 * 
	 * @param iteration
	 * @param state
	 */
	public void setState(int iteration, HashMap<Meaning,Double> state){
		states_.put(iteration,state);
		if (!this.stringRep_.equals("dummy_01")){
			this.setChanged();
			this.notifyObservers();
		}
	}
	
	/**
	 * Method which returns the last score value of the Meaning parameter
	 * 
	 * @param m
	 * 			Meaning for which you want to know its association score
	 * @return the current association score of this word with meaning m
	 */
	public double getLastAssociationScore(Meaning m){
		if (!associationScore_.containsKey(m)){
			return 0;
		}
		double score = associationScore_.get(m);
		return score;
	}
	
	/**
	 * 
	 * @param iteration The current iteration number. If the word has not appered in the current iteration the last iteration it has appeard is computed
	 * @return a pair of the meaning with the highest probability and the probability of the word
	 */
	public Object[] getHighestMeaning(int iteration){
		int lastIter = lastIteration(iteration);
		if (!states_.containsKey(lastIter)){
			return new Object[]{new String(), 0};
		}
		HashMap<Meaning, Double> state = states_.get(lastIter);
		Iterator<Meaning> it = state.keySet().iterator();
		double pMax = 0;
		Meaning mMax = null;
		while (it.hasNext()){
			Meaning m = it.next();
			if (state.get(m) >= pMax){
				pMax = state.get(m);
				mMax = m;
			}
		}
		return new Object[]{mMax, pMax};
	}

	/**
	 * Method to find the last iteration in which this word occurred before the iteration given as parameter.
	 * 
	 * @param iteration strictly upper bound for computation
	 * @return the last iteration this word has occurred in before the iteration given as parameter
	 */
	public int lastIteration(int iteration){
		Set<Integer> keySet = states_.keySet();
		List<Integer> keys = new LinkedList<Integer>(keySet);
		Collections.sort(keys);
		
		int lastIter = 0;
		Iterator<Integer> it = keys.iterator();
		while(it.hasNext()){
			Integer iter = it.next();
			if (iter <= iteration){
				lastIter = iter;
			}
		}
		return lastIter;
	}
	
	/**
	 * Method to determine frequency up to the iteration given as parameter.
	 * 
	 * @param iteration
	 * 			upper bound for the computation
	 * @return	frequency up to "iteration"
	 */
	public int getCurrentFrequency(int iteration){
		Set<Integer> keySet = states_.keySet();
		List<Integer> keys = new LinkedList(keySet);
		Collections.sort(keys);
		int freq = 0;
		Iterator<Integer> it = keys.iterator();
		while(it.hasNext()){
			Integer iter = it.next();
			if (iter <= iteration){
				freq++;
			}
			else{
				return freq;
			}
		}
		return freq;
	}
	
	/**
	 * See put method of HASHMAP
	 * 
	 * @param m
	 * @param score
	 */
	public void setAssociationScore(Meaning m, Double score){
		associationScore_.put(m, score);
	}
	
	//GETTER
	
	public String getStringRep(){
		return stringRep_;
	}
	
	public int getFrequency(){
		return frequency_;
	}
	
	public HashMap<Integer, HashMap<Meaning,Double>> getStates(){
		return states_;
	}
	
	public HashMap<Meaning,Double> getAssociationScore(){
		return associationScore_;
	}
	
	public double getAssocSum() {
		return assocSum;
	}

	public void setAssocSum(double assocSum) {
		this.assocSum = assocSum;
	}
	
	//SETTER
	
	public void setStringRep(String a){
		stringRep_ = a;
	}
	
	public void setFrequency(int a){
		frequency_ = a;
	}
	
	public void setStates(HashMap<Integer, HashMap<Meaning,Double>> a){
		states_ = a;
	}
	
	/**
	 * Method which returns the string representation of this Word object
	 */
	public String toString() {
		return getStringRep();
	}


	@Override
	public int compareTo(Object anotherWord) {
		if (!(anotherWord instanceof Word))
			throw new ClassCastException("A Word object was expected.");
		String anotherStringRep=((Word) anotherWord).getStringRep();
		return this.getStringRep().compareTo(anotherStringRep);
	}
}
