/*******************************************************************************
* 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
 *
 * Created by Steve Zymler
 */
package uk.ac.ic.doc.kevlar.core.framework;

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

import uk.ac.ic.doc.kevlar.api.programs.arguments.IArgumentInstance;
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.IOutputNode;
import uk.ac.ic.doc.kevlar.core.framework.internal.IProgram;
import uk.ac.ic.doc.kevlar.core.framework.internal.RuntimePipe;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.ILoader;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.ILoadingEventListener;
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.ITypeChecker;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeClass;
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.publicUtil.collections.IImmutableSet;
import uk.ac.ic.doc.kevlar.publicUtil.collections.Tuple;

/**
 * @author Daniel Burke and Steve Zymler
 *  
 */
public class Context implements IContext, ILoadingEventListener {
    private long id;

    private boolean destroyed;

    private Framework framework;

    private Map<Long, PipelineProgram> pipeProgMap;

    private Map<Long, Pipe> pipeMap;

    private Map<Long, RuntimePipe> runtimePipeMap;

    /*
     * private long pipeProgID = 0; private long pipeID = 0;
     */
    private IContextListener listener;
    
    private boolean executing = false;

    public Context(long id, Framework framework, IContextListener listener) {
        if (listener == null) {
            throw new IllegalArgumentException("listener is null");
        }

        this.id = id;
        this.destroyed = false;
        this.framework = framework;
        this.pipeProgMap = new HashMap<Long, PipelineProgram>();
        this.pipeMap = new HashMap<Long, Pipe>();
        this.runtimePipeMap = new HashMap<Long, RuntimePipe>();
        this.listener = listener;
    }

    public long getID() {
        return id;
    }

    public boolean equals(Object obj) {
        if (obj instanceof Context) {
            return (id == ((IContext) obj).getID());
        }
        return false;
    }

    public int hashCode() {
        return (int) (id % Integer.MAX_VALUE);
    }

    public IPipelineProgram getProgram(long refID) {
        return pipeProgMap.get(Long.valueOf(refID));
    }

    public IPipe getPipe(long refID) {
        return pipeMap.get(Long.valueOf(refID));
    }

    /**
     * We can call this function to tell a context it has been removed from the
     * framework
     *  
     */
    void destroy() {
        destroyed = true;
    }

    private synchronized boolean pipeAlreadyAdded(long fromProgramID,
            INodeID fromNode, long toProgramID, INodeID toNode) {
        PipelineProgram fromProgram = pipeProgMap.get(Long
                .valueOf(fromProgramID));
        PipelineProgram toProgram = pipeProgMap.get(Long.valueOf(toProgramID));

        IPipe outputPipe = fromProgram.getConnectedOutputPipes().get(fromNode);
        IPipe inputPipe = toProgram.getConnectedInputPipes().get(toNode);

        // atleast one of the pipes doesn't exist (so certainly not same pipe)
        if (outputPipe == null || inputPipe == null) {
            return false;
        }

        // check if the pipes are the same, so programs are connected
        return outputPipe.equals(inputPipe);
    }

    public synchronized void addPipe(long fromProgramID, INodeID fromNode,
            long toProgramID, INodeID toNode, long refID) {

        // validate both programs exist in this context
        if (!pipeProgMap.containsKey(Long.valueOf(fromProgramID))
                || !pipeProgMap.containsKey(Long.valueOf(toProgramID))) {

            throw new IllegalArgumentException("fromProgram and toProgram "
                    + "must be part of this context");
        }

        // validate the reference id is new and isn't in use.
        if (pipeMap.containsKey(Long.valueOf(refID))) {

            throw new IllegalArgumentException(
                    "This id has been used already, please give a new one");
        }

        // validate that the from node and to node belong to the from program
        // and
        // to program
        PipelineProgram fromProgram = pipeProgMap.get(Long
                .valueOf(fromProgramID));
        PipelineProgram toProgram = pipeProgMap.get(Long.valueOf(toProgramID));

        IInputOutputNodeTuple ioTupleFrom = fromProgram.getIONodes();
        IInputOutputNodeTuple ioTupleTo = toProgram.getIONodes();

        if (!ioTupleFrom.getOutputs().contains(fromNode)
                || !ioTupleTo.getInputs().contains(toNode)) {
            throw new IllegalArgumentException("fromNode and toNode "
                    + "must be members of the fromProgram and toProgram "
                    + "output and input sets");
        }

        // validate that there is not already a pipe connecting the programs
        // on the nodes specified.
        if(fromProgram.getConnectedOutputPipes().get(fromNode) != null ||
                toProgram.getConnectedInputPipes().get(toNode) != null)
        {
                throw new IllegalArgumentException(
                "one of the nodes in the programs were already connected by pipe");
        }
        /*if (pipeAlreadyAdded(fromProgramID, fromNode, toProgramID, toNode)) {
            throw new IllegalArgumentException(
                    "nodes in programs were already connected by pipe");
        }*/

        // Call helper method to attempt to add the pipe
        Tuple<TypeCheckResult, IPipe> result = makePipe(refID, fromProgram,
                fromNode, toProgram, toNode);

        if (result.getFirst().isSuccess()) {
            // if addition was successful, tell the listener
            listener.pipeAdded(refID);
        } else {
            // if addition failed, tell teh listener
            listener.pipeAddFailed(refID, result.getFirst().getProblemPipes());
        }
    }

