package net.mmberg.nadia.processor.lg.qg;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;

import net.mmberg.nadia.processor.NadiaProcessor;
import net.mmberg.nadia.processor.NadiaProcessorConfig;
import net.mmberg.nadia.processor.dialogmodel.aqd.AQD;
import net.mmberg.nadia.processor.lg.qg.interrogatives.*;

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

import opennlp.ccg.realize.*;
import opennlp.ccg.synsem.LF;
import opennlp.ccg.grammar.*;

public class Generator {

	private static Generator instance;
	private Grammar grammar;
	private Realizer realizer;
	private boolean print=false;
	private List<InterroElem> interrogatives;
	private Lexicon lex;
	private int generation_number=0;
    private static Logger logger=NadiaProcessor.getLogger();
	
	private class InterroElem{
		public List<String> applicable_types;
		public Interrogative interrogative;
		public List<Object> params;
		public float politeness;
		
		public InterroElem(List<String> applicable_types, Interrogative interrogative, List<Object> params, float politeness){
			this.applicable_types=applicable_types;
			this.interrogative=interrogative;
			this.params=params;
			this.politeness=politeness;
		}
	}

	public static Generator getInstance(){
		if(instance==null){
			try{
				NadiaProcessorConfig config=NadiaProcessorConfig.getInstance();
				instance=new Generator(new URL(config.getProperty(NadiaProcessorConfig.CCGGRAMMARPATH)), new URL(config.getProperty(NadiaProcessorConfig.ONTOLOGYPATH)));
			}
			catch(MalformedURLException ex){
				ex.printStackTrace();
			}
		}
		return instance;
	}

	private Generator(URL grammarURL, URL ontologyURL){
		this.init(grammarURL, ontologyURL);
	}
	
	private void init(URL grammarURL, URL ontologyURL) {
		try {
			grammar = new Grammar(grammarURL);
			realizer = new Realizer(grammar);
			lex = new Lexicon(ontologyURL);
								
			//SAXBuilder builder = new SAXBuilder();
			//Document lfxml = builder.build("./res/7/nowtellme"); //test3
			
			
			//the list of interrogatives is dependent on the question type (a y/n-type cannot be asked as a wh-question) 
			
			//List of Interrogatives with parameters (politeness,subj) and associated politeness score
			interrogatives=new ArrayList<InterroElem>();
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new N(), Arrays.asList((Object)false,false),-2));

