/*******************************************************************************
* Copyright (c)                                                                *
*           2005   Tristan Allwood,                                            *
*           2005   Daniel Burke,                                               *
*           2005   Marc Hull,                                                  *
*           2005   Ekaterina Itskova,                                          *
*           2005   Steve Zymler                                                *
*                                                                              *
* Permission is hereby granted, free of charge, to any person obtaining a      *
* copy of this software and associated documentation files (the                *
* "Software"), to deal in the Software without restriction, including          *
* without limitation the rights to use, copy, modify, merge, publish,          *
* distribute, sublicense, and/or sell copies of the Software, and to           *
* permit persons to whom the Software is furnished to do so, subject to        *
* the following conditions:                                                    *
*                                                                              *
* The above copyright notice and this permission notice shall be included      *
* in all copies or substantial portions of the Software.                       *
*                                                                              *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS      *
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF                   *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.       *
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY         *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,         *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE            *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                       *
*******************************************************************************/
/*
 * Created on 19-Oct-2004
 *
 */
package uk.ac.ic.doc.kevlar.api.programs.arguments;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import uk.ac.ic.doc.kevlar.api.programs.XMLModelLoadingException;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.BoolArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.MultilineArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.SetArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.SetValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.SinglelineArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInstance.ArgumentInstance;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInstance.BoolArgumentValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInstance.SetArgumentValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInstance.TextArgumentValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.ArgumentExistsCondition;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.ArgumentStateIOMapping;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.BoolArgumentCondition;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.InputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.OutputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.SetArgumentCondition;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.ITypeLoader;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.IInterfaceClass;
import uk.ac.ic.doc.kevlar.hial.ArgumentValuePair;
import uk.ac.ic.doc.kevlar.hial.IArgumentValuePair;
import uk.ac.ic.doc.kevlar.publicInterfaces.INode;
import uk.ac.ic.doc.kevlar.publicUtil.dom.XMLUtil;

/**
 * @author Daniel Burke
 *
 */
public final class ArgumentModelProvider {
	
	private ArgumentModel model;

	/**
	 *
	 * 
	 *
	 * @param xmlData the 'argument-model' element that contains data to represent an argument model
	 * @param loader the loader to use for loading types
	 * 
	 * The xml must be validated against the program DTD.
	 * If it is not, this constructor may additionally throw any RuntimeException
	 * @throws ClassNotFoundException
	 * @throws XMLModelLoadingException
	 */
	public ArgumentModelProvider(Element xmlData, ITypeLoader loader) throws XMLModelLoadingException, ClassNotFoundException
	{
	    try{
		    Element argumentsElement = XMLUtil.yankElement(xmlData, "arguments");
		    Element mappingsElement =  XMLUtil.yankElement(xmlData, "mappings");
		    
		    Collection<IBasicArgumentInfo> arguments = 
		        new ArrayList<IBasicArgumentInfo>();
		    List<IArgumentStateIOMapping> mappings = 
		        new ArrayList<IArgumentStateIOMapping>();
		    
		    for(Element argumentElement : XMLUtil.getElementIterator(
		            argumentsElement.getElementsByTagName("argument")))
		    {
		        arguments.add(makeBasicArgument(argumentElement));
		    }
	
		    for(Element mappingElement : XMLUtil.getElementIterator(
		            mappingsElement.getElementsByTagName("mapping")))
		    {
		        mappings.add(makeArgumentStateIOMapping(mappingElement, loader));
		    }
		    
		    model = new ArgumentModel(arguments, mappings);
	    
	    }
	    catch(ClassNotFoundException e)
	    {
	        throw e;
	    }
	    catch(Exception e)
	    {
	        throw new XMLModelLoadingException(e);
	        
	    }
	}
	
	public IArgumentModel getArgumentModel()
	{
		return model;
	}
	
	private static IBasicArgumentInfo makeBasicArgument(Element argElem)
	{
	    String name = XMLUtil.yankText(argElem, "name");
	    IArgumentHelp help = makeArgumentHelp(XMLUtil.yankElement(argElem, "help"));
	    boolean compulsary = null != XMLUtil.yankElement(argElem, "is-compulsary");
	    
	    
	    Element specialisedArgElem;
	    if((specialisedArgElem = 
	        XMLUtil.yankElement(argElem, "set-argument")) != null)
	    {
	        return makeSetArgument(
	                name, compulsary, help, specialisedArgElem);
	    }
	    else if((specialisedArgElem = 
	        XMLUtil.yankElement(argElem, "bool-argument")) != null)
	    {
	        return makeBoolArgument(
	                name, compulsary, help, specialisedArgElem);
	    }
	    else if((specialisedArgElem = 
	        XMLUtil.yankElement(argElem, "text-argument")) != null)
	    {
	        return makeTextArgument(
	                name, compulsary, help, specialisedArgElem);
	    }
	    
	    throw new IllegalArgumentException("The XML is malformed");
	}
	