    /**
     * Use this function to make a pipe, and attempt to add it to the context.
     * If the addition preserves the context type invarients, the addition will
     * go ahead and a new pipe and succesfful TypeCheckResult are returned. If
     * the invarients break on adding this pipe, a null pipe will be returned
     * along with a TypeCheckResult that lists all the problem pipes.
     * 
     * Note: A LUB is a template name to Type mapping
     * 
     * @param id
     *            The id to use to refer to this pipe in event callbacks
     * @param fromProgram
     *            The source program of the pipe
     * @param fromNode
     *            The source node of the source program
     * @param toProgram
     *            The destination program of the pipe
     * @param toNode
     *            The destination node of the destination program
     * @return the result of the addition and the new pipe if pipe addition was
     *         successful, otherwise null
     */
    private Tuple<TypeCheckResult, IPipe> makePipe(long id,
            PipelineProgram fromProgram, INodeID fromNode,
            PipelineProgram toProgram, INodeID toNode)
    {
        return makePipe(id, fromProgram, fromNode, toProgram, toNode, new Pipe());
    }
    
    private Tuple<TypeCheckResult, IPipe> makePipe(long id,
            PipelineProgram fromProgram, INodeID fromNode,
            PipelineProgram toProgram, INodeID toNode, Pipe base) {

        // Construct the pipe in the system and add it
        base.setup(id, fromProgram, fromNode, toProgram, toNode);
        Pipe newPipe = base;
        pipeMap.put(Long.valueOf(id), newPipe);

        // We will remove the pipe later if the type checking fails.

        // Since we may need to remove the pipe, to save having to rollback the
        // LUBs,
        // we only make changes to a set of delta LUBs.
        Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLUBs = new HashMap<PipelineProgram, Map<String, IInterfaceClass>>();

        // With the pipe added, find out if the context type invarients hold.
        TypeCheckResult result = floodTypeCheck(newPipe, deltaLUBs);

        if (result.isSuccess()) {
            // If they do, commit the new LUBs to the context programs.
            applyLUBs(deltaLUBs);
        } else {
            // Otherwise destroy the pipe and return a null pipe.
            newPipe.destroy();
            pipeMap.remove(Long.valueOf(newPipe.getID()));
            newPipe = null;
        }

        return new Tuple<TypeCheckResult, IPipe>(result, newPipe);
    }

    /**
     * Used to update the LUBs of programs
     * 
     * @param deltaLUBs
     *            The set of LUBs for each program
     */
    private void applyLUBs(
            Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLUBs) {

        // For each program and LUB set, update the LUBs.
        for (Map.Entry<PipelineProgram, Map<String, IInterfaceClass>> entry : deltaLUBs
                .entrySet()) {

            PipelineProgram program = entry.getKey();
            Map<String, IInterfaceClass> programDeltaLUBs = entry.getValue();

            program.updateLUBs(programDeltaLUBs);
        }
    }

    public synchronized void addProgram(IProgramID id, long refID) {
        IProgramRepository programRep = framework.getProgramRepository();

        IProgram program = programRep.getProgram(id);

        if (program == null) {
            listener.programAddFailed(refID);
            return;
        }

        if (pipeProgMap.containsKey(Long.valueOf(refID))) {
            throw new IllegalArgumentException("This id is already in use");
        }

        PipelineProgram newPipeProgram = new PipelineProgram(refID, id, program);

        pipeProgMap.put(Long.valueOf(refID), newPipeProgram);

        // Show add program message on stdout
        System.out.println("Framework:: Program " + id.getID()
                + " added to context successfully.");

        listener.programAdded(refID);
    }

    public synchronized void removePipe(long pipeID) {

        // validate pipe is in this context
        if (!pipeMap.containsKey(Long.valueOf(pipeID))) {
            throw new IllegalArgumentException(
                    "pipe is not part of this context");
        }

        Pipe pipe = pipeMap.get(Long.valueOf(pipeID));

        // destroy the pipe and notify the listener
        killPipe(pipe);
        listener.pipeRemoved(pipe.getID());
    }

