/**
 * 
 * Written by Márton Münz and Philip C Biggin
 * Copyright (c) University of Oxford, United Kingdom
 * Visit http://sbcb.bioch.ox.ac.uk/jgromacs/
 * 
 * This source code file is part of JGromacs v1.0.
 * 
 * JGromacs v1.0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JGromacs v1.0. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JGromacs v1.0. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package jgromacs.ui;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import jgromacs.data.IndexSetList;

/**
 * Parent class of JGromacs applications.
 * You can extend this class to write JGromacs applications.
 * (See TemplateApplication.java !)
 *
 */
public abstract class Application {
	
	private static final int INPUT = 1;
	private static final int OUTPUT = 2;
	private static final int SETTING = 3;
	private String start_time = "";
	private String end_time = "";
	private String applicationName;
	private String author;
	private String description;
	private String version;
	private boolean writeLogFile = false;
	private String logFileName = "logfile.log";
	
	/**
	 * XML configuration file name
	 * 
	 */
	protected String XMLFileName = "template.xml";
	private ArrayList<String> flags = new ArrayList<String>();
	private ArrayList<Argument> arguments = new ArrayList<Argument>();
	private boolean canRunWithoutArguments = false;
	 
	// Inner class: Argument
	private class Argument {
		public int type = 1;
		public String description = "";
		public boolean optional = false;
		public boolean given = false;
		public String value = "";
				
		public Argument(int type, String description,
				boolean optional, String value) {
			super();
			this.type = type;
			this.description = description;
			this.optional = optional;
			this.value = value;
		}

	}
	
	//	End of inner class: Argument
	
	
	/**
	 * Constructor
	 *
	 */
	public Application(){
		
	}
	
	/**
	 * Don't override this method!
	 *
	 */
	protected void run(String[] args){
		readXML();
		printWelcomeMessage();
		if ((args.length>0)||(canRunWithoutArguments)) {
		if (askForHelp(args))  printHelpMessage();
		else {
		analyzeArguments(args);
		start_time = getDateTime();
		if (writeLogFile) writeStartInfoToLogFile(args);
		if (checkArguments()) runCore(); 
		else {
			System.out.println("Fatal error: a compulsory argument is missing!");
		}
		end_time = getDateTime();
		printFinishedMessage();
		writeEndInfoToLogFile();
		}
		}
	}
		
	private void analyzeArguments(String[] args){
		String flag = "";
		for (int i = 0; i < args.length; i++) {
			if (i<args.length-1){
			String s = args[i];
			String snext = args[i+1];
			if (s.startsWith("-")){
				flag = s.substring(1);
				if (!flags.contains(flag))
				System.out.println("Argument is not correct!");		
			}
			else System.out.println("Argument is not correct!");
			if (snext.startsWith("-"))
				getArgumentByFlag(flag).given = true;
			else { 
				getArgumentByFlag(flag).given = true;
				getArgumentByFlag(flag).value = snext;
				i++;
			 }
			}
			else {
				String slast = args[i];
				if (slast.startsWith("-")){
					flag = slast.substring(1);
					if (!flags.contains(flag))
					System.out.println("Argument is not correct!");
					else getArgumentByFlag(flag).given = true;
				}
				else System.out.println("Argument is not correct!");				
			}
		}
	}
	
	private void readXML(){
		try {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	//	factory.setValidating(true);
		DocumentBuilder parser = factory.newDocumentBuilder();
		Document doc = parser.parse(XMLFileName);
		Element meta = (Element)doc.getElementsByTagName("meta").item(0);
		applicationName = getElementTextValue(meta,"name");
		author = getElementTextValue(meta,"author");
		description = getElementTextValue(meta,"description");
		version = getElementTextValue(meta,"version");
		Element args = (Element)doc.getElementsByTagName("arguments").item(0);
		NodeList argList = args.getElementsByTagName("argument");
		for (int i = 0; i < argList.getLength(); i++) {
			Element element = (Element)argList.item(i);
			readArgument(element);
		}
		} catch(Exception e){
			System.err.println("Error: "+e);
		}
	}
	
