package Controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

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

/**
 * This class implements the log file writer of the LtMoW tool.
 * 
 * @author Karsten Knuth
 * @version 1.0
 * 
 * <LOG FILE>
 * 	<SITUATIONS>
 *		<SITUATION>
 *			<UTTERANCE>
 *				<WORD string representation="Troll" frequency="0">
 *					<STATES>
 *						//  States not yet available
 *					</STATES>
 *					<ASSOCIATION SCORE>
 *						// empty since list is empty
 *					</ASSOCIATION SCORE>
 *				</WORD>
 *			</UTTERANCE>
 *			<SCENE>
 *				<MEANING>
 *					<STRING REPRESENTATION MEANING	"stringRe">
 *					<IMAGE	"image to string">
 *				</MEANING>
 *			</SCENE>
 *		</SITUATION>
 *		<SITUATION>
 *			.
 *			.
 *			.
 *		</SITUATIONS>
 *	</SITUATIONS>
 *	
 *	<PARAMETERS>
 *		<UTTERANCE FILE	"getUtteranceFile().toString()">
 *		<IMAGE DATA BASE	"getImageDB().toString()">
 *		<THRESHOLD		"getThreshold().toString()">
 *		<LOG FILE		"getLogFile().toString()">		
 *		<ADDITIVE NOISE	"getAdditiveNoise()">
 *		<SUBTRACTIVE NOISE	"getSubtractiveNoise()">
 *	</PARAMETERS>
 *	
 *	<WORDS>
 *		<WORD 	string representation="getStringRep()" frequency="x">
 *			<STATES>
 *				<ITERATION	" y">
 *					<MEANING MAP	"# of elements">
 *						<MEANING-VALUE>
 *							<MEANING>
 *								<STRING REPRESENTATION MEANING	"stringRe">
 *								<IMAGE	"image to string">
 *							</MEANING>
 *							<VALUE		"xx">
 *						</MEANING-VALUE>
 *					</MEANING MAP>
 *				</ITERATION>
 *				<ITERATION	"z">
 *					.
 *					.
 *					.
 *				</ITERATION>
 *			</STATES>
 *			<ASSOCIATION SCORE>
 *				// Leer, da Liste leer
 *			</ASSOCIATION SCORE>
 *		</WORD>
 *		<WORD>
 *			.
 *			.
 *			.
 *		</WORDS>
 *	</WORDS>
 *</LOG FILE>*/




public class LogWriter implements Observer {
	/**
	 * Attributes
	 */
	private File logFile_;
	private Document document_;
	private Element root_;
	
	/**
	 * Explicit constructor
	 * 
	 * @param logFile File where the results shall be saved
	 */
	public LogWriter(File logFile)
	{
		logFile_ = logFile;
		
		//build skeleton of the jdom tree and create jdom document
		//		<LOGFILE>
		//			<SITUATIONS></SITUATIONS>
		//			<WORDS></WORDS>
		//		</LOGFILE>
		//To simplify matters the PARAMETERS node is not added here, see createParameters(Parameters)
		root_ 				= new Element("LOGFILE");
		document_ 			= new Document(root_);
//		Element words 		= new Element("WORDS");
//		Element situations 	= new Element("SITUATIONS");
		//Element parameters 	= new Element("PARAMETERS");
		
		//root_.addContent(parameters);
//		root_.addContent(situations);
//		root_.addContent(words);
	}
	
	
	/**
	 * This method builds an PARAMETERS node/subtree for the jdom LOGFILE tree. It is ideally only called once per logfile
	 * 
	 * @param parameters Parameters object which shall be transformed into a tree
	 * 
	 * @return Element object corresponding to the argument
	 */
	private Element createParameters(Parameters parameters)
	{
		//create root node PARAMETERS
		Element ret = new Element("PARAMETERS");
		
		//create nodes corresponding to the attributes of the argument
		Element utteranceFile = new Element("UTTERANCE_FILE");
		Attribute it = new Attribute("utteranceFile_", parameters.getUtteranceFile().getAbsolutePath());
		utteranceFile.setAttribute(it);
		Element imageDB = new Element("IMAGE_DATA_BASE");
		Attribute jt = new Attribute("imageDB_", parameters.getImageDB().getAbsolutePath());
		imageDB.setAttribute(jt);
		Element threshold = new Element("THRESHOLD");
		Attribute tt = new Attribute("threshold_", ((Double)parameters.getThreshold()).toString());
		threshold.setAttribute(tt);
		Element logFile = new Element("LOG_FILE");
		Attribute vt = new Attribute("logFile_", parameters.getLogFile().getAbsolutePath());
		logFile.setAttribute(vt);
		Element additiveNoise = new Element("ADDITIVE_NOISE");
		Attribute wt = new Attribute("additiveNoise_", ((Double)parameters.getAdditiveNoise()).toString());
		additiveNoise.setAttribute(wt);
		Element subtractiveNoise = new Element("SUBTRACTIVE_NOISE");
		Attribute xt = new Attribute("subtractiveNoise_", ((Double)parameters.getSubtractiveNoise()).toString());
		subtractiveNoise.setAttribute(xt);
		
		//merge tree
		ret.addContent(utteranceFile);
		ret.addContent(imageDB);
		ret.addContent(threshold);
		ret.addContent(logFile);
		ret.addContent(additiveNoise);
		ret.addContent(subtractiveNoise);
		
		//return the complete PARAMETERS tree
		return ret;
	}
		
