/*******************************************************************************
* 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 24-Oct-2004
 *
 */
package uk.ac.ic.doc.kevlar.core.framework;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import uk.ac.ic.doc.kevlar.api.programs.arguments.ArgumentModelProvider;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentInstance;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentModel;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IBasicArgumentInfo;
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.IInputOutputNodeTuple;
import uk.ac.ic.doc.kevlar.api.programs.arguments.ITextArgumentValue;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.argumentInfo.TextArgumentInfo;
import uk.ac.ic.doc.kevlar.core.framework.internal.IProgram;
import uk.ac.ic.doc.kevlar.core.framework.internal.IProgramInstance;
import uk.ac.ic.doc.kevlar.core.framework.internal.programs.IContextMainpulatingBuiltin;
import uk.ac.ic.doc.kevlar.core.framework.internal.programs.IProgramExitListener;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.IBasicTypeClass;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.IInterfaceClass;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.IParameteredTypeClass;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeClass;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.ParameteredTypeClass;
import uk.ac.ic.doc.kevlar.publicInterfaces.IInputPipe;
import uk.ac.ic.doc.kevlar.publicInterfaces.INode;
import uk.ac.ic.doc.kevlar.publicInterfaces.INodeID;
import uk.ac.ic.doc.kevlar.publicInterfaces.IOutputPipe;
import uk.ac.ic.doc.kevlar.publicInterfaces.IProgramDescription;

/**
 * @author Daniel Burke and Steve Zymler
 *
 */
public class PipelineProgram implements IPipelineProgram 
{
    IProgramID getProgramID()
    {
        return programID;
    }
    private long id;
    private IProgramID programID;
    private boolean destroyed;    
    private IProgram program;
    private IArgumentModel model;
    private IInputOutputNodeTuple ioFunction;
    private Set<String> invalidArgs = new HashSet<String>();
    private IArgumentInstance instance;
    private IProgramDescription description;
    private Set<String> availableArguments;
    
    private IProgramInstance executingInstance = null;
    
    private Map<INodeID, IPipe> connectedInputPipes ;
    private Map<INodeID, IPipe> connectedOutputPipes ;
    
    private Map<String, IInterfaceClass> lowerBounds =
        new HashMap<String, IInterfaceClass>();
    
	public PipelineProgram( 
			long id, 
			IProgramID programID, 
			IProgram program)
	{
		this.id = id ;
		
		this.program = program;
		this.model = program.getArgumentModelProvider().getArgumentModel();
		this.description = program.getDescription().getProgramDescription();
		
		this.connectedInputPipes = new HashMap<INodeID, IPipe>();
		this.connectedOutputPipes = new HashMap<INodeID, IPipe>();
						
		updateArgumentInstance(
				ArgumentModelProvider.makeArgumentInstance(
						new HashMap<String, IBasicArgumentValue>()));
		
		setIOFunction(getUpToDateIOFunction());
		
		for(IInputNode node : ioFunction.getInputs())
		{
		    ITypeClass type = node.getType();
		    if(type instanceof ParameteredTypeClass)
		    {
		        ParameteredTypeClass ptype = (ParameteredTypeClass)type;
		        lowerBounds.put(ptype.getTemplateName(), null);
		    }
		}
	}
	
	public long getID() 
	{	
		return id;
	}
		
	public synchronized boolean equals(Object obj) 
	{
		if( obj instanceof PipelineProgram )
		{
			return (id == ((IPipelineProgram)obj).getID()) ; 
		}
		if( obj instanceof ProgramIDDescription)
		{
			return programID == ((IProgramIDDescription)obj).getProgramID() ;
		}
		return false ;
	}		
		
	public synchronized int hashCode() 
	{	
		return (int) (id % Integer.MAX_VALUE);
	}
	
	public synchronized void destroy()
	{
	    this.destroyed = true ;
	}