	private void readArgument(Element element){
		String typestr = element.getAttribute("type");
		String optional = element.getAttribute("optional");
		boolean opt = false;
		if (optional.trim().toUpperCase().equals("TRUE")) opt = true;
		else opt = false;
		int type = INPUT;
		if (typestr.trim().toUpperCase().equals("INPUT")) type = INPUT;
		if (typestr.trim().toUpperCase().equals("OUTPUT")) type = OUTPUT;
		if (typestr.trim().toUpperCase().equals("SETTING")) type = SETTING;
		String description = getElementTextValue(element, "description");
		String value = getElementTextValue(element, "default");
		String flag = getElementTextValue(element, "flag");
		Argument arg = new Argument(type, description, opt, value);
		flags.add(flag);
		arguments.add(arg);
	}
	
	private String getElementTextValue(Element element, String tag) {
		String ret = null;
		NodeList list = element.getElementsByTagName(tag);
		if(list != null && list.getLength() > 0) {
			Element el = (Element)list.item(0);
			ret = el.getFirstChild().getNodeValue();
		}
		return ret;
	}

	
	private String getDateTime(){
		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
	    Date date = new Date();
	    return dateFormat.format(date);
	} 
	
	private boolean askForHelp(String[] args){
		if (args.length==1){
			if (args[0].equals("-h")) return true;
			else return false;
		}
		else return false;
	}
		
	private void printWelcomeMessage(){
		System.out.println();
		System.out.println(alignTextToCenter(";-)  J G  r  o  m  a  c  s  (-;",40));
		System.out.println();
		System.out.println(alignTextToCenter("a Java API for analysing protein motions",40));
		System.out.println();
		System.out.println(alignTextToCenter(";-)  VERSION 1.0.1  (-;",40));
		System.out.println();
		System.out.println();
		System.out.println(alignTextToCenter("Written by Marton Munz and Philip C Biggin",40));
		System.out.println(alignTextToCenter("Copyright (c)  University of Oxford, United Kingdom",40));
		System.out.println(alignTextToCenter("visit http://sbcb.bioch.ox.ac.uk/jgromacs/",40));
		System.out.println();
		System.out.println(alignTextToCenter("This program is free software; you can redistribute it and/or",40));
		System.out.println(alignTextToCenter("modify it under the terms of the GNU General Public License",40));
		System.out.println(alignTextToCenter("as published by the Free Software Foundation; either version 3",40));
		System.out.println(alignTextToCenter("of the License, or (at your option) any later version.",40));
		System.out.println();
		System.out.println(alignTextToCenter(";-)  "+applicationName+"  (-;",40));
		System.out.println(alignTextToCenter("Version: "+version,40));
		System.out.println(alignTextToCenter("Written by "+author,40));
		System.out.println();
	}
	
