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

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import uk.ac.ic.doc.kevlar.Kevlar;
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.IInputOutputNodeTuple;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IOutputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.internal.iomapping.NodeID;
import uk.ac.ic.doc.kevlar.core.framework.IContext;
import uk.ac.ic.doc.kevlar.core.framework.IContextListener;
import uk.ac.ic.doc.kevlar.core.framework.IFramework;
import uk.ac.ic.doc.kevlar.core.framework.IPipe;
import uk.ac.ic.doc.kevlar.core.framework.IPipelineProgram;
import uk.ac.ic.doc.kevlar.core.framework.IProgramIDDescription;
import uk.ac.ic.doc.kevlar.publicInterfaces.INodeID;
import uk.ac.ic.doc.kevlar.publicUtil.dom.XMLUtil;

/**
 * @author Daniel Burke
 *
 */
public class HialContext implements IHialContext, IContextListener {


    private IFramework framework;
    private IContext context;
    private IHialContextListener listener;
    private boolean destroyed;
    private boolean executing = false;
    private AtomicInteger completedCount = new AtomicInteger(0);
    
    
    private IProgramIDDescription show;
    private Set<Long> secretPrograms = new HashSet<Long>();
    private Set<Long> secretPipes = new HashSet<Long>();
    
    
    
    private Map<IRefObject, Long> ref2id = new HashMap<IRefObject, Long>();
    private Map<IRefObject, IHialProgram> programs = new HashMap<IRefObject, IHialProgram>();
    private Map<IRefObject, IHialPipe> pipes = new HashMap<IRefObject, IHialPipe>();
    private Map<Long, IRefObject> id2ref = new HashMap<Long, IRefObject>();
    private long currentID;
    

    public HialContext(IFramework framework, IHialContextListener listener, IProgramIDDescription show) {
        this.framework = framework;
        this.listener = listener;
        this.show = show;
        this.context = framework.createNewContext(this);
    }
    
    public synchronized void destroy()
    {
        framework.removeContext(context);
        this.context = null;
        destroyed = true;
    }

    public synchronized boolean isDestroyed()
    {
    	return destroyed;
    }
    
    

    
    public synchronized void addPipe(IRefObject fromProgramRefID, INodeID fromNodeID,
            IRefObject toProgramRefID, INodeID toNodeID, IRefObject refID)
    {
        ref2id.put(refID, Long.valueOf(currentID));
        id2ref.put(Long.valueOf(currentID), refID);
        currentID++;
        
        long fromProgramID = convert(fromProgramRefID);
        long toProgramID = convert(toProgramRefID);
        
        context.addPipe(fromProgramID, fromNodeID, toProgramID, toNodeID, currentID - 1);
    }
    
    public synchronized void addProgram(
    		IProgramIDDescription description, 
			IRefObject refID) 
    {
        ref2id.put(refID, Long.valueOf(currentID));
        id2ref.put(Long.valueOf(currentID), refID);
        currentID++;
        
        context.addProgram(description.getProgramID(), currentID - 1);
    }
    
    public synchronized IHialPipe getPipe(IRefObject refID)
    {
        return pipes.get(refID);
    }
    
    public synchronized IHialProgram getProgram(IRefObject refID)
    {
        return programs.get(refID);
    }
    
    public synchronized void removePipe(IRefObject pipeRefID)
    {
        context.removePipe( convert(pipeRefID) );       
    }
    
    public synchronized void removeProgram(IRefObject programRefID)
    {
        context.removeProgram( convert(programRefID) );  
    }
    
    public synchronized void removeAll()
    {
    	Set<IRefObject> pipeRefs = new HashSet<IRefObject>(pipes.keySet());
    	for(IRefObject pipeRef : pipeRefs)
    	{
    		removePipe(pipeRef);
    	}
    	
    	Set<IRefObject> programRefs = new HashSet<IRefObject>(programs.keySet());
    	for(IRefObject programRef : programRefs)
    	{
    		removeProgram(programRef);
    	}
    }
    

    
    
    
    
    private final boolean verifyIsNotSecret(long refID)
    {
        boolean secret = isSecret(refID);
        if(secret && Kevlar.DEBUG)
        {
            System.err.println("Unepxected secret id");
            System.err.println( Arrays.deepToString(Thread.currentThread().getStackTrace() ) );
        }
        return !secret;
    }
    
