/*
 * Copyright (C) 2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 *
 * This file is part of WSDDE.
 *
 * WSDDE is free software: it may be distributed and/or modified under
 * the terms of the GNU General Public License version 3 as published
 * by the Free Software Foundation and appearing in the file doc/gpl.txt
 * included in the packaging of this file.
 *
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */


package wsdde;


import java.io.IOException;

import java.io.StringWriter;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Random;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import wsdde.general.DatabaseHelper;
import wsdde.general.Utils;


public class ExperimentDescription {


	
	Document inputDocument;
	Document outputDocument;
	
	int maxMethod;

	String maxMethodTime;
	
	int allMethodsNum;
	int paramCombinationNum;
	
	int paramsNum;
	int paramOptionsTable [];
	int paramTmpTable [];
	
	
	/**
	 * Constructor for ExperimentDescription Class
	 * 
	 * @param doc input xml document with meta description
	 */
	public ExperimentDescription(Document doc){
		inputDocument = doc;
	}
	
	/**
	 * Counts all possibilities of configurations of wsd experiments. Sets object
	 * variables corresponding to parameters and their combinations.
	 * 
	 * 
	 * @return number of possible configurations of experiment parameters
	 */
	private int countConfigurations(){
		int selectionsNum = inputDocument.getElementsByTagName("selection").getLength();
		int machineLearningNum = inputDocument.getElementsByTagName("machine_learning").getLength();
		int corporaNum = inputDocument.getElementsByTagName("corpus").getLength();
		NodeList nl = inputDocument.getElementsByTagName("param");
		paramsNum = nl.getLength();
		paramOptionsTable = new int[nl.getLength()];
		paramTmpTable = new int[nl.getLength()];
		paramCombinationNum = 1;
		for(int i = 0; i < nl.getLength(); i++){
			int tmpOpt = 0;
			tmpOpt = ((Element)nl.item(i)).getElementsByTagName("param_value").getLength();
			paramTmpTable[i] = 1;
			paramOptionsTable[i] = tmpOpt;
			for(int j = 0; j < i; j++){
				paramTmpTable[j] = paramTmpTable[j]*tmpOpt;
			}
			paramCombinationNum = paramCombinationNum * tmpOpt;
		}
		int ret = selectionsNum * machineLearningNum * corporaNum * paramCombinationNum;

		allMethodsNum = ret;
		return ret;
	}
	/**
	 * Method obtains configuration of parameters 
	 * 
	 * @param n a number of configurations to be obtained
	 * 
	 * @return configuration of parameters based of input number
	 */
	private Integer[] getParamIndexesTable(int n){
		Integer [] ret = new Integer[paramsNum];
		for(int i = 0; i < paramsNum; i++){
			ret[i] = (n / paramTmpTable[i]);
			n = n % paramTmpTable[i];
		}
		return ret;
	}
	
	private void getAllCombinations(){
		Node wsdExperiment = outputDocument.getElementsByTagName("wsd_experiment").item(0);
		NodeList corporas = inputDocument.getElementsByTagName("corpus");
		NodeList selections = inputDocument.getElementsByTagName("selection");
		NodeList mls = inputDocument.getElementsByTagName("machine_learning");
		Element fGenerators = (Element)inputDocument.getElementsByTagName("feature_generators").item(0);
		Vector<Element> featureGensVector = Utils.getChildElementsVector(fGenerators);
//		NodeList featureGens = inputDocument.getElementsByTagName("feature_generator");
		int methodNum = 0;
		for(int i = 0; i < corporas.getLength(); i++){
			for(int j = 0; j < selections.getLength(); j++){
				for(int k = 0; k < mls.getLength(); k++){
					for(int l = 0; l < 	paramCombinationNum; l++){
						methodNum++;
						Node wsd = outputDocument.createElement("wsd_method");						
						((Element)wsd).setAttribute("id", Integer.toString(methodNum));
						Element maxMethodTimeElement = outputDocument.createElement("max_method_time");
						maxMethodTimeElement.setTextContent(maxMethodTime);
						wsd.appendChild(maxMethodTimeElement);
						wsd.appendChild(outputDocument.importNode(cleanNodeProperties(corporas.item(i)), true));
						wsd.appendChild(outputDocument.importNode(cleanNodeProperties(selections.item(j)), true));
						wsd.appendChild(outputDocument.importNode(cleanNodeProperties(mls.item(k)), true));
						Integer [] tmp = getParamIndexesTable(l);
						int p = 0; // numer parametru;
						Node featureGenerators = outputDocument.createElement("feature_generators");
	//					for(int m = 0; m < featureGens.getLength(); m++){
						for(int m = 0; m < featureGensVector.size(); m++){
//							Element featureGenerator = outputDocument.createElement("feature_generator");
//							Element fg = (Element)featureGens.item(m);
							Element fg = featureGensVector.get(m);
							Element featureGenerator = outputDocument.createElement(fg.getNodeName());
							NodeList parameters = fg.getElementsByTagName("param");
							for(int n = 0; n < parameters.getLength(); n++){
								Element parameter = outputDocument.createElement("param");
								Element inParameter = (Element) parameters.item(n);
								parameter.setAttribute("name", inParameter.getAttribute("name"));
								parameter.setAttribute("value", ((Element)inParameter.getElementsByTagName("param_value").item(tmp[p])).getAttribute("value"));
								featureGenerator.appendChild(parameter);
								p++;
							}
							
							featureGenerators.appendChild(featureGenerator);
						}
						wsd.appendChild(featureGenerators);
						wsdExperiment.appendChild(wsd);
					}
				}
			}
		}
	}
	
	
	