	private static ISetArgumentInfo makeSetArgument(
	        String name, 
	        boolean compulsary,
	        IArgumentHelp help, 
	        Element setArgElem)
	{
	    
	    String initialValue = XMLUtil.yankText(setArgElem, "initial");
	    
	    Element setValuesElement = XMLUtil.yankElement(setArgElem, "set-values");
	    NodeList setValueList = setValuesElement.getElementsByTagName("set-value");
	    
	    Collection<ISetValue> setValues = new ArrayList<ISetValue>();
	    for(Element setValue : XMLUtil.getElementIterator(setValueList))
	    {
	        setValues.add( makeSetValue(setValue));
	    }
	    
	    return makeSetArgument(
	            name, compulsary, help, setValues, initialValue);
	}
	
	private static ISetValue makeSetValue(Element setValueElem)
	{
	    String name = XMLUtil.yankText(setValueElem, "name");
	    Element childrenElement = XMLUtil.yankElement(setValueElem, "children");
	    
	    Collection<String> children = null;
	    if(childrenElement != null)
	    {
	        children = makeChildren( childrenElement );
	    }
	    
	    return makeSetValue(name, children);
	}
	
	private static Collection<String> makeChildren(Element childrenElem)
	{
	    NodeList childList = childrenElem.getElementsByTagName("child");
	    Collection<String> children = new ArrayList<String>();
	    for(Element child : XMLUtil.getElementIterator(childList))
	    {
	        children.add(XMLUtil.getInnerContent(child));
	    }
	    
	    return children;
	}
	
	private static IBoolArgumentInfo makeBoolArgument(
	        String name, 
	        boolean compulsary,
	        IArgumentHelp help, 
	        Element boolArgElem)
	{
	    Element trueChildrenElement = XMLUtil.yankElement(boolArgElem, "true-children");
	    Element falseChildrenElement = XMLUtil.yankElement(boolArgElem, "false-children");
	    Collection<String> trueChildren = null;
	    Collection<String> falseChildren = null;
	    
	    if(trueChildrenElement != null)
	    {
	        trueChildren = makeChildren(trueChildrenElement);
	    }
	    
	    if(falseChildrenElement != null)
	    {
	        falseChildren = makeChildren(falseChildrenElement);
	    }
	    
	    boolean initialValue = null != XMLUtil.yankElement(boolArgElem, "initially-on");
	    
	    return makeBoolArgument(name, compulsary, 
	            help, trueChildren, falseChildren, initialValue);
	}
	
	private static ITextArgumentInfo makeTextArgument(
	        String name, 
	        boolean compulsary,
	        IArgumentHelp help, 
	        Element textArgElem)
	{
	    String initial = XMLUtil.yankText(textArgElem, "initial");
	    boolean isMultiline = null != XMLUtil.yankElement(textArgElem, "is-multiline");
	    boolean isFilearg = null != XMLUtil.yankElement(textArgElem, "is-file-arg");
	    String regexText = XMLUtil.yankText(textArgElem, "regex"); 
	    
	    Pattern regex = null;
	    if(regexText != null)
	    {
	        regex = Pattern.compile(regexText);
	    }
	    return makeTextArgument(
	            name, 
	            compulsary, 
	            help, 
	            initial, 
	            isMultiline, 
	            isFilearg, 
	            regex);
	}
	
	private static IArgumentHelp makeArgumentHelp(Element helpElem)
	{
	    return makeArgumentHelp(
	            XMLUtil.yankText(helpElem, "overview"), 
	            XMLUtil.yankText(helpElem, "validation-info"));
	}
	