    public synchronized IArgumentInstance getArgumentInstance() 
    {   
        return ArgumentModelProvider.makeArgumentInstance(
        		new HashMap<String, IBasicArgumentValue>(instance.getAssigmentsMap()));
    }

    public synchronized IInputOutputNodeTuple getIONodes() 
    {    	
        return ioFunction;
    }
  
    public synchronized Set<String> getAvailableArguments() 
	{

        return new HashSet<String>(availableArguments);
    }
    
    public synchronized IProgramDescription getDescription()
    {
    	return description;
    }
    
	public IBasicArgumentInfo getArgumentInfo(String argName)
	{
		return model.getArgumentNameMap().get(argName);
	}
    
    public synchronized void addArgument(String argName)
    {
        instance.getAssigmentsMap().put(
                argName, 
                model.getArgumentNameMap().
                	get(argName).getInitialValue());
        
        updateArgumentInstance(instance);
    }
    
    /**
     * Sideffects: 
     * instance will be updated to the latest argument instance
     * getAvailableArguments() returns latest available arguments
     * getArgumentInstance() returns the latest argument instance
     * Warning! Does not update getIONodes() return value
     * refreshIONodes() must be called.
     * @param instance Current argument assignments.
     */
    public synchronized void updateArgumentInstance(IArgumentInstance instance)
    {
		Map<String, IBasicArgumentValue> assignments = instance.getAssigmentsMap();
		Map<String, IBasicArgumentValue> newAssignments = new HashMap<String, IBasicArgumentValue>();
		IArgumentInstance newInstance = ArgumentModelProvider.makeArgumentInstance(newAssignments);
		newAssignments = newInstance.getAssigmentsMap();
		
		availableArguments = model.getAvailableArguments(newInstance);
		
		boolean changed = true;
		while(changed)
		{
		    changed = false;
			for(String argName : availableArguments)
			{
				IBasicArgumentInfo arg = model.getArgumentNameMap().get(argName);
				if(assignments.containsKey(argName) && !newAssignments.containsKey(argName))
				{
				    changed = true;
				    newAssignments.put(
							argName, 
							assignments.get(argName)
							);
				}
				else if(arg.isCompulsary() && !newAssignments.containsKey(argName))
				{
					changed = true;
					newAssignments.put(
							argName, 
							model.getArgumentNameMap().get(argName).getInitialValue()
							);
				}
			}
			
			if(changed)
			{
				availableArguments = model.getAvailableArguments(newInstance);
			}
		}
		
		this.instance = newInstance;
		
		updateInvalidArgs();
	}
    
    private void updateInvalidArgs()
    {
		invalidArgs.clear();
		for(Map.Entry<String, IBasicArgumentValue> argEntry : instance.getAssigmentsMap().entrySet())
		{
		    String arg = argEntry.getKey();
		    IBasicArgumentValue value = argEntry.getValue();
		    
		    IBasicArgumentInfo argInfo = model.getArgumentNameMap().get(arg);
		    if(argInfo instanceof TextArgumentInfo)
		    {
		        TextArgumentInfo textArg = (TextArgumentInfo)argInfo;
		        if(textArg.hasValidationPattern())
		        {
		            String text = ((ITextArgumentValue)value).getValue();
		            if(!textArg.getValidationPattern().matcher(text).matches())
		            {
		                invalidArgs.add(arg);
		            }
		        }
		    }
		}
    }
    
    public synchronized IInputOutputNodeTuple getUpToDateIOFunction()
    {
        return model.getIOFunction(instance);
    }
    
    public synchronized void setIOFunction(IInputOutputNodeTuple ioFunction)
    {
        this.ioFunction = ioFunction;
    }

	public synchronized void addOutputPipe(INodeID id, IPipe pipe) 
	{
		connectedOutputPipes.put( id, pipe );
	}


	public synchronized void addInputPipe(INodeID id, IPipe pipe) 
	{
		connectedInputPipes.put( id, pipe );		
	}
	
	public synchronized void removeOutputPipe(INodeID id)
	{
		connectedOutputPipes.remove(id);
	}
	
