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

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import uk.ac.ic.doc.kevlar.Kevlar;
import uk.ac.ic.doc.kevlar.api.programs.XMLModelLoadingException;
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.internal.iomapping.NodeID;
import uk.ac.ic.doc.kevlar.api.programs.descriptions.ProgramDescriptionProvider;
import uk.ac.ic.doc.kevlar.api.programs.returnValues.ReturnModelProvider;
import uk.ac.ic.doc.kevlar.core.framework.Context;
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.IShowExecutionManager;
import uk.ac.ic.doc.kevlar.core.framework.PipelineProgram;
import uk.ac.ic.doc.kevlar.core.framework.ShowExecutionManagerProvider;
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.IReturnValue;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.LoaderProvider;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.xmlparse.IProgramXMLFile;
import uk.ac.ic.doc.kevlar.core.framework.internal.loaders.xmlparse.ProgramXMLFileProvider;
import uk.ac.ic.doc.kevlar.core.framework.typecheck.IInterfaceClass;
import uk.ac.ic.doc.kevlar.gui.showers.IShower;
import uk.ac.ic.doc.kevlar.publicInterfaces.IInputPipe;
import uk.ac.ic.doc.kevlar.publicInterfaces.IJVMEnvironment;
import uk.ac.ic.doc.kevlar.publicInterfaces.INodeID;
import uk.ac.ic.doc.kevlar.publicInterfaces.IOutputPipe;
import uk.ac.ic.doc.kevlar.publicInterfaces.IType;

/**
 * @author Daniel Burke
 *
 */
public class Show implements IProgram, IContextMainpulatingBuiltin
{
	static final int MAX_BEFORE_FLUSH = 50;
	static final String DATA_NODE_NAME = "Data";
	
    private final ArgumentModelProvider amp;
    private final ProgramDescriptionProvider pdp;
    final ReturnModelProvider rmp; 
    
    Show(){
        
            IProgramXMLFile ipxf = ProgramXMLFileProvider.getProgramXMLFile(
                getClass().getResourceAsStream("Show.xml"));
        try {
            amp = new ArgumentModelProvider(ipxf.getArgumentModel(),LoaderProvider.getLoader().getTypeLoader());
            pdp = new ProgramDescriptionProvider(ipxf.getDescriptionModel());
            rmp = new ReturnModelProvider(ipxf.getReturnModel());
        } catch (XMLModelLoadingException e) {
            throw new InternalError("ChangeDirectory Builtin Failed: " + e);
        } catch (ClassNotFoundException e) {
            throw new InternalError("ChangeDirectory Builtin Failed: " + e);
        }
                
    }
    
    
    public ArgumentModelProvider getArgumentModelProvider() {
        return amp;
    }

    public ProgramDescriptionProvider getDescription() {
        return pdp;
    }

    public ReturnModelProvider getReturnModelProvider() {
        return rmp;
    }

    public IProgramInstance run(IArgumentInstance args, IJVMEnvironment env,
            Map<INodeID, IInputPipe> ins, Map<INodeID, IOutputPipe> outs) {
       
        return new IProgramInstance(){

            public boolean isRunning() {
                return false;
            }

            public void kill() {
                return;
            }
            

            //FIXME: toa02 - new method to make this compile.
            public void addExitNotifier(IProgramExitListener notifer) {
                // TODO Auto-generated method stub

            }

            public IReturnValue getReturnValue() {
    
                return new IReturnValue(){

                    public int getLiteralReturnValue() {
                            return 0;
                    }

                    public String getReturnDescription() {
                        return "The wrong run method was called for show";
                    }

                    public boolean isErroredReturn() {
                        return true;
                    }
                    
                };
            }
            
        };
    }
    
    public IProgramInstance run(
            IArgumentInstance args, 
            IJVMEnvironment env,
            Map<INodeID, IInputPipe> ins, 
            Map<INodeID, IOutputPipe> outs,
            Context executingContext,
            PipelineProgram thisProgram)
    {
        
        final AtomicBoolean retOk = new AtomicBoolean(true);
        
        
        
        IPipe dataPipe = thisProgram.getConnectedInputPipes().get(new NodeID(DATA_NODE_NAME));
        IInterfaceClass sourceType = null;
        String friendlyName = null;
        if(dataPipe != null)
        {
	
	        IPipelineProgram program = dataPipe.getFromProgram();
	        sourceType = program.getOutputNodeType(dataPipe.getFromNodeID());
	        friendlyName = program.getDescription().getFriendlyName();
        }
        
        if(Kevlar.DEBUG && dataPipe == null)
        {
            System.out.println("On entry to Show's run, pipeline program did not have connected Data pipe");
        }
        
        ShowInstance showInstance = new ShowInstance(args, env, ins, outs, sourceType, friendlyName, retOk);
        
        
        final Thread t = new Thread(showInstance);
        t.setName("Show instance thread");
        showInstance.setThread(t);
        t.setDaemon(true);
        t.start();
        
        return showInstance;
    }

    public String toString() {
        return "IProgram: Show";
    }

    public String getCanonicalName() {
        return getClass().getCanonicalName();
    }

    public boolean isBuiltin() {
        return true;
    }
    
    private class ShowInstance implements Runnable, IProgramInstance
    {
        private Thread instanceThread;
        
        AtomicBoolean die;
        AtomicBoolean isDead = new AtomicBoolean(false);
        AtomicBoolean pastDeadlockOnKillStage = new AtomicBoolean(false);
        
        private String name;
        private String showUsing;
        private IInputPipe data;
        
        private IArgumentInstance args; 
        private Map<INodeID, IInputPipe> ins;
        private IInterfaceClass sourceType;
        private String friendlyName;
        AtomicBoolean retOk;
        
        
        private Set<IProgramExitListener> notifiers = new HashSet<IProgramExitListener>();