			interrogatives.add(new InterroElem(Arrays.asList("fact"), new NRequest(), Arrays.asList((Object)false,false),-1));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new WhRequest(), Arrays.asList((Object)false,false),-1));

			interrogatives.add(new InterroElem(Arrays.asList("fact"), new N(), Arrays.asList((Object)true,false),0));
			
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new NRequest(), Arrays.asList((Object)true,false),1));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new WhRequest(), Arrays.asList((Object)true,false),1));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new WhQuestion(), Arrays.asList((Object)false,false),2));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanWhQuestion(), Arrays.asList((Object)false,false),3));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanNQuestion(), Arrays.asList((Object)false,false),3));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanWhQuestion(), Arrays.asList((Object)true,false),4));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanNQuestion(), Arrays.asList((Object)true,false),4));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanWhQuestion(), Arrays.asList((Object)false,true),4));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanNQuestion(), Arrays.asList((Object)false,true),4));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanWhQuestion(), Arrays.asList((Object)true,true),5));
			interrogatives.add(new InterroElem(Arrays.asList("fact"), new CanNQuestion(), Arrays.asList((Object)true,true),5));
			//decision:
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new N(), Arrays.asList((Object)false,false),-2));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new N(), Arrays.asList((Object)true,false),-1));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),0));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),1));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),2));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),3));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),4));
			interrogatives.add(new InterroElem(Arrays.asList("decision"),new YNQuestion(), Arrays.asList((Object)false,false),5));

			//TODO
			//wh-n-qu:
			//what's your destination?
			//wh-ing:
			//where are you leaving from?
			//where are you heading to?
			//ellipses:
			//...and your destination?
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	/**
	 * The type describes the top level of the question type hierarchy (e.g., fact, decision)
	 * @param type
	 * @param politeness
	 * @return
	 */
	private ArrayList<InterroElem> getInterroElemsbyTypeAndPoliteness(String type, int politeness){
		String basetype=type.split("\\.")[0];
		ArrayList<InterroElem> elems=new ArrayList<InterroElem>();
		for(InterroElem elem : interrogatives){
			if(elem.applicable_types.contains(basetype) && elem.politeness==politeness) elems.add(elem);
		}
		return elems;
	}
	

	public String generateQuestion(Interrogative type, WordConf wconf, boolean opener, boolean sayPlease, boolean subj){
		return realize(type.createLF(wconf.getWhWord(), wconf.getVerb(), wconf.getNoun(), opener, sayPlease, subj), type.getPunctuation());
	}
	
	public WordConf chooseWords(String dimension, String specification, String referent, int formality){
		
		logger.info("searching words for: "+ dimension + ", "+specification+", "+referent+", "+formality);
		
		String[] dimArr;
		if(dimension.contains(".")){
			dimArr=dimension.split("\\.");
			//if(dimArr.length==2) dimArr=new String[]{dimArr[1], dimArr[1]};
		}
		else{
			dimArr=new String[]{dimension}; //dimArr=new String[]{dimension, dimension};
		}
		
		String d1= (dimArr.length>1)?dimArr[dimArr.length-2]:dimArr[0];
		String d2= (dimArr.length>1)?dimArr[dimArr.length-1]:dimArr[0];
			
		String wh_word=lex.getLex(d1, specification, referent, "whadv", formality).get(0);
		String verb=lex.getLex(d1, specification, referent, "v", formality).get(0);
		String noun=lex.getLex(d2, specification, referent, "n", formality).get(0);
		
		logger.info("found: wh="+wh_word +", v="+verb+", n="+ noun);
		
		/*
		String wh_word=lex.getLex(dimArr[dimArr.length-2], specification, referent, "whadv", formality).get(0);
		String verb=lex.getLex(dimArr[dimArr.length-2], specification, referent, "v", formality).get(0);
		String noun=lex.getLex(dimArr[dimArr.length-1], specification, referent, "n", formality).get(0);
		*/
		
		return new WordConf(wh_word, verb, noun);
	}
	
	public String generateParaphrase(GenConf conf){
		return generateParaphrase(conf.getType(), conf.getWhWord(), conf.getVerb(), conf.getNoun(), conf.getFormality(), conf.getPoliteness(), conf.isOpener());
	}
	
	public ArrayList<String> generateParaphrases(int number, GenConf conf){
		return generateParaphrases(number, conf.getType(), conf.getWhWord(), conf.getVerb(), conf.getNoun(), conf.getFormality(), conf.getPoliteness(), conf.isOpener());
	}
	
	private ArrayList<String> generateParaphrases(int n, String dimension, String wh_word, String verb, String noun, int formality, int politeness, boolean opener){
		
		ArrayList<String> candidates= new ArrayList<String>();
		
		//politeness:
		// - say please 
		// - (modal particles? -> could you PERHAPS / POSSIBLY ...)
		// - command > question > indirect question / subj (sentence structure)
		//formality:
		// - choice of words (context: academic, party, written/spoken...)
		// - T/V (social distance)
		
		ArrayList<InterroElem> elems=getInterroElemsbyTypeAndPoliteness(dimension, politeness);
		for(int i=0; i<n && i<elems.size(); i++){
			InterroElem elem=elems.get(i);
			candidates.add(
					realize(elem.interrogative.createLF(wh_word, verb, noun, opener, (Boolean)elem.params.get(0), (Boolean)elem.params.get(1)),elem.interrogative.getPunctuation()));
		}
			
		return candidates;
	}
	
	private String generateParaphrase(String dimension, String wh_word, String verb, String noun, int formality, int politeness, boolean opener){
		ArrayList<InterroElem> elems=getInterroElemsbyTypeAndPoliteness(dimension, politeness);
		InterroElem elem=elems.get(0);	
		return realize(elem.interrogative.createLF(wh_word, verb, noun, opener, (Boolean)elem.params.get(0), (Boolean)elem.params.get(1)), elem.interrogative.getPunctuation());
		
		//TODO return object instead of string, need information which class created phrase in order to prevent repetitive styles
	}
	
	private String realize(Element lf_xml, String punctuation){
						
		Document lf_doc = createDoc(lf_xml);
		if(lf_doc==null) logger.warning("Logical Form Document could not be created");
		
		LF lf=null;
		try{
			////there seems to be a bug in grammar.loadLF() that produces a Malformed URL Exception when run on Tomcat
			lf = grammar.loadLF(lf_doc); //lf = Realizer.getLfFromDoc(lfxml); <-- this example from literature is wrong!
			if(lf==null) throw new Exception("Logical Form is null.");
		}
		catch(Exception ex1){
			logger.warning("Error while loading logical form with grammar '"+grammar.getName()+"'. Content is: ");
			XMLOutputter serializer= new XMLOutputter (Format.getPrettyFormat());
			try {
				serializer.output(lf_doc, System.out);
			} catch (IOException e) {
				e.printStackTrace();
			}
			ex1.printStackTrace();
		}
			
		if(print){
			System.out.println("Realizing:\r\n"+lf.prettyPrint("")); //don't know what the argument is for
			
			XMLOutputter serializer= new XMLOutputter (Format.getPrettyFormat());
			try {
				serializer.output(lf_doc, System.out);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		Edge bestEdge = realizer.realize(lf);
		if(bestEdge==null || bestEdge.getSign()==null){
			logger.warning("An error occured while realizing the utterance from the logical form.");
		}
		
		return bestEdge.getSign().getOrthography()+punctuation;

	}
	
	private Document createDoc(Element lf){
		Document lfxml=new Document();
		Element xml=new Element("xml");
		Element lfnode=new Element("lf");
		lfnode.addContent(lf);
		xml.addContent(lfnode);
		lfxml.setRootElement(xml);
		return lfxml;
	}
	
	public void printParaphrases(String dimension, String specification, String referent, int politeness, int formality, boolean opener){
		printParaphrases(dimension, specification, referent, politeness, politeness, formality, formality, opener);
	}
	
	public void printParaphrases(String dimension, String specification, String referent, int politeness_from, int politeness_to, int formality_from, int formality_to, boolean opener){
		//specification == "constraint"?
			
		//meaning: dimension, spec, referent
		//syntax: pos
		int formality;
		int politeness;
		for(formality=formality_from; formality<formality_to+1; formality++){
			for(politeness=politeness_from; politeness<politeness_to+1; politeness++){
		
				WordConf wconf = chooseWords(dimension, specification, referent, formality);
				GenConf gconf=new GenConf(dimension, wconf.getWhWord(), wconf.getVerb(), wconf.getNoun(), formality, politeness, opener);
				
				
				//show all phrases (with alternatives in brackets)
				ArrayList<String> phrases=generateParaphrases(3, gconf);
				String phrase=phrases.get(0);
				if(phrases.size()>1){
					phrase+=" (";
					for(int i=1; i<phrases.size(); i++) phrase+=phrases.get(i)+", ";
					phrase=phrase.substring(0, phrase.length()-2);
					phrase+=")";
				}
				
//				//select random
//				Random randomizer = new Random();
//				int random=randomizer.nextInt(phrases.size());
//				String phrase=phrases.get(random);

				printBeautifully(phrase, true);
			}
		}
	}
	
	public String generateQuestion(AQD aqd){
		return generateQuestion(aqd, false);
	}
	
	public String generateQuestion(AQD aqd, boolean variate){
		
		//Politeness and opener variation:
		boolean opener = aqd.getForm().getTemporalOpener();
		int politeness=aqd.getForm().getPoliteness();

		if(variate){
			//openers (and/now) only if not the first question and only every second question
			opener=((generation_number+1)%2==1)?false:true;
			
			//politeness variation:
			if(aqd.getForm().getPoliteness()>=-1 && aqd.getForm().getPoliteness() <=4){
				int variation=((generation_number+1)%3)-1; //add 0,1,-1,...
				politeness=aqd.getForm().getPoliteness()+variation;
			}
		}
		generation_number++;
		//--
		
		WordConf wconf = chooseWords(aqd.getType().getAnswerType(), aqd.getContext().getSpecification(), aqd.getContext().getReference(), aqd.getForm().getFormality());
		//GenConf gconf=new GenConf(aqd.getType().getAnswerType(), wconf.getWhWord(), wconf.getVerb(), wconf.getNoun(), aqd.getForm().getFormality(), aqd.getForm().getPoliteness(), aqd.getForm().getTemporalOpener());
		GenConf gconf=new GenConf(aqd.getType().getAnswerType(), wconf.getWhWord(), wconf.getVerb(), wconf.getNoun(), aqd.getForm().getFormality(), politeness, opener);
		return makeBeautifully(generateParaphrase(gconf));
	}
	
	
	private String makeBeautifully(String phrase){
		if(phrase!=null && phrase.length()>0){
			phrase=phrase.replaceAll("_", " ");
			phrase=phrase.substring(0, 1).toUpperCase()+phrase.substring(1);
			return phrase;
		}
		else return "";
	}
	
	private void printBeautifully(String phrase, boolean asDialogue){
	
		phrase=makeBeautifully(phrase);
		
		if(asDialogue){
			System.out.println("S: "+phrase);
			System.out.println("U: ...");
		}
		else System.out.println(phrase);
	}
}