	public synchronized void removeInputPipe(INodeID id)
	{
		connectedInputPipes.remove(id);
	}

	public synchronized Map<INodeID, IPipe> getConnectedInputPipes() {
		return connectedInputPipes;
	}
	
	public synchronized Map<INodeID, IPipe> getConnectedOutputPipes() {
		return connectedOutputPipes;
	}
	
	public synchronized boolean inputHasConnectedPipe(INodeID id)
	{
	    return connectedInputPipes.containsKey(id);
	}
	
	public synchronized boolean outputHasConnectedPipe(INodeID id)
	{
	    return connectedOutputPipes.containsKey(id);
	}
	
	public synchronized IPipe getInputPipe(INodeID id)
	{
	    return connectedInputPipes.get(id);
	}
	
	public synchronized IPipe getOutputPipe(INodeID id)
	{
	    return connectedOutputPipes.get(id);
	}
	
	public synchronized boolean isDestroyed() 
	{
		return this.destroyed;
	}
	
    public synchronized IInterfaceClass getLeastUpperBound(String typeParameterName)
    {
        return lowerBounds.get(typeParameterName);
    }
    
    public synchronized void updateLUBs(Map<String, IInterfaceClass> deltaLUBs)
    {
        for(Map.Entry<String, IInterfaceClass> entry : 
            deltaLUBs.entrySet())
        {
            String paramName = entry.getKey();
            IInterfaceClass bound = entry.getValue();
            
            lowerBounds.put(paramName, bound);
        }
    }
    
    public synchronized IInterfaceClass getLeastUpperBound(String typeParameterName, 
            Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLUBs)
    {
		if( deltaLUBs.get(this).containsKey( typeParameterName ))
		{
		    return deltaLUBs.get(this).get(typeParameterName);
		}
		else
		{
		    return lowerBounds.get(typeParameterName);
		}
    }

	public synchronized Map<String, IInterfaceClass> getLowerBounds() {
		return lowerBounds;
	}

	public synchronized void setLowerBounds(Map<String, IInterfaceClass> lowerBounds) {
		this.lowerBounds = lowerBounds;
	}
	
	public synchronized void run( Map<INodeID, IInputPipe> ins, 
									 Map<INodeID, IOutputPipe> outs,
									 Context context)
	{
		
		if(program.isBuiltin() && program instanceof IContextMainpulatingBuiltin)
		{
		    IContextMainpulatingBuiltin builtinProgram = (IContextMainpulatingBuiltin)program;
		    executingInstance = builtinProgram.run(instance, null, ins, outs, context, this);
		}
		else
		{
		    executingInstance = program.run( instance, null, ins, outs );
		}
	}

	public synchronized String getCanonicalName()
	{
	    return program.getCanonicalName();
	}
	
	public synchronized IInterfaceClass getOutputNodeType(INodeID outputNodeID )
	{	    
	    INode outputNode = ioFunction.getOutputNode(outputNodeID);
	    
		ITypeClass typeOutput = outputNode.getType();
		
		IInterfaceClass classFrom;
				
		if( typeOutput instanceof IBasicTypeClass )
		{
			classFrom = ((IBasicTypeClass) typeOutput).getInterfaceClass();
		}
		else	
		{
			IParameteredTypeClass paramTC = (IParameteredTypeClass) typeOutput;
			String templateName =  paramTC.getTemplateName() ;
			
			classFrom = this.lowerBounds.get( templateName );		
		}
		
		return classFrom ;				
	}
	
	public synchronized void addExecutionEndedListener(final IExecutionEndedListener listener)
    {
        executingInstance.addExitNotifier(new IProgramExitListener() {
            public void programExited(IProgramInstance instance)
            {
                listener.programExecutionEnded();
            }
        });
    }
	
	public void kill()
    {
	    executingInstance.kill();
    }

    public Set<String> getInvalidArguments()
    {
        return new HashSet<String>(invalidArgs);
    }
}