	private static IArgumentStateIOMapping makeArgumentStateIOMapping(
	        Element mappingElem, 
	        ITypeLoader loader) throws ClassNotFoundException
	{
	    Element conditionsElement = XMLUtil.yankElement(mappingElem, "conditions");
	    Element inputsElement =  XMLUtil.yankElement(mappingElem, "input-pipes");
	    Element outputsElement =  XMLUtil.yankElement(mappingElem, "output-pipes");
	    
	    Collection<IBasicArgumentCondition> conditions = 
	        new ArrayList<IBasicArgumentCondition>();
	    Collection<IInputNode> inputs = 
	        new ArrayList<IInputNode>();
	    Collection<IOutputNode> outputs = 
	        new ArrayList<IOutputNode>();
	    
	    for(Element conditionElement : XMLUtil.getElementIterator(
	            conditionsElement.getElementsByTagName("condition")))
	    {
	        conditions.add(makeBasicArgCondition(conditionElement));
	    }
	    
	    for(Element pipeElement : XMLUtil.getElementIterator(
	            inputsElement.getElementsByTagName("pipe")))
	    {
	        inputs.add(makeInputNode(pipeElement, loader));
	    }
	    
	    for(Element pipeElement : XMLUtil.getElementIterator(
	            outputsElement.getElementsByTagName("pipe")))
	    {
	        outputs.add(makeOutputNode(pipeElement, loader));
	    }
	    
	    return makeArgumentStateIOMapping(conditions, inputs, outputs);
	}
	
	private static IBasicArgumentCondition makeBasicArgCondition(Element conditionElem)
	{
	    String target = XMLUtil.yankText(conditionElem, "target");
	    
	    Element specialisedConditionElem;
	    
	    if((specialisedConditionElem = 
	        XMLUtil.yankElement(conditionElem, "set-condition")) != null)
	    {
	        return makeSetArgCondition(target, specialisedConditionElem);
	    }
	    else if((specialisedConditionElem = 
	        XMLUtil.yankElement(conditionElem, "bool-true-condition")) != null)
	    {
	        return makeBoolArgCondition(target, true);
	    }
	    else if((specialisedConditionElem = 
	        XMLUtil.yankElement(conditionElem, "bool-false-condition")) != null)
	    {
	        return makeBoolArgCondition(target, false);
	    }
	    else if((specialisedConditionElem = 
	        XMLUtil.yankElement(conditionElem, "exists-condition")) != null)
	    {
	        return makeArgumentExistsCondition(target, true);
	    }
	    else if((specialisedConditionElem = 
	        XMLUtil.yankElement(conditionElem, "not-exists-condition")) != null)
	    {
	        return makeArgumentExistsCondition(target, false);
	    }
	    
	    throw new IllegalArgumentException("The XML is malformed");
	}
	
	private static ISetArgumentCondition makeSetArgCondition(
	        String target, Element conditionElem)
	{
	    
	    NodeList allowedValueList = XMLUtil.yankElement(conditionElem, 
	            "allowed-values").getElementsByTagName("value");
	    
	    Collection<String> allowedValues = new ArrayList<String>();
	    for(Element allowedValue : XMLUtil.getElementIterator(allowedValueList))
	    {
	        allowedValues.add(XMLUtil.getInnerContent(allowedValue));
	    }
	    
	    return makeSetArgCondition(target, allowedValues);
	}
	
	private static IInputNode makeInputNode(
	        Element pipeElem, ITypeLoader loader) throws ClassNotFoundException
	{
	    return (IInputNode)makeIONode(pipeElem, loader, true);
	}
	
	private static IOutputNode makeOutputNode(
	        Element pipeElem, ITypeLoader loader) throws ClassNotFoundException
	{
	    return (IOutputNode)makeIONode(pipeElem, loader, false);
	}
	
	private static INode makeIONode(
	        Element pipeElem, ITypeLoader loader, boolean isInput) throws ClassNotFoundException
	{
	    String name = XMLUtil.yankText(pipeElem, "name");
	    String typeText = XMLUtil.yankText(pipeElem, "type");
	    String pipeHelp = XMLUtil.yankText(pipeElem, "pipe-help");

	    String paramName = XMLUtil.yankText(pipeElem, "param-name");

	    IInterfaceClass type;
	    try{
	    	type = loader.getTypeFromName(typeText);
	    }
	    catch(NoSuchElementException e)
		{
	    	throw new ClassNotFoundException("Type loader could not find type : " +typeText, e);
		}
	    
	    if(paramName == null)
	    {
		    return makeInputOutputNode(name, type, pipeHelp, isInput);
	    }
	    else
	    {
	        return makeInputOutputNode(name, type, pipeHelp, isInput, paramName);
	    }
	}
	