    /**
     * Removes a pipe from the system and updates the types of the context so
     * that the invarients hold
     * 
     * @param pipe
     *            the pipe to remove from the system
     */
    private void killPipe(Pipe pipe) {
        // We need to start an update to fix the LUBs
        // This update starts at the destination program of the pipe
        // and recurses across the pipeline
        PipelineProgram toProgram = (PipelineProgram) pipe.getToProgram();

        if (pipe.getToNode().getType() instanceof IParameteredTypeClass) {
            // Consider the complex case first,
            // The to node is a templated node
            String paramName = ((IParameteredTypeClass) pipe.getToNode().getType())
                    .getTemplateName();

            // remove the program
            pipe.destroy();
            pipeMap.remove(Long.valueOf(pipe.getID()));

            // Update the LUBs for all effected programs
            updateLUB(toProgram, paramName);
        } else {

            // remove the program
            pipe.destroy();
            pipeMap.remove(Long.valueOf(pipe.getID()));
        }
    }

    private void killProgram(PipelineProgram program) {
        for (IPipe p : new HashSet<IPipe>(program.getConnectedOutputPipes().values())) {
            Pipe pipe = (Pipe) p;

            killPipe(pipe);
        }

        for (IPipe p : new HashSet<IPipe>(program.getConnectedInputPipes().values())) {
            Pipe pipe = (Pipe) p;

            killPipe(pipe);
        }

        program.destroy();
        pipeProgMap.remove(Long.valueOf(program.getID()));
    }

    public synchronized void removeProgram(long progID) {
        if (!pipeProgMap.containsKey(Long.valueOf(progID))) {
            throw new IllegalArgumentException(
                    "program is not part of this context");
        }

        PipelineProgram program = pipeProgMap.get(Long.valueOf(progID));

        Set<IPipe> pipes = new HashSet<IPipe>(program
                .getConnectedInputPipes().values());

        pipes.addAll(program.getConnectedOutputPipes().values());

        killProgram(program);

        for (IPipe pipe : pipes) {
            listener.pipeRemoved(pipe.getID());
        }

        listener.programRemoved(program.getID());
    }

    private void handleUpdatedProgram(PipelineProgram program) {
        IInputOutputNodeTuple oldIOFunction = program.getIONodes();
        IInputOutputNodeTuple newIOFunction = program.getUpToDateIOFunction();

        boolean changed = false;
        for (IInputNode input : oldIOFunction.getInputs()) {
            
            if(program.getConnectedInputPipes().get(input) == null)
            {
                continue;
            }
            
            if (!newIOFunction.getInputs().contains(input.getNodeID())) {
                changed = true;
                break;
            }
            
            if(!newIOFunction.getInputNode(input.getNodeID()).equals(input))
            {
                changed = true;
                break;
            }
        }

        if (!changed) {
            for (IOutputNode output : oldIOFunction.getOutputs()) {
                
                if(program.getConnectedOutputPipes().get(output) == null)
                {
                    continue;
                }
                
                if (!newIOFunction.getOutputs().contains(output.getNodeID())) {
                    changed = true;
                    break;
                }
                
                if(!newIOFunction.getOutputNode(output.getNodeID()).equals(output))
                {
                    changed = true;
                    break;
                }
            }
        }

        if (!changed) {
            program.setIOFunction(newIOFunction);
            listener.programStateChanged(program.getID());
            return;
        }

        List<SavedPipeTuple> savedInputs = new ArrayList<SavedPipeTuple>();
        List<SavedPipeTuple> savedOutputs = new ArrayList<SavedPipeTuple>();

        for (IPipe p : program.getConnectedOutputPipes().values()) {
            Pipe pipe = (Pipe) p;

            if (newIOFunction.getOutputs().contains(pipe.getFromNodeID())) {

                savedOutputs.add(new SavedPipeTuple(pipe.getID(),
                        (PipelineProgram) pipe.getToProgram(), pipe
                                .getFromNodeID(), pipe.getToNodeID(), pipe));
                killPipe(pipe);
            } else {
                killPipe(pipe);
                listener.pipeLost(pipe.getID());
            }
        }

        for (IPipe p : program.getConnectedInputPipes().values()) {
            Pipe pipe = (Pipe) p;

            if (newIOFunction.getInputs().contains(pipe.getToNodeID())) {
                savedInputs.add(new SavedPipeTuple(pipe.getID(),
                        (PipelineProgram) pipe.getFromProgram(), pipe
                                .getFromNodeID(), pipe.getToNodeID(), pipe));
                killPipe(pipe);
            } else {
                killPipe(pipe);
                listener.pipeLost(pipe.getID());
            }
        }

        program.setIOFunction(newIOFunction);

        for (SavedPipeTuple pipe : savedInputs) {
            Tuple<TypeCheckResult, IPipe> result = makePipe(pipe.getId(), pipe
                    .getOtherProgram(), pipe.getFromNodeID(), program, pipe
                    .getToNodeID(), pipe.getPipe());

            if (!result.getFirst().isSuccess()) {
                listener.pipeFailed(pipe.getPipe().getID());
            }
        }

        for (SavedPipeTuple pipe : savedOutputs) {
            Tuple<TypeCheckResult, IPipe> result = makePipe(pipe.getId(),
                    program, pipe.getFromNodeID(), pipe.getOtherProgram(), pipe
                            .getToNodeID(), pipe.getPipe());

            if (!result.getFirst().isSuccess()) {
                listener.pipeFailed(pipe.getPipe().getID());
            }
        }
        
        listener.programStateChanged(program.getID());
    }