	/**
	 * This method builds a jdom tree that represents the Situation object given as argument.
	 * 
	 * @param situation Situation which is supposed to be modeled
	 * 
	 * @return tree representation of the argument
	 */
	private Element createSituation(Situation situation)
	{
		//create root element
		Element ret = new Element("SITUATION");
		
		//create nodes corresponding to the attributes of the argument
		Element utterance = new Element("UTTERANCE");
		Element scene = new Element("SCENE");
		
		// for all words in this situation add an WORD subtree to the UTTERANCE node
		for(Word w : situation.getUtterance()){
			utterance.addContent(createWordElement(w));
		}
		
		// for all meanings in the scene of the situation add a MEANING subtree to the SCENE node
		for(Meaning m : situation.getScene()){
			scene.addContent(createMeaningElement(m));
		}
		
		// add UTTERANCE & SCENE trees to the SITUATION node
		ret.addContent(utterance);
		ret.addContent(scene);
		
		//return the complete SITUATION tree
		return ret;
	}
	
	/**
	 * This method builds a jdom tree that represents the Meaning object given as argument.
	 * 
	 * @param m Meaning which is supposed to be modeled
	 * 
	 * @return tree representation of the argument
	 */
	private Element createMeaningElement(Meaning m)
	{
		//create root element
		Element ret = new Element("MEANING");
		
		//build nodes corresponding to the attributes of the argument
		Element stringRep	= new Element ("STRING_REPRESENTATION_MEANING");
		Attribute it = new Attribute("StringRepresentation", m.getStringRep());
		stringRep.setAttribute(it);
		Element image;
		if (m.getImage() != null) {
			image = new Element ("IMAGE");
			Attribute at = new Attribute("Image", m.getImage().toString());
			image.setAttribute(at);
		} else {
			image = new Element ("IMAGE");
			//Kein Content
		}
		
		//add STRING REPRESENTATION MEANING & IMAGE nodes to the root node
		ret.addContent(stringRep);
		ret.addContent(image);
		
		//return the complete MEANING tree
		return ret;
	}
	
	/**
	 * This method builds a jdom tree that represents a Map<Meaning,Double> object given as argument.
	 * 
	 * @param map Map<Meaning,Double> which is supposed to be modeled
	 * 
	 * @return tree representation of the argument
	 */
	private Element createMeaningMapElement(Map<Meaning,Double> map)
	{
		//create root element of the MEANING MAP tree, add number of elements in the map as content
		Element ret = new Element("MEANING_MAP");
		Attribute it = new Attribute("Size", ((Integer)map.size()).toString());
		ret.setAttribute(it);
		
		// for all keys(Meaning objects) in this map
		for(Meaning k : map.keySet()){
			//create a top node MEANING - VALUE
			Element mealue = new Element("MEANING--VALUE");
			
			//create a node VALUE representing the double value of the map
			Element meaningValue = new Element("VALUE");
			Attribute ot = new Attribute("Value", ((Double)map.get(k)).toString());
			meaningValue.setAttribute(ot);
			//add a MEANING tree to the MEANING-VALUE node
			mealue.addContent(createMeaningElement(k));
			//add the VALUE node to the MEANING-VALUE node
			mealue.addContent(meaningValue);
			
			//add the MEANING-VALUE tree to the MEANING MAP node
			ret.addContent(mealue);
		}
		
		//return the complete MEANING MAP tree
		return ret;
	}
	