    private final boolean isSecret(long refID)
    {
        if(secretPrograms.size() == 0 && secretPipes.size() == 0)
        {
            return false; 
        }

        return secretPrograms.contains(Long.valueOf(refID)) ||
        	secretPipes.contains(Long.valueOf(refID));
    }
	
	public synchronized void pipeAdded(long refID)
	{
	    if(isSecret(refID))
	    {
	        return;
	    }
	    
	    IRefObject ref = convert(refID);
	    pipes.put(ref, new HialPipe(this, context, refID));
		listener.pipeAdded(ref);
	}
	
	public synchronized void pipeAddFailed(long refID, List<IPipe> problemPipes)
	{
	    if(!verifyIsNotSecret(refID))
	    {
	        return;
	    }
		listener.pipeAddFailed(removeFromSystem(refID), problemPipes);
	}
	
	public synchronized void pipeFailed(long refID)
	{
	    if(!verifyIsNotSecret(refID))
	    {
	        return;
	    }
		listener.pipeFailed(removeFromSystem(refID));
	}
	
    public void pipeLost(long refID) {
	    if(!verifyIsNotSecret(refID))
	    {
	        return;
	    }
		listener.pipeLost(removeFromSystem(refID));
    }
	
	
	public synchronized void pipeRemoved(long refID)
	{
	    if(isSecret(refID))
	    {
	        secretPipes.remove(Long.valueOf(refID));
	        return;
	    }
		listener.pipeRemoved(removeFromSystem(refID));
	}
	
	public synchronized void programAdded(long refID)
	{
	    if(isSecret(refID))
	    {
	        return;
	    }
	    IRefObject ref = convert(refID);
	    programs.put(ref, new HialProgram(this, context, refID));
		listener.programAdded(ref);
	}
	
	public synchronized void programAddFailed(long refID)
	{
	    if(!verifyIsNotSecret(refID))
	    {
	        return;
	    }
		listener.programAddFailed(removeFromSystem(refID));
	}
	
	public synchronized void programFailed(long refID)
	{
	    if(!verifyIsNotSecret(refID))
	    {
	        return;
	    }
		listener.programFailed(removeFromSystem(refID));

	}
	
	public synchronized void programRemoved(long refID)
	{  
	    if(isSecret(refID))
	    {
	        secretPrograms.remove(Long.valueOf(refID));
	        return;
	    }
		listener.programRemoved(removeFromSystem(refID));
	}
	
	public synchronized void programStateChanged(long refID)
	{
	    if(isSecret(refID))
	    {
	        return;
	    }
		listener.programStateChanged(convert(refID));
	}
	
	private IRefObject removeFromSystem(long refID)
	{
	    IRefObject ref = convert(refID);
	    ref2id.remove(ref);
	    id2ref.remove(Long.valueOf(refID));
	    programs.remove(ref);
	    pipes.remove(ref);
	    return ref;
	}
	
	long convert(IRefObject ref)
	{
	    if(!ref2id.containsKey(ref))
	    {
	        throw new IllegalArgumentException("Reference object : " + ref + " is not in the system");
	    }
	    return ref2id.get(ref).longValue();
	}
	
	IRefObject convert(long id)
	{
	    if(!id2ref.containsKey(Long.valueOf(id)))
	    {
	        throw new IllegalArgumentException("Reference object : " + id + " is not in the system");
	    }
	    return id2ref.get(Long.valueOf(id));
	}

	void putContext(
			StringBuilder text, 
			IGUIContextSaver guiCallback, 
			int depth)
	{
	
		synchronized(context)
		{
			depth = XMLUtil.putOpenElem(text, "context", depth);
			
			depth = XMLUtil.putOpenElem(text, "gui-context", depth);
			
			guiCallback.putGUIContext(text, depth);
			
			depth = XMLUtil.putCloseElem(text, "gui-context", depth);
			
			depth = XMLUtil.putOpenElem(text, "programs", depth);
			
			for(IHialProgram program : programs.values())
			{
				((HialProgram)program).putProgram(text, guiCallback, depth);
			}
			
			depth = XMLUtil.putCloseElem(text, "programs", depth);
	
			depth = XMLUtil.putOpenElem(text, "pipes", depth);
			
			for(IHialPipe pipe : pipes.values())
			{
				((HialPipe)pipe).putPipe(text, guiCallback, depth);
			}
			
			depth = XMLUtil.putCloseElem(text, "pipes", depth);
			
			depth = XMLUtil.putCloseElem(text, "context", depth);
		}
	}
		