    public synchronized void addArgument(String argName, long progID) {
        if (!pipeProgMap.containsKey(Long.valueOf(progID))) {
            throw new IllegalArgumentException("prog is not in this context");
        }

        PipelineProgram program = pipeProgMap.get(Long.valueOf(progID));

        program.addArgument(argName);

        handleUpdatedProgram(program);
    }

    public synchronized void updateArgumentInstance(IArgumentInstance instance,
            long progID) {
        if (!pipeProgMap.containsKey(Long.valueOf(progID))) {
            throw new IllegalArgumentException("prog is not in this context");
        }

        PipelineProgram program = pipeProgMap.get(Long.valueOf(progID));

        program.updateArgumentInstance(instance);

        handleUpdatedProgram(program);
    }

    public synchronized boolean isDestroyed() {
        return this.destroyed;
    }

    public synchronized void typeTreeUpdated(ILoader source) {
        // Does nothing
    }

    public synchronized void availableProgramsUpdated(ILoader source) {
        return;
        /*
        Set<IProgramIDDescription> progSet = framework.getProgramRepository()
                .getProgramDescriptions();
        
        Set<PipelineProgram> programs = new HashSet<PipelineProgram>(pipeProgMap.values());

        Set<IProgramID> progIDs = new HashSet<IProgramID>();
        for(IProgramIDDescription progIDDescrip : progSet)
        {
            progIDs.add(progIDDescrip.getProgramID());
        }
        
        for (PipelineProgram program : programs) {
            if (!progIDs.contains(program.getProgramID())) {
                Collection<IPipe> pipes = new HashSet<IPipe>(program
                        .getConnectedInputPipes().values());

                pipes.addAll(program.getConnectedOutputPipes().values());

                killProgram(program);

                for (IPipe pipe : pipes) {
                    listener.pipeLost(pipe.getID());
                }

                listener.programFailed(program.getID());
            }
        }*/
    }

    /**
     * This function may be called at any point to validate the entire set of
     * type invarients for a single program
     * 
     * @param program
     *            to verify
     * @throws AssertionError
     *             if the invarient is dead
     */
    private void calculateVerificationLUB(PipelineProgram program) {

        IImmutableSet<IInputNode> inputs = program.getIONodes().getInputs();

        Set<String> checked = new HashSet<String>();

        // For each input node, if the node is a template name
        // verify the LUB associated with that template name is correct
        for (IInputNode inputNode : inputs) {
            ITypeClass input = inputNode.getType();

            if (input instanceof IParameteredTypeClass) {
                String templateName = ((IParameteredTypeClass) input)
                        .getTemplateName();
                if (!checked.contains(templateName)) {
                    calculateVerificationLUB(program, templateName);
                    checked.add(templateName);
                }
            }
        }
    }