	/**
	 * This method builds a jdom tree that represents a Map<Meaning,Double> object given as argument.
	 *  
	 * @param map Association map that is supposed to be modeled
	 * 
	 * @return tree representation of the argument
	 */
	private Element createAssociationMapElement(Map<Meaning,Double> map)
	{
		//create root element ASSOCIATION MAP
		Element ret = new Element("ASSOCIATION_MAP");
		Attribute it = new Attribute("Size", ((Integer)map.size()).toString());
		ret.setAttribute(it);
		
		//for all keys (Meanings) 
		for(Meaning k : map.keySet())
		{
			//create top node MEANING - VALUE
			Element mealue = new Element("MEANING_-_VALUE");
			
			//create node VALUE with the corresponding value to the key k
			Element meaningValue = new Element("VALUE");
			Attribute ot = new Attribute("Value", ((Double)map.get(k)).toString());
			meaningValue.setAttribute(ot);
			//add MEANING node corresponding to key k to the top node
			mealue.addContent(createMeaningElement(k));
			//add VALUE node to MEANING-VALUE
			mealue.addContent(meaningValue);
			
			//add MEANING-VALUE to ASSOCIATION MAP node
			ret.addContent(mealue);
		}
		
		//return complete tree
		return ret;
	}
	

	/**
	 * This method builds a jdom tree that represents a Word object given as argument.
	 * 
	 * @param word Word that is supposed to be modeled
	 * 
	 * @return tree representation of the argument
	 */
	private Element createWordElement(Word word){
		//create root node WORD
		Element ret = new Element("WORD");
		
		//build attributes corredponding to the arguments attributes stringRep_ & frequency_ since they are of no further depth
		Attribute stringRep = new Attribute("STRING_REPRESENTATION",word.getStringRep());
		Attribute frequency = new Attribute("FREQUENCY", ((Integer)word.getFrequency()).toString());
		
		//add these attributes to WORD
		ret.setAttribute(stringRep);
		ret.setAttribute(frequency);
		
		//create nodes for the attributes states_ & associationScore_ of the argument since they have a deeper structure
		Element states=				new Element("STATES");
		
		//see loops in earlier declared methods
		for(int k : word.getStates().keySet()){
			Element state = new Element("STATE");
			Attribute it = new Attribute("Iteration", ((Integer)k).toString());
			state.setAttribute(it);
			state.addContent(createMeaningMapElement(word.getStates().get(k)));
			states.addContent(state);
		}
		
		//see loops in earlier declared methods
		
		Element assScore = new Element("ASSOC.SCORE");
		assScore.addContent(createAssociationMapElement(word.getAssociationScore()));			
		
		//add STATES & ASSOCIATION SCORE to the WORD node
		ret.addContent(states);
		ret.addContent(assScore);
				
		//return complete WORD tree
		return ret;
	}
	
	/**
	 * This methods writes the XML representation of the jdom tree to the log file.
	 * 
	 * @throws IOException
	 */
	public void flush() throws IOException
	{
		XMLOutputter outputter = new XMLOutputter();
		outputter.setFormat(Format.getPrettyFormat());
		FileOutputStream outputStream = new FileOutputStream(logFile_);
		outputter.output(document_, outputStream);
	}
	
	/**
	 * This method builds the complete tree that is supposed to be outputted later.
	 * 
	 * @param m Model that is supposed to be modeled
	 */
	public void buildTree(Model m)
	{		
		//add PARAMETERS tree to the second positions of LOGFILES children
		root_.addContent(createParameters(m.getParameters()));
		
		Element situ = new Element("SITUATIONS");
		Element words = new Element("WORDS");
		
		root_.addContent(situ);
		root_.addContent(words);
		
		//add situations to the tree
		for (Situation s : m.getSituations()){
			root_.getChild("SITUATIONS").addContent(createSituation(s));
		}
		
		//add all words to the WORDS node
		for (Word w : m.getWordTable()){
			root_.getChild("WORDS").addContent(createWordElement(w));
		}
	}
	
	/**
	 * Inherited update mehtod
	 * 
	 * @param o
	 */
	public void update(Observable o, Object arg)
	{
		Word w;
		//o must be of type WORD to continue
		if (o instanceof Word)
		{
			w = (Word)o;
		}
		else
			{
			return;
			}
		
		//remove the WORDS subtree from LOGFILE
		root_.getChild("WORDS").removeChild(w.getStringRep());
		//add updated WORDS subtree to LOGFILE
		root_.getChild("WORDS").addContent(createWordElement(w));
	}
}