	private void getProbCombinations() {
		Node wsdExperiment = outputDocument.getElementsByTagName("wsd_experiment").item(0);
		NodeList corporas = inputDocument.getElementsByTagName("corpus");
		NodeList selections = inputDocument.getElementsByTagName("selection");
		NodeList mls = inputDocument.getElementsByTagName("machine_learning");
		Element fGenerators = (Element)inputDocument.getElementsByTagName("feature_generators").item(0);
		Vector<Element> featureGensVector = Utils.getChildElementsVector(fGenerators);		
//		NodeList featureGens = inputDocument.getElementsByTagName("feature_generator");
		HashSet<String> hs = new HashSet<String>();
		int i = 0;
		while(i < maxMethod){
			Node wsd = outputDocument.createElement("wsd_method");
			((Element)wsd).setAttribute("id", Integer.toString(i+1));
			int corpIndex = (i*corporas.getLength())/maxMethod; 
			int selectionIndex = chooseNode(selections);
			int mlsIndex = chooseNode(mls);
			Element maxMethodTimeElement = outputDocument.createElement("max_method_time");
			maxMethodTimeElement.setTextContent(maxMethodTime);
			wsd.appendChild(maxMethodTimeElement);
			wsd.appendChild(outputDocument.importNode(cleanNodeProperties(corporas.item(corpIndex)), true));
			wsd.appendChild(outputDocument.importNode(cleanNodeProperties(selections.item(selectionIndex)), true));
			wsd.appendChild(outputDocument.importNode(cleanNodeProperties(mls.item(mlsIndex)), true));			
			String checkString = Integer.toString(corpIndex) +
					";" + Integer.toString(selectionIndex) +
					";" + Integer.toString(mlsIndex) +
					";";
			Node featureGenerators = outputDocument.createElement("feature_generators");
//			for(int j = 0; j < featureGens.getLength(); j++){
			for(int j = 0; j < featureGensVector.size(); j++){			
//				Element featureGenerator = outputDocument.createElement("feature_generator");
//				Element fg = (Element)featureGens.item(j);
				Element fg = featureGensVector.get(j);
				Element featureGenerator = outputDocument.createElement(fg.getNodeName());
				NodeList parameters = fg.getElementsByTagName("param");
				for(int k = 0; k < parameters.getLength();k++){
					Element parameter = outputDocument.createElement("param");
					Element inParameter = (Element) parameters.item(k);
					parameter.setAttribute("name", inParameter.getAttribute("name"));
					NodeList paramValues = inParameter.getElementsByTagName("param_value");
					int paramIndex = chooseNode(paramValues);
					parameter.setAttribute("value", ((Element)paramValues.item(paramIndex)).getAttribute("value"));
					featureGenerator.appendChild(parameter);
					checkString = checkString + Integer.toString(paramIndex) + ";";
				}
				featureGenerators.appendChild(featureGenerator);				
			}			
			wsd.appendChild(featureGenerators);
		//	i++;
		//	wsdExperiment.appendChild(wsd);
			if(hs.add(checkString)){
			wsdExperiment.appendChild(wsd);
				i++;
			}			
		}
		return;
	}


	private int chooseNode(NodeList nl){
		int [] tab = new int[nl.getLength()];
		for(int i = 0; i < nl.getLength(); i++){
			tab[i] = Integer.parseInt(((Element)nl.item(i)).getAttribute("distrib"));
		}
		int sum = 0;
		for(int i = 0; i < tab.length; i++){
			sum += tab[i];
		}
		Random generator = new Random();
		int rand = generator.nextInt(sum);
		for(int i = 0; i < tab.length; i++){
			if (rand < tab[i]){
				return i;
			}
			rand -= tab[i];
		}
		// never here
		return 0;

	}
	/**
	 * @param n a node to be cleaned
	 * 
	 * @return returns a copy of node with removed specific attributes
	 */
	private Element cleanNodeProperties(Node n){
		Element e = (Element) n.cloneNode(true);
		e.removeAttribute("distrib");
		return e;
//		
	}