    /**
     * This function may be called at any point to validate the entire set of
     * type invarients for a single template variable for a single program
     * 
     * @param program
     *            to verify
     * @param paramName
     *            to verify
     * @throws AssertionError
     *             if the invarient is dead
     */
    private void calculateVerificationLUB(PipelineProgram program,
            String paramName) {

        ITypeChecker typeChecker = framework.getTypeChecker();

        IImmutableSet<IInputNode> inputs = program.getIONodes().getInputs();
        Set<IInterfaceClass> types = new HashSet<IInterfaceClass>();

        // For each input node of the program that shares the template name
        for (IInputNode inputNode : inputs) {
            ITypeClass input = inputNode.getType();
            if (input instanceof IBasicTypeClass) {
                continue;
            }

            if (!((IParameteredTypeClass) input).getTemplateName().equals(
                    paramName)) {
                continue;
            }

            // get the pipe connected to that input and infer the type
            // of the input
            Pipe inputPipe = (Pipe) program.getConnectedInputPipes().get(
                    inputNode.getNodeID());

            IInterfaceClass typeOfPipe = typeChecker.inferTypeOfNode(null,
                    program.getLowerBounds(), inputPipe.getFromNode());

            // If the type is not unrestricted, add it to the set of type
            // constraints
            if (typeOfPipe != null) {
                types.add(typeOfPipe);
            }
        }

        // Calculate the actual LUB for the paramName of the program
        IInterfaceClass calcLUB = typeChecker.getLeastUpperBound(types);
        IInterfaceClass officialLUB = program.getLeastUpperBound(paramName);

        // They should be equal or both null (unrestricted type)
        if (calcLUB != null && !calcLUB.equals(officialLUB) || calcLUB == null
                && officialLUB != null) {
            throw new AssertionError(
                    "Meltdown Imminent: LUB failure on parameter: " + paramName);
        }
    }

    /**
     * Used to cause a cascading update to LUBs of programs in the context
     * 
     * @param program
     *            the program that may have a template name with an incurrect
     *            LUB
     * @param paramName
     *            the template name that may have an incorrect LUB
     */
    private void updateLUB(PipelineProgram program, String paramName) {
        ITypeChecker typeChecker = framework.getTypeChecker();

        IImmutableSet<IInputNode> inputs = program.getIONodes().getInputs();
        IImmutableSet<IOutputNode> outputs = program.getIONodes().getOutputs();
        Set<IInterfaceClass> types = new HashSet<IInterfaceClass>();

        // For every input node with a matching template name
        for (IInputNode inputNode : inputs) {
            ITypeClass input = inputNode.getType();
            if (input instanceof IBasicTypeClass) {
                continue;
            }

            if (!((IParameteredTypeClass) input).getTemplateName().equals(
                    paramName)) {
                continue;
            }

            // Caclulate the type of data coming into the input node
            Pipe inputPipe = (Pipe) program.getConnectedInputPipes().get(
                    inputNode.getNodeID());
            if(inputPipe == null)
            {
                continue;
            }

            IInterfaceClass typeOfPipe = typeChecker.inferTypeOfNode(null, program.getLowerBounds(), inputPipe.getFromNode());

            // If the type is not unrestricted, add it to the set of
            // constraining types
            if (typeOfPipe != null) {
                types.add(typeOfPipe);
            }
        }

        // Calculate the new LUB based on the current types coming in on input
        // nodes
        IInterfaceClass newLUB = typeChecker.getLeastUpperBound(types);
        IInterfaceClass oldLUB = program.getLeastUpperBound(paramName);

        // If there is no change in LUB, the algorithm terminates, the base
        // case.
        if (newLUB != null && newLUB.equals(oldLUB) || newLUB == null
                && oldLUB == null) {
            return;
        }

        // Otherwise update the LUB based on the input types
        program.getLowerBounds().put(paramName, newLUB);

        // Now to cascade the effects onto the outputs
        // For each output with a matching paramname
        for (IOutputNode outputNode : outputs) {
            ITypeClass output = outputNode.getType();
            if (output instanceof IBasicTypeClass) {
                continue;
            }

            if (!((IParameteredTypeClass) output).getTemplateName().equals(
                    paramName)) {
                continue;
            }

            // Get the destination of the output pipe
            Pipe outputPipe = (Pipe) program.getConnectedOutputPipes().get(
                    outputNode.getNodeID());

            if(outputPipe == null)
            {
                continue;
            }
            
            IInputNode nextInputNode = outputPipe.getToNode();
            ITypeClass nextInput = nextInputNode.getType();

            // In the simple case, the algorithm terminates
            if (nextInput instanceof IBasicTypeClass) {
                continue;
            }

            // For parameterized output destination nodes,
            // we recurse to flood the update until equilibrium
            updateLUB((PipelineProgram) outputPipe.getToProgram(),
                    ((IParameteredTypeClass) nextInput).getTemplateName());
        }
    }