        public ShowInstance(
            IArgumentInstance args, 
            IJVMEnvironment env,
            Map<INodeID, IInputPipe> ins, 
            Map<INodeID, IOutputPipe> outs,
            IInterfaceClass sourceType,
            String friendlyName,
            AtomicBoolean retOk)
        {
            this.args = args;
            this.ins = ins;
            this.sourceType = sourceType;
            this.friendlyName = friendlyName;
            this.retOk = retOk;
            
            die = new AtomicBoolean(false);
        }
        
        public void setThread(Thread instanceThread)
        {
            // no need to synchronize, this is called before the
            // object is returned
            this.instanceThread = instanceThread;
        }
        
        public void run()
        {
            try
            {
                runShow();
            }
            catch(RuntimeException e)
            {
                retOk.set(false);
                throw e;
            }
            finally
            {
                isDead.set(true);
                tellNotifiers();
            }
        }
        
        public void runShow() {

            Boolean b = args.getBool("Open-lazily");
            boolean openLazily = (b == null ? false : b.booleanValue());  
            //openLazily = false;
            
            name = args.getText("Name");
            if(name == null)
            {
                name = friendlyName;
            }
            
            showUsing = args.getText("Show-using");
            NodeID dataNode = new NodeID(DATA_NODE_NAME);
            /* toa02: If show has no pipes hooked up to it
           then it gets a null map. */
            if ( ins == null || friendlyName == null) 
            { 
                return; 
            }
            
            data = ins.get(dataNode);
            if(data == null)
            {
                return;
            }
            
            if(sourceType == null)
            {
                if(Kevlar.DEBUG)
                {
                    System.out.println("Show had a *-type pipe connected");
                }
                return;
            }

            IShowExecutionManager manager = ShowExecutionManagerProvider.getShowExecutionManager();
            
            IShower shower = null;
            if(!openLazily)
            {
                shower = manager.getShower(name, showUsing, sourceType);
                shower.executionStarted(friendlyName, Calendar.getInstance());
                pastDeadlockOnKillStage.set(true);
            }
            
            List<IType> items = new ArrayList<IType>();
            IType item;
            
            if(die.get()) 
            {
                return;
            }
            
            try
            {
                while(true)
                {
                    if(name.equals("Test"))
                            {
                            	System.out.println("hi");
                            }
                    item = data.read();
                    if(die.get()) 
                    {
                        return;
                    }
                    
                    if(openLazily)
                    {
                        shower = manager.getShower(name, showUsing, sourceType);
                        shower.executionStarted(friendlyName, Calendar.getInstance());
                        pastDeadlockOnKillStage.set(true);
                        openLazily = false;

                        if(die.get()) 
                        {
                            return;
                        }
                    }
                    
                    items.add(item);
                    
                    try {
                        int count = 0;
	                    while(data.ready())
	                    {
	                    	items.add(data.read());
	                        if(die.get()) 
	                        {
	                            return;
	                        }
	                        
	                    	count++;
	                    	if(count > MAX_BEFORE_FLUSH)
	                    	{
	                    	    break;
	                    	}
	                    }
                    }
                    catch(IllegalStateException e)
                    {
                    	// Flush what we've got
                    }
                    
                    if(!shower.isRemoved())
                    {
                        shower.addAllObjects(items);
                        items.clear();
                    }
                    else
                    {
                        break;
                    }
            	}
                
                data.close();
            }
            catch(InterruptedException ie)
            {
               // we've been killed :S
            }
            catch(IllegalStateException ise)
            {
                /* It's okay, we're done */
            }
            
        }
        
        public boolean isRunning() {
            synchronized(instanceThread)
            {
                return  !isDead.get() && instanceThread.isAlive();
            }
        }
        
        public void addExitNotifier(IProgramExitListener notifer) {
            synchronized ( notifiers ) {
                if( instanceThread.isAlive() ) {
                    notifiers.add(notifer);
                    return;
                }
            }
           notifer.programExited(this);
        }
        
        void tellNotifiers() {
            
            synchronized ( notifiers ) {
                for ( IProgramExitListener list : notifiers ) {
                    list.programExited(this);
                }
            }
            
        }
            
        public void kill() {
            //ok so you can't actually do this.
            synchronized(instanceThread)
            {
	            if(instanceThread.isAlive() ) {
                    retOk.set(false);
                    endProcess();
	            }
            }
        }
        
        private void endProcess()
        {
            synchronized(instanceThread)
            {
	            if(instanceThread.isAlive() ) {
                    die.set(true);
                    if(data != null)
                    {
                        data.close();
                    }
                    // I've decided all of this is unneccessary
                    /*
	                try {
	                   
	                    //TODO: Fix this broken code so that killing is more reliable
	                    if(pastDeadlockOnKillStage.get())
	                    {
	                        instanceThread.join();
	                    }
	                } catch ( InterruptedException ie ) {
	                    // ignore it and leave 
	                }*/
	            }
            }
        }

        public IReturnValue getReturnValue() {
            endProcess();
            // retOK is synchronized
            
            return new IReturnValue(){

                public int getLiteralReturnValue() {
                    if( retOk.get() ) {
                        return 0;
                    } else {
                        return 1;
                    }
                }

                public String getReturnDescription() {
                    synchronized(rmp)
                    {
	                    if ( retOk.get() ) {
	                        
	                        return rmp.getReturnModel().getReturnValueMap().get(Integer.valueOf(0)).getMessage();
	                    } else {
	                        return rmp.getReturnModel().getReturnValueMap().get(Integer.valueOf(1)).getMessage();
	                    }
                    }
                }

                public boolean isErroredReturn() {
                    return !retOk.get();
                }
                
            };
        }
    }
}