	/**
	 * 
	 * 
	 * @throws TransformerFactoryConfigurationError 
	 * @throws ParserConfigurationException 
	 * @throws Exception 
	 */
	private void generateDocumentFromMetaDescription()throws TransformerFactoryConfigurationError, ParserConfigurationException{	
		DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
		outputDocument = docBuilder.newDocument();
		Element wsdExperiment = outputDocument.createElement("wsd_experiment");
		String wsdId = ((Element)inputDocument.getElementsByTagName("experiment_description").item(0)).getAttribute("id");
		wsdExperiment.setAttribute("id", wsdId);
		String tableName = ((Element)inputDocument.getElementsByTagName("experiment_description").item(0)).getAttribute("table_name");
		wsdExperiment.setAttribute("table_name", tableName);
		String wsdOverrideFlag = ((Element)inputDocument.getElementsByTagName("experiment_description").item(0)).getAttribute("override");
		wsdExperiment.setAttribute("override", wsdOverrideFlag);
		
		
		maxMethod = Integer.valueOf(inputDocument.getElementsByTagName("max_method").item(0).getTextContent());
		maxMethodTime = inputDocument.getElementsByTagName("max_method_time").item(0).getTextContent();
//		wsdExperiment.appendChild(outputDocument.importNode(inputDocument.getElementsByTagName("max_method_time").item(0),true));
//		wsdExperiment.appendChild(outputDocument.importNode(inputDocument.getElementsByTagName("max_memory").item(0),true));
		outputDocument.appendChild(wsdExperiment);				
	
		countConfigurations();
		if(maxMethod >= allMethodsNum){
			getAllCombinations();
		}
		else {
			getProbCombinations();
		}
		
		return;

	}

	public static String getNewExperimentDBDescritpionFromXMLFile(String inputFile){
		try{
			Document inputDoc = Utils.getXMLDocFromFile(inputFile);
			String tableName = inputDoc.getElementsByTagName("table_name").item(0).getTextContent();
			int exampleNum = Integer.parseInt(inputDoc.getElementsByTagName("best_limit").item(0).getTextContent());
			int generatedNum = Integer.parseInt(inputDoc.getElementsByTagName("new_exp_per_fgenerator").item(0).getTextContent());
			Document outputDoc = DatabaseHelper.getNewExperimentDescription(tableName, exampleNum, generatedNum);
			Element expOut = (Element)outputDoc.getElementsByTagName("wsd_experiment").item(0);
			Element expIn = (Element)inputDoc.getElementsByTagName("description").item(0);
			expOut.setAttribute("id", expIn.getAttribute("id"));
			expOut.setAttribute("override", "override");
			return Utils.stringFromXML(outputDoc);
		}
		catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	
	/**
	 * Generates xml with description of wsd experiments based on xml input file with 
	 * meta description of all experiments. 
	 * 
	 * @param xml_meta_description_file_name - name of file containing meta description for 
	 * wsd experiments
	 * 
	 * @return a String with XML that contains descriptions of wsd experiments generated
	 * based on xml meta description file.
	 * @throws Exception 
	 */
	public static String getXMLFromMetaDescription(String xml_meta_description_file_name) {
		try {
/*			DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
			Document doc;
			doc = docBuilder.parse(xml_meta_description_file_name);//.newDocument();
	*/
			Document doc = Utils.getXMLDocFromFile(xml_meta_description_file_name);
			
			
			ExperimentDescription experimentDescription = new ExperimentDescription(doc);
			experimentDescription.generateDocumentFromMetaDescription();
			
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");

			//initialize StreamResult with File object to save to file
			StreamResult result = new StreamResult(new StringWriter());
			DOMSource source = new DOMSource(experimentDescription.outputDocument);
			transformer.transform(source, result);

			String xmlString = result.getWriter().toString();
			
			return xmlString;
			
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}
		return "";
	
	}
	
	
	
	
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		String outputXml = getXMLFromMetaDescription("experiment_meta_description_m.xml");
		//outputXml = "żółw";
		Utils.saveInFile(outputXml, "gen_from_meta.xml");
		System.out.print(outputXml);
		System.out.print("END");
		// TODO Auto-generated method stub

	}

}