    /**
     * When a new pipe is added to the context or the type of the data flowing
     * through a pipe changes, this function is used to ask, what does this
     * change? It will test to see if the context type invarients have broken
     * 
     * @param pipe
     *            the pipe that has been added or had its input type changed
     * @param deltaLUBs
     *            A map to store the LUBs that change as a result of the added
     *            pipes
     * @return a result that is successful iff the invarients remain intact. If
     *         it not successful, the problem pipes are returned
     */
    private TypeCheckResult floodTypeCheck(Pipe pipe,
            Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLUBs) {

        // First check if the pipe meets the basic type requirements of
        // the destination node.
        if (!typeCheckLocal(pipe, deltaLUBs)) {

            // If not, then the added pipe has already caused a problem
            // Return the result
            List<IPipe> problemPipes = new ArrayList<IPipe>();
            problemPipes.add(pipe);
            return new TypeCheckResult(problemPipes);
        }

        // If the destination node of the pipe is not
        // a templated node, then the addition is successful
        INode toNode = pipe.getToNode();

        if (toNode.getType() instanceof IBasicTypeClass) {
            return new TypeCheckResult();
        }

        // otherwise we have see how changing the input node type
        // will effect other parts of the pipeline
        PipelineProgram toProgram = (PipelineProgram) pipe.getToProgram();
        String toNodeParamName = ((IParameteredTypeClass) toNode.getType())
                .getTemplateName();

        // Cacluate the new lub for the program node the pipe feeds into
        IInterfaceClass newLUB = calculateNewLUB(pipe, deltaLUBs);

        // base case is when the new lub and old lub are identical.
        // the addition is successful, return.
        Map<String, IInterfaceClass> toProgramDeltaLUBs = deltaLUBs
                .get(toProgram);

        IInterfaceClass oldLUB = getLUB(toProgramDeltaLUBs, toProgram
                .getLowerBounds(), toNodeParamName);

        if (newLUB != null && newLUB.equals(oldLUB) || newLUB == null
                && oldLUB == null) {
            return new TypeCheckResult();
        }

        // If there is a change, record in deltaLUBs the change.
        // The changes are not recorded in the program LUBs so it is easy to
        // rollback the pipe addition when things blow up.
        if (toProgramDeltaLUBs == null) {
            toProgramDeltaLUBs = new HashMap<String, IInterfaceClass>();
        }

        toProgramDeltaLUBs.put(toNodeParamName, newLUB);

        deltaLUBs.put(toProgram, toProgramDeltaLUBs);

        // Find out which output pipes are effected by the changes
        List<IPipe> problemPipes = new ArrayList<IPipe>();
        for (INode node : toProgram.getIONodes().getOutputs()) {
            ITypeClass nodeTypeClass = node.getType();
            if (nodeTypeClass instanceof IBasicTypeClass) {
                continue;
            }

            IParameteredTypeClass paramTypeClass = (IParameteredTypeClass) nodeTypeClass;

            if (!paramTypeClass.getTemplateName().equals(toNodeParamName)) {
                continue;
            }

            // For each output pipe with a matching template name,
            // recurse to see if this pipe type checks.
            Pipe outPipe = (Pipe) toProgram.getConnectedOutputPipes().get(
                    node.getNodeID());

            if (outPipe != null) {
                // The deltaLUBs are accumulated in the recursion into the same
                // map
                // This is important when the pipeline goes in a loop
                TypeCheckResult result = floodTypeCheck(outPipe, deltaLUBs);

                // If an output pipe dies as a result of the new pipe,
                // 
                if (!result.isSuccess()) {
                    problemPipes.addAll(result.getProblemPipes());
                }
            }
        }

        // If any problems were caused by the new pipe,
        // return a failing result. otherwise return a successful one.
        if (problemPipes.isEmpty()) {
            return new TypeCheckResult();
        } else {
            return new TypeCheckResult(problemPipes);
        }
    }

    private IInterfaceClass calculateNewLUB(Pipe pipe,
            Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLUBs) {
        ITypeChecker typeChecker = framework.getTypeChecker();

        INode toNode = pipe.getToNode();
        INode fromNode = pipe.getFromNode();
        PipelineProgram toProgram = (PipelineProgram) pipe.getToProgram();
        PipelineProgram fromProgram = (PipelineProgram) pipe.getFromProgram();

        IInterfaceClass typeOfPipe = typeChecker.inferTypeOfNode(deltaLUBs
                .get(fromProgram), fromProgram.getLowerBounds(), fromNode);

        Set<IInterfaceClass> types = new HashSet<IInterfaceClass>();

        if (typeOfPipe != null) {
            types.add(typeOfPipe);
        }

        String toNodeParamName = ((IParameteredTypeClass) toNode.getType())
                .getTemplateName();

        IInterfaceClass currentBound = toProgram
                .getLeastUpperBound(toNodeParamName);

        if (currentBound != null) {
            types.add(currentBound);
        }

        return typeChecker.getLeastUpperBound(types);
    }