	private String alignTextToCenter(String text, int center){
		int n = center-text.length()/2;
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < n; i++) buf.append(" ");
		return buf.toString()+text;
	}
	
	private void printFinishedMessage(){
		System.out.println();
		System.out.println("---------------------------------------------------------------------------");
		System.out.println(applicationName+" has successfully finished.");
		System.out.println("It has been running from "+start_time+" to "+end_time);
		System.out.println("---------------------------------------------------------------------------");
		System.out.println();
	}
	
	private void printHelpMessage(){
		System.out.println("DESCRIPTION");
		System.out.println("-----------");
		System.out.println(description);
		System.out.println();
				
		ArrayList<String> inputFlags = new ArrayList<String>();
		ArrayList<String> inputDescriptions = new ArrayList<String>();
		ArrayList<Boolean> inputIsOptional = new ArrayList<Boolean>();
		ArrayList<String> inputDefaults = new ArrayList<String>();
		ArrayList<String> outputFlags = new ArrayList<String>();
		ArrayList<String> outputDescriptions = new ArrayList<String>();
		ArrayList<Boolean> outputIsOptional = new ArrayList<Boolean>();
		ArrayList<String> outputDefaults = new ArrayList<String>();
		ArrayList<String> settingFlags = new ArrayList<String>();
		ArrayList<String> settingDescriptions = new ArrayList<String>();
		ArrayList<Boolean> settingIsOptional = new ArrayList<Boolean>();
		ArrayList<String> settingDefaults = new ArrayList<String>();
		
	    String flag = "";
	    for (int i = 0; i < flags.size(); i++) {
			flag = flags.get(i);
			Argument arg = arguments.get(i);
	 		if (arg.type==INPUT){
				inputFlags.add(flag);
				inputDescriptions.add(arg.description);
				inputDefaults.add(arg.value);
				inputIsOptional.add(arg.optional);
			}
			if (arg.type==OUTPUT){
				outputFlags.add(flag);
				outputDescriptions.add(arg.description);
				outputDefaults.add(arg.value);
				outputIsOptional.add(arg.optional);
			}
			if (arg.type==SETTING){
				settingFlags.add(flag);
				settingDescriptions.add(arg.description);
				settingDefaults.add(arg.value);
				settingIsOptional.add(arg.optional);
			}
		}
		
		if (inputFlags.size()>0) {
		System.out.println(" Inputs:");
		System.out.println();
		ArrayList<String> flags = new ArrayList<String>();
		ArrayList<String> descriptions = new ArrayList<String>();
		ArrayList<String> optionals = new ArrayList<String>();
		ArrayList<String> defs = new ArrayList<String>();
		int maxflag = 4;
		int maxdescription = 11;
		int maxoptional = 4;
		int maxdefs = 12;
		flags.add("Flag");
		defs.add("DefaultValue");
		optionals.add("Opt.");
		descriptions.add("Description");
		for (int i = 0; i < inputFlags.size(); i++) {
				flags.add("-"+(String)inputFlags.get(i));
				if (((String)inputFlags.get(i)).length()>maxflag)
					maxflag=((String)inputFlags.get(i)).length();
				descriptions.add((String)inputDescriptions.get(i));
				if (((String)inputDescriptions.get(i)).length()>maxdescription)
					maxdescription=((String)inputDescriptions.get(i)).length();
				if ((Boolean)inputIsOptional.get(i)) optionals.add("Yes");
				else optionals.add("No");
				defs.add((String)inputDefaults.get(i));
				if (((String)inputDefaults.get(i)).length()>maxdefs)
					maxdefs=((String)inputDefaults.get(i)).length();
		}	
		

		String format = "   %1$-"+(maxflag+1)+"s  %2$-"+(maxdefs+1)+"s  %3$-"+(maxoptional+1)+"s  %4$-"+(maxdescription+1)+"s\n";
		System.out.format(format, flags.get(0),defs.get(0),optionals.get(0),descriptions.get(0));

		System.out.print("   ");
		for (int i = 0; i < (maxflag+maxdescription+maxoptional+maxdefs+10); i++)
		System.out.print("-");
		System.out.println();
		
		for (int i = 1; i < flags.size(); i++)
			System.out.format(format, flags.get(i),defs.get(i),optionals.get(i),descriptions.get(i));
		
		}
		
		if (outputFlags.size()>0) {
		System.out.println();
		System.out.println(" Outputs:");
		System.out.println();
		
		ArrayList<String> flags = new ArrayList<String>();
		ArrayList<String> descriptions = new ArrayList<String>();
		ArrayList<String> optionals = new ArrayList<String>();
		ArrayList<String> defs = new ArrayList<String>();
		int maxflag = 4;
		int maxdescription = 11;
		int maxoptional = 4;
		int maxdefs = 12;
		flags.add("Flag");
		defs.add("DefaultValue");
		optionals.add("Opt.");
		descriptions.add("Description");
		
		for (int i = 0; i < outputFlags.size(); i++) {
				flags.add("-"+(String)outputFlags.get(i));
				if (((String)outputFlags.get(i)).length()>maxflag)
					maxflag=((String)outputFlags.get(i)).length();
				descriptions.add((String)outputDescriptions.get(i));
				if (((String)outputDescriptions.get(i)).length()>maxdescription)
					maxdescription=((String)outputDescriptions.get(i)).length();
				if ((Boolean)outputIsOptional.get(i)) optionals.add("Yes");
				else optionals.add("No");
				defs.add((String)outputDefaults.get(i));
				if (((String)outputDefaults.get(i)).length()>maxdefs)
					maxdefs=((String)outputDefaults.get(i)).length();
		}	
		

		String format = "   %1$-"+(maxflag+1)+"s  %2$-"+(maxdefs+1)+"s  %3$-"+(maxoptional+1)+"s  %4$-"+(maxdescription+1)+"s\n";
		System.out.format(format, flags.get(0),defs.get(0),optionals.get(0),descriptions.get(0));

		System.out.print("   ");
		for (int i = 0; i < (maxflag+maxdescription+maxoptional+maxdefs+10); i++)
		System.out.print("-");
		System.out.println();
		
		for (int i = 1; i < flags.size(); i++)
			System.out.format(format, flags.get(i),defs.get(i),optionals.get(i),descriptions.get(i));
		
		
		}
		
		if (settingFlags.size()>0) {
			System.out.println();
			System.out.println(" Settings:");
			System.out.println();
			
			ArrayList<String> flags = new ArrayList<String>();
			ArrayList<String> descriptions = new ArrayList<String>();
			ArrayList<String> optionals = new ArrayList<String>();
			ArrayList<String> defs = new ArrayList<String>();
			int maxflag = 4;
			int maxdescription = 11;
			int maxoptional = 4;
			int maxdefs = 12;
			flags.add("Flag");
			defs.add("DefaultValue");
			optionals.add("Opt.");
			descriptions.add("Description");
			
			for (int i = 0; i < settingFlags.size(); i++) {
					flags.add("-"+(String)settingFlags.get(i));
					if (((String)settingFlags.get(i)).length()>maxflag)
						maxflag=((String)settingFlags.get(i)).length();
					descriptions.add((String)settingDescriptions.get(i));
					if (((String)settingDescriptions.get(i)).length()>maxdescription)
						maxdescription=((String)settingDescriptions.get(i)).length();
					if ((Boolean)settingIsOptional.get(i)) optionals.add("Yes");
					else optionals.add("No");
					defs.add((String)settingDefaults.get(i));
					if (((String)settingDefaults.get(i)).length()>maxdefs)
						maxdefs=((String)settingDefaults.get(i)).length();
			}	
			

			String format = "   %1$-"+(maxflag+1)+"s  %2$-"+(maxdefs+1)+"s  %3$-"+(maxoptional+1)+"s  %4$-"+(maxdescription+1)+"s\n";
			System.out.format(format, flags.get(0),defs.get(0),optionals.get(0),descriptions.get(0));

			System.out.print("   ");
			for (int i = 0; i < (maxflag+maxdescription+maxoptional+maxdefs+10); i++)
			System.out.print("-");
			System.out.println();
			
			for (int i = 1; i < flags.size(); i++)
				System.out.format(format, flags.get(i),defs.get(i),optionals.get(i),descriptions.get(i));
			
			
			}
	
		System.out.println();
	}
	
	private boolean checkArguments(){
		 for (int i = 0; i < flags.size(); i++) {
		  if ((!arguments.get(i).optional)&(!arguments.get(i).given)) return false;
	    }
		return true;
	}
	
	/**
	 * You can override this method to include your own code.
	 * This is the entry point of your program. 
	 *
	 */
	protected abstract void runCore();
	
	
	/**
	 * Returns true if the attribute defined by the given flag was called by the user
	 * @param flag attribute flag
	 *
	 */
	protected boolean isArgumentGiven(String flag){
		Argument arg = getArgumentByFlag(flag);
		return arg.given;
	}
	
	/**
	 * Returns the value defined by the given attribute flag
	 * @param flag attribute flag
	 * @return value
	 *
	 */
	protected String getArgumentValue(String flag){
		Argument arg = getArgumentByFlag(flag);
		return arg.value;
	}
	
	/**
	 * Asks the user which index set is to be selected from the index set list
	 * @param list index set list to select from
	 * @param question question to be asked
	 * @return list index of selected index set
	 */
	protected int askIndexSetFromUser(IndexSetList list, String question){
		System.out.println("   "+question);
		System.out.println();
		for (int i = 0; i < list.getNumberOfIndexSets(); i++)
		System.out.println("   "+i+": "+list.getIndexSet(i).getName()+" ("+list.getIndexSet(i).getNumberOfIndices()+" atoms)");
		System.out.println();
			System.out.print("   Your selection: ");
			try {
				InputStreamReader isr = new InputStreamReader(System.in);
				BufferedReader br = new BufferedReader(isr);
				String nstring = br.readLine();
				int n = Integer.valueOf(nstring);
				 if ((n>=0) && (n<list.getNumberOfIndexSets())) {
			        	System.out.println("   "+"You have selected the set "+list.getIndexSet(n).getName()+".");
			        	return n;
			     }
			        else { System.out.print("   "+"Incorrect input.");
			        return -1;
			        }

			} catch (IOException e) {
			return -1;
			}
	}
	
	/**
	 * Asks the user which index set is to be selected from the index set list
	 * @param list index set list to select from
	 * @return list index of selected index set
	 */
	protected int askIndexSetFromUser(IndexSetList list){
		return askIndexSetFromUser(list,"Please select an index set from the following possibilites:");	
	}
	
	/**
	 * Sets if the application can run without argument
	 * @param can true if the application runs without argument
	 */
	protected void canRunWithoutArgument(boolean can){
		canRunWithoutArguments = can;
	}
	
	/**
	 * Sets if the application writes log file
	 * @param write true if the application writes log file
	 */
	protected void setWritingToLogFile(boolean write){
		writeLogFile = write;
	}
	
	/**
	 * Sets the name of log file
	 * @param filename name of log file
	 */
	protected void setLogFileName(String filename){
		logFileName = filename;
	}
	
	/**
	 * Sets if the application writes log file
	 * @param write true if the application writes log file
	 */
	protected void writeToLogFile(String text){
		if (writeLogFile){
			File file = new File(logFileName);
			   try {
			      BufferedWriter output =  new BufferedWriter(new FileWriter(file,true));
			      try {
			    		output.write(text);
			 			
				        }
			finally {
			  output.close();
			}
			}
			catch (IOException ex){
			  ex.printStackTrace();
		  }
		}
	}
	
	private void writeStartInfoToLogFile(String[] args){
		StringBuffer buf = new StringBuffer("---------------------------------------------------------------------------\n");
		buf.append(applicationName+" started: "+start_time+"\n");
		buf.append("Command used: "+applicationName+" ");
		for (int i = 0; i < args.length; i++) buf.append(args[i]+" ");
		buf.append("\n");
		buf.append("---------------------------------------------------------------------------\n");
		writeToLogFile(buf.toString());
	}
	
	private void writeEndInfoToLogFile(){
		String info = "---------------------------------------------------------------------------\n";
		info+=applicationName+" succesfully finished: "+end_time+"\n";
		info+="---------------------------------------------------------------------------\n";
		writeToLogFile(info);
	}
	
	/**
	 * Returns the name of log file
	 * @return name of log file
	 */
	protected String getLogFileName(){
		return logFileName;
	}
	
	private Argument getArgumentByFlag(String flag){
		for (int i = 0; i < flags.size(); i++) {
			if (flags.get(i).equals(flag)) return arguments.get(i);
		}
		return null;
	}
	
}