	public Object getSyncRoot()
	{
	      return context;
	}
	
	private void addShow(IRefObject programRef, INodeID outputNodeID, String showName, boolean openOnFirst)
	{
	    long showID = currentID;
	    currentID++;
	    secretPrograms.add(Long.valueOf(showID));
	    context.addProgram(show.getProgramID(), showID);
	    
	    if(showName != null)
	    {
	        context.addArgument("Name", showID);
	        IPipelineProgram program = context.getProgram(showID);
	        IArgumentInstance args = program.getArgumentInstance();
	        args.getAssigmentsMap().put("Name", ArgumentModelProvider.makeTextValue(showName));
	        context.updateArgumentInstance(args, showID);
	    }
	    
	    if(openOnFirst)
	    {
	        context.addArgument("Open-lazily", showID);
	    }
	    
	    long pipeID = currentID;
	    currentID++;
	    secretPipes.add(Long.valueOf(pipeID));
	    context.addPipe(convert(programRef), outputNodeID, showID, new NodeID("Data"), pipeID);
	}
	
	private void addShows()
	{
	    // TODO: deb02 : fix so that execution only terminates when all show programs have terminated too.
	    if(programs.size() == 1)
	    {
	        IHialProgram program = null;
	        IRefObject programRef = null;
	        for(Map.Entry<IRefObject, IHialProgram> p : programs.entrySet())
	        {
	            program = p.getValue();
	            programRef = p.getKey();
	            break;
	        }
	        
	        IInputOutputNodeTuple ioNodes = program.getIONodes();
	        int count = 0;
	        INodeID output = null;
	        for(IOutputNode node : ioNodes.getOutputs())
	        {
	            if(!node.getName().equals("Error"))
	            {
	                count++;
	                output = node.getNodeID();
	            }
	        }
	        
	        if(count == 1)
	        {
	            if(program.getOutputPipe(output) == null)
	            {
	                addShow(programRef, output, null, false);
	            }
	        }
	    }
	    
	    INodeID errorNodeID = new NodeID("Error");
        for(Map.Entry<IRefObject, IHialProgram> p : programs.entrySet())
        {
            IHialProgram program = p.getValue();
            IRefObject programRef = p.getKey();

            if(program.getIONodes().getOutputs().contains(errorNodeID))
            {
	            if(program.getOutputPipe(errorNodeID) == null)
	            {
	                addShow(programRef, errorNodeID, program.getDescription().getFriendlyName() + " Error", true);
	            }
            }
        }
	}
	
	private void removeShows()
	{
	    for(Long id : secretPrograms)
	    {
	        context.removeProgram(id.longValue());
	    }
	}
	
	public synchronized void executeContext() {
	    if(executing)
	    {
	        throw new IllegalStateException("Already executing");
	    }
	    if(programs.size() == 0)
	    {
	        listener.executionCompleted();
	        return;
	    }
	    
	    executing = true;
	    addShows();
	    
	    completedCount.set( programs.size() );
	    framework.executeContext(context);
	    
	    for(IHialProgram program : programs.values())
	    {
	        ((HialProgram)program).setExecutingState(true);
	    }
    }
	
	void programCompleted(IHialProgram prog)
	{
	    IRefObject programRef = null;
	    boolean completed = false;
	    synchronized(this)
	    {
	    	completedCount.decrementAndGet();
	    	completed = completedCount.get() == 0;
	    
		    for(Map.Entry<IRefObject, IHialProgram> programEntry : programs.entrySet())
		    {
		        if(programEntry.getValue() == prog)
		        {
		            programRef = programEntry.getKey();
		            break;
		        }
		    }
	           
	    
		    if(completed) 
		    {
			    for(IHialProgram program : programs.values())
			    {
			        ((HialProgram)program).setExecutingState(false);
			    }
		        
			    removeShows();
		        executing = false;
		    }
		}
	    
	    listener.executionCompleted(programRef);
	    if(completed)
	    {
	        listener.executionCompleted();
	    }
	}
	
	public synchronized void killAll()
	{
	    for(IHialProgram program : programs.values())
	    {
	        program.kill();
	    }
	}
}