    private boolean typeCheckLocal(Pipe pipe,
            Map<PipelineProgram, Map<String, IInterfaceClass>> deltaLowerBounds) {
        ITypeChecker typeChecker = framework.getTypeChecker();

        Map<String, IInterfaceClass> fromProgramDeltaLUBs = deltaLowerBounds
                .get(pipe.getFromProgram());

        Map<String, IInterfaceClass> fromProgramBaseLUBs = pipe
                .getFromProgram().getLowerBounds();

        return typeChecker.isPipeable(fromProgramDeltaLUBs,
                fromProgramBaseLUBs, pipe.getFromNode(), pipe.getToNode());
    }

    private boolean typeCheckLocal(Pipe pipe) {
        ITypeChecker typeChecker = framework.getTypeChecker();

        Map<String, IInterfaceClass> fromProgramBaseLUBs = pipe
                .getFromProgram().getLowerBounds();

        return typeChecker.isPipeable(fromProgramBaseLUBs, pipe.getFromNode(),
                pipe.getToNode());
    }

    private IInterfaceClass getLUB(Map<String, IInterfaceClass> deltaLUBs,
            Map<String, IInterfaceClass> baseLUBs, String paramName) {
        if (deltaLUBs != null && deltaLUBs.containsKey(paramName)) {
            return deltaLUBs.get(paramName);
        }

        return baseLUBs.get(paramName);
    }

    private void validateEntireContext() {
        for (PipelineProgram program : pipeProgMap.values()) {
            calculateVerificationLUB(program);
        }

        for (Pipe pipe : pipeMap.values()) {
            typeCheckLocal(pipe);
        }
    }
    
    public synchronized boolean isExecuting()
    {
        return executing;
    }

    public synchronized void execute()
    {
        // validate entire context before execution
        //TODO: make validateEntireContext bugfree !!
        //validateEntireContext();


        Map<PipelineProgram, Map<INodeID, IInputPipe>> inputs = new HashMap<PipelineProgram, Map<INodeID, IInputPipe>>();

        Map<PipelineProgram, Map<INodeID, IOutputPipe>> outputs = new HashMap<PipelineProgram, Map<INodeID, IOutputPipe>>();

        for (IPipe compileTimePipe : pipeMap.values()) {
            Map<INodeID, IInputPipe> inputMap = inputs.get(compileTimePipe
                    .getToProgram());
            Map<INodeID, IOutputPipe> outputMap = outputs.get(compileTimePipe
                    .getFromProgram());

            if (null == inputMap) {
                inputMap = new HashMap<INodeID, IInputPipe>();
            }
            if (null == outputMap) {
                outputMap = new HashMap<INodeID, IOutputPipe>();
            }

            RuntimePipe runtimePipe = new RuntimePipe(compileTimePipe.getID());
            this.runtimePipeMap.put(new Long(runtimePipe.getID()), runtimePipe);

            inputMap.put(compileTimePipe.getToNodeID(), runtimePipe
                    .getInputPipe());
            inputs.put((PipelineProgram) compileTimePipe.getToProgram(),
                    inputMap);

            outputMap.put(compileTimePipe.getFromNodeID(), runtimePipe
                    .getOutputPipe());
            outputs.put((PipelineProgram) compileTimePipe.getFromProgram(),
                    outputMap);
        }

        for (PipelineProgram compileTimeProgram : pipeProgMap.values()) {
            compileTimeProgram.run(inputs.get(compileTimeProgram), outputs
                    .get(compileTimeProgram), this);
        }
        
        executing = true;
    }