	private static INode makeInputOutputNode(
	        String name, 
	        IInterfaceClass type,
	        String pipeHelp,
	        boolean isInput)
	{
	    if(isInput)
	    {
	        return new InputNode(name, type, pipeHelp);
	    }
	    return new OutputNode(name, type, pipeHelp);
	}
	        
	private static INode makeInputOutputNode(
	        String name, 
	        IInterfaceClass type,
	        String pipeHelp,
	        boolean isInput,
	        String paramName)
	{
	    if(isInput)
	    {
	        return new InputNode(name, type, pipeHelp, paramName);
	    }
	    return new OutputNode(name, type, pipeHelp, paramName);
	}
	
/*
	private ArgumentModelProvider(Collection<IBasicArgumentInfo> arguments, 
			List<IArgumentStateIOMapping> mappings)
	{
		model = new ArgumentModel(arguments, mappings);
	}
	*/


	private static IArgumentHelp makeArgumentHelp(String overview, String validationMessage)
	{
		return new ArgumentHelp(overview, validationMessage);
	}
	
	/*
	private static IArgumentHelp makeArgumentHelp(String overview)
	{
		return new ArgumentHelp(overview);
	}*/
	
	private static ISetValue makeSetValue(
			String name, 
			Collection<String> children)
	{
		return new SetValue(name, children);
	}
	
	private static ISetArgumentInfo makeSetArgument(
			String name, 
			boolean compulsary, 
			IArgumentHelp help,  
			Collection<ISetValue> setValues,
			String defaultValue)
	{
		return new SetArgumentInfo(name, help, compulsary, setValues, defaultValue);
	}
	
	private static IBoolArgumentInfo makeBoolArgument(
			String name, 
			boolean compulsary, 
			IArgumentHelp help,
			Collection<String> trueChildren,
			Collection<String> falseChildren,
			boolean initialValue)
	{
		return new BoolArgumentInfo(name, compulsary, help, trueChildren, falseChildren, initialValue);
	}
	
	private static ITextArgumentInfo makeTextArgument(
			String name, 
			boolean compulsary, 
			IArgumentHelp help,
			String initialValue,
			boolean isMultiline,
			boolean isFilearg,
			Pattern validationPattern)
	{
	    if(isMultiline)
	    {
			return new MultilineArgumentInfo(
			        name, 
			        compulsary, 
			        help,
			        initialValue, 
			        isFilearg, 
			        validationPattern);    
	    }
	    else
	    {
			return new SinglelineArgumentInfo(
			        name, 
			        compulsary, 
			        help,
			        initialValue, 
			        isFilearg, 
			        validationPattern);  
	    }

	}
	
	public static IBoolArgumentValue makeBooleanValue(boolean b)
	{
		return BoolArgumentValue.valueOf(b);
	}
	
	public static ISetArgumentValue makeSetValue(String itemName)
	{
		return SetArgumentValue.valueOf(itemName);
	}
	
	public static ITextArgumentValue makeTextValue(String text)
	{
		return TextArgumentValue.valueOf(text);
	}
	
	private static IBoolArgumentCondition makeBoolArgCondition(
			String targetArgument,
			boolean testValue)
	{
		return new BoolArgumentCondition(targetArgument, testValue);
	}
	
	private static ISetArgumentCondition makeSetArgCondition(
			String targetArgument,
			Collection<String> allowedValues)
	{
		return new SetArgumentCondition(targetArgument, allowedValues);
	}
	
	private static IArgumentStateIOMapping makeArgumentStateIOMapping(
			Collection<IBasicArgumentCondition> conditions, 
			Collection<IInputNode> inputs, Collection<IOutputNode> outputs)
	
	{	
		return new ArgumentStateIOMapping(conditions, inputs, outputs);
	}
	
	public static IArgumentInstance makeArgumentInstance(Map<String, IBasicArgumentValue> assignments)
	{
		return new ArgumentInstance(assignments);
	}
	
	private static IBasicArgumentCondition makeArgumentExistsCondition(String targetArgument, boolean exists)
	{
		return new ArgumentExistsCondition(targetArgument, exists);
	}
	
	public String toString()
	{
	    return toString(0);
	}
	
	public String toString(int depth)
	{
	    return model.toString(depth);
	}
	
	public static IArgumentValuePair makeArgumentValuePair(IBasicArgumentInfo argInfo, IBasicArgumentValue value)
	{
	    return new ArgumentValuePair(argInfo, value);
	}
}
