/*******************************************************************************
* 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 18-Oct-2004
 *
 */
package uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentInstance;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentStateIOMapping;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IBasicArgumentCondition;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IBasicArgumentValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IInputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IOutputNode;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.ParameteredTypeClass;
import uk.ac.ic.doc.kevlar.publicInterfaces.INode;
import uk.ac.ic.doc.kevlar.publicUtil.collections.IImmutableSet;
import uk.ac.ic.doc.kevlar.publicUtil.collections.ImmutableHashSet;
import uk.ac.ic.doc.kevlar.publicUtil.dom.XMLUtil;


/**
 * @author Daniel Burke
 *
 */
public final class ArgumentStateIOMapping implements IArgumentStateIOMapping
{
    private IImmutableSet<AbstractArgumentCondition> conditions;
    private IImmutableSet<IInputNode> inputs;
    private IImmutableSet<IOutputNode> outputs;
    
    public ArgumentStateIOMapping(
    		Collection<IBasicArgumentCondition> conditions, 
			Collection<IInputNode> inputs, 
			Collection<IOutputNode> outputs)
    		
    {
    	if(conditions == null)
    	{
    		conditions = new ArrayList<IBasicArgumentCondition>(0);
    	}
    	
    	if(inputs == null)
    	{
    		inputs = new ArrayList<IInputNode>(0);
    	}
    	
    	if(outputs == null)
    	{
    		outputs = new ArrayList<IOutputNode>(0);
    	}
    	
        TreeSet<AbstractArgumentCondition> tempConditions =
            new TreeSet<AbstractArgumentCondition>();

        for(IBasicArgumentCondition condition : conditions)
        {
            if(!(condition instanceof SetArgumentCondition ||
                    condition instanceof BoolArgumentCondition ||
					condition instanceof ArgumentExistsCondition))
            {
                throw new IllegalArgumentException("All conditions" +
                		" must be of type Set/BoolArgumentCondition");
            }
            tempConditions.add((AbstractArgumentCondition)condition);
        }
        
        this.conditions = new ImmutableHashSet<AbstractArgumentCondition>(tempConditions);
        
        this.inputs = new ImmutableHashSet<IInputNode>(inputs);
        this.outputs = new ImmutableHashSet<IOutputNode>(outputs);

        Map<String, ParameteredTypeClass> foundTemplates = 
            new HashMap<String, ParameteredTypeClass>();
        
        Set<String> nodeNames = new HashSet<String>();
        for(INode node : this.inputs)
        {
        	if(! (node instanceof AbstractNode))
        	{
        		throw new IllegalArgumentException("Nodes must be of NodeDataImpl type");
        	}
        	
	    	if(node.getType() instanceof ParameteredTypeClass)
	    	{
	    	    ParameteredTypeClass templateType = (ParameteredTypeClass)node.getType();
	    	    if(foundTemplates.containsKey(templateType.getTemplateName()))
	    	    {
	    	        ParameteredTypeClass old = foundTemplates.get(
	    	                templateType.getTemplateName());
	    	        if(templateType.getInterfaceClassUpperBound().getTypeClass() !=
	    	            old.getInterfaceClassUpperBound().getTypeClass())
	    	        {
	    	            throw new IllegalArgumentException("All nodes of the same template name " +
	    	            		"must have the same upper bound");
	    	        }
	    	    }
	    	    else
	    	    {
	    	        foundTemplates.put(templateType.getTemplateName(), templateType);
	    	        
	    	    }
	    	}
        	
	    	if(nodeNames.contains(node.getName()))
	    	{
	    	    throw new IllegalArgumentException("All node names within an argument IO mapping must be unique");
	    	}
	    	nodeNames.add(node.getName());
        }
        
        for(INode node : this.outputs)
        {
        	if(! (node instanceof AbstractNode))
        	{
        		throw new IllegalArgumentException("Nodes must be of NodeDataImpl type");
        	}
        	
	    	if(node.getType() instanceof ParameteredTypeClass)
	    	{
	    	    ParameteredTypeClass templateType = (ParameteredTypeClass)node.getType();
	    	    if(!foundTemplates.containsKey(templateType.getTemplateName()))
	    	    {
	    	    	throw new IllegalArgumentException("For every IO function, output nodes " +
	    	    			"may only have a template name with " +
	    	    			"a corropsonding to an input node");
	    	    }
	    	    
    	        ParameteredTypeClass old = foundTemplates.get(
    	                templateType.getTemplateName());
    	        if(templateType.getInterfaceClassUpperBound().getTypeClass() !=
    	            old.getInterfaceClassUpperBound().getTypeClass())
    	        {
    	            throw new IllegalArgumentException("All nodes of the same template name " +
    	            		"must have the same upper bound");
    	        }
	    	}
	    	
	    	if(nodeNames.contains(node.getName()))
	    	{
	    	    throw new IllegalArgumentException("All node names within an argument IO mapping must be unique");
	    	}
	    	nodeNames.add(node.getName());
        }
        
    }

    public IImmutableSet<AbstractArgumentCondition> getConditions() {
        return conditions;
    }
    
	public IImmutableSet<IInputNode> getInputs() {
		return inputs;
	}

	public IImmutableSet<IOutputNode> getOutputs() {
		return outputs;
	}
	
	public boolean isMatch(IArgumentInstance instance)
	{
		Map<String, IBasicArgumentValue> assignments = instance.getAssigmentsMap();
		
		for(AbstractArgumentCondition condition : conditions)
		{
			if(condition instanceof ArgumentExistsCondition)
			{
				return ((ArgumentExistsCondition)condition).existanceRequired() ==
					assignments.containsKey(condition.getTargetArgument());
			}
			
			if(!assignments.containsKey(condition.getTargetArgument()))
			{
				return false;
			}
			
			if(!condition.isMatch(assignments.get(condition.getTargetArgument())))
			{
				return false;
			}
		}
		
		return true;
	}
	
	public String toString()
	{
		return toString(0);
	}
	
	public String toString(int depth)
	{
		StringBuilder text = new StringBuilder();
		
		depth = XMLUtil.putOpenElem(text, "mapping", depth);
		
		depth = XMLUtil.putOpenElem(text, "conditions", depth);
		for(AbstractArgumentCondition condition : conditions)
		{
			text.append(condition.toString(depth));
		}
		depth = XMLUtil.putCloseElem(text, "conditions", depth);

		depth = XMLUtil.putOpenElem(text, "input-pipes", depth);
		for(IInputNode node : inputs)
		{
			text.append(((AbstractNode)node).toString(depth));
		}
		depth = XMLUtil.putCloseElem(text, "input-pipes", depth);
		
		depth = XMLUtil.putOpenElem(text, "output-pipes", depth);
		for(IOutputNode node : outputs)
		{
			text.append(((AbstractNode)node).toString(depth));
		}
		depth = XMLUtil.putCloseElem(text, "output-pipes", depth);
		
		depth = XMLUtil.putCloseElem(text, "mapping", depth);
		
		return text.toString();
	}
}