    /*
     * Old type checker
     * 
     * /** kills a pipe that no longer type checks connected to the node.
     * recalculates any LUB of this node right-flood fills the LUB across
     * directly related parameterized types.
     * 
     * @param program the program the node belongs to @param inputNodeID te node
     * we want to typecheck @return a list of pipes that were broken, otherwise
     * empty list and successful bool / private TypeCheckResult
     * typeCheck(PipelineProgram program, INodeID inputNodeID) {
     * 
     * return typeCheckHelper( program, inputNodeID, new HashMap
     * <PipelineProgram, Map <String, IInterfaceClass>>() ); }
     * 
     * 
     * 
     * private TypeCheckResult typeCheckHelper( PipelineProgram program, INodeID
     * inputNodeID, Map <PipelineProgram, Map <String, IInterfaceClass>>
     * deltaLowerBounds) { /* First, get pipe connected - if there is one,
     * pipe.getType(), validate is pipable to Nodes LUB (Templated) or
     * BasicType.
     * 
     * IF templated node, recalculate LUB for that parameter foreach(Output
     * pipe, uses that parameter) recurse on pipe's output end. /
     * if(program.getConnectedInputPipes().containsKey(inputNodeID)) {
     * ITypeChecker typeChecker = framework.getTypeChecker() ;
     * 
     * IPipe p = program.getConnectedInputPipes().get(inputNodeID); Pipe pipe =
     * (Pipe)p;
     * 
     * INode fromNode =
     * p.getFromProgram().getIONodes().getOutputNode(p.getFromNodeID());
     * 
     * INode toNode = program.getIONodes().getInputNode(inputNodeID); // Getting
     * delta lubs for the output end of the pipe for the // input node we're
     * looking at. Map <String, IInterfaceClass> fromProgramDeltaLUBs =
     * deltaLowerBounds.get(p.getFromProgram());
     * 
     * Map <String, IInterfaceClass> fromProgramBaseLUBs =
     * pipe.getFromProgram().getLowerBounds();
     * 
     * Map <String, IInterfaceClass> toProgramDeltaLUBs =
     * deltaLowerBounds.get(program);
     * 
     * Map <String, IInterfaceClass> toProgramBaseLUBs =
     * program.getLowerBounds();
     * 
     * if(typeChecker.isPipeable(fromProgramDeltaLUBs, fromProgramBaseLUBs,
     * fromNode, toNode)) { IInterfaceClass typeOfPipe =
     * typeChecker.inferTypeOfNode( fromProgramDeltaLUBs, fromProgramBaseLUBs,
     * fromNode);
     * 
     * ITypeClass typeClass = toNode.getType(); if( typeClass instanceof
     * IParameteredTypeClass ) { // Algo note : V.effecient for adding, but
     * doesn't work for // removing IParameteredTypeClass paramTypeOfNode =
     * (IParameteredTypeClass) typeClass;
     * 
     * Set <IInterfaceClass> types = new HashSet <IInterfaceClass>();
     * 
     * types.add(typeOfPipe);
     * 
     * if( toProgramDeltaLUBs. containsKey( paramTypeOfNode.getTemplateName() )) {
     * types.add( toProgramDeltaLUBs.get(paramTypeOfNode.getTemplateName()) ); }
     * else { types.add(
     * toProgramBaseLUBs.get(paramTypeOfNode.getTemplateName()) ); }
     * 
     * IInterfaceClass typeNewLUB = typeChecker.getLeastUpperBound( types );
     * 
     * if( null != typeNewLUB ) { if( !typeNewLUB.equals(
     * toProgramDeltaLUBs.get( paramTypeOfNode.getTemplateName() ))) {
     * toProgramDeltaLUBs.put( paramTypeOfNode.getTemplateName(), typeNewLUB );
     * for( INode node : program.getIONodes().getOutputs()) { ITypeClass
     * nodeTypeClass = node.getType(); if( typeClass instanceof
     * IParameteredTypeClass ) { IParameteredTypeClass paramTypeClass =
     * (IParameteredTypeClass) nodeTypeClass ;
     * 
     * List <TypeCheckResult> resultList = new ArrayList <TypeCheckResult>();
     * 
     * if( paramTypeClass.getTemplateName()
     * .equals(paramTypeOfNode.getTemplateName())) { IPipe outIPipe =
     * program.getConnectedInputPipes().get(node.getNodeID()); Pipe outPipe =
     * (Pipe)outIPipe;
     * 
     * resultList.add( typeCheckHelper((PipelineProgram) outPipe.getToProgram(),
     * outPipe.getToNodeID(), deltaLowerBounds ) ); }
     * 
     * 
     * List <IPipe> problemPipes = new ArrayList <IPipe>(); for( TypeCheckResult
     * result : resultList ) { if( !result.isSuccess() ) { problemPipes.addAll(
     * result.getProblemPipes() ); } }
     * 
     * TypeCheckResult finalResult; if( !problemPipes.isEmpty() ) { finalResult =
     * new TypeCheckResult( problemPipes ); } else // successful { finalResult =
     * new TypeCheckResult();
     * 
     * //put it in as base toProgramBaseLUBs.put(
     * paramTypeOfNode.getTemplateName(), typeNewLUB ); } return finalResult; } } } }
     * else { List <IPipe> problemPipes = new ArrayList <IPipe>();
     * problemPipes.add(p);
     * 
     * return new TypeCheckResult( problemPipes ); } } return new
     * TypeCheckResult(); } else { List <IPipe> problemPipes = new ArrayList
     * <IPipe>(); problemPipes.add(p);
     * 
     * return new TypeCheckResult( problemPipes ); } } return new
     * TypeCheckResult(); }
     */

}
