/*******************************************************************************
* 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 26-Oct-2004
 *
 */
package uk.ac.ic.doc.kevlar.gui.internal.pipeline;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

import uk.ac.ic.doc.kevlar.api.programs.IProgramInfoNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IBasicArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IBoolArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IInputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IMultilineArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IOutputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.ISetArgumentInfo;
import uk.ac.ic.doc.kevlar.api.programs.arguments.ISinglelineArgumentInfo;
import uk.ac.ic.doc.kevlar.core.framework.IProgramIDDescription;
import uk.ac.ic.doc.kevlar.gui.GUIManager;
import uk.ac.ic.doc.kevlar.gui.internal.CanvasManager;
import uk.ac.ic.doc.kevlar.gui.internal.animation.AAnimation;
import uk.ac.ic.doc.kevlar.gui.internal.animation.AnimationGroupListenerEntry;
import uk.ac.ic.doc.kevlar.gui.internal.animation.AnimationManager;
import uk.ac.ic.doc.kevlar.gui.internal.animation.IAnimationListener;
import uk.ac.ic.doc.kevlar.gui.internal.dragdrop.ADraggableObject;
import uk.ac.ic.doc.kevlar.gui.internal.dragdrop.IDropTarget;
import uk.ac.ic.doc.kevlar.gui.internal.help.HelpManager;
import uk.ac.ic.doc.kevlar.gui.internal.keyboard.TabManager;
import uk.ac.ic.doc.kevlar.gui.internal.layout.ILayoutData;
import uk.ac.ic.doc.kevlar.gui.internal.layout.ILayoutProgram;
import uk.ac.ic.doc.kevlar.gui.internal.layout.LayoutManager;
import uk.ac.ic.doc.kevlar.gui.internal.pipeline.arguments.Argument;
import uk.ac.ic.doc.kevlar.gui.internal.pipeline.arguments.BoolArgument;
import uk.ac.ic.doc.kevlar.gui.internal.pipeline.arguments.MultiLineTextArgument;
import uk.ac.ic.doc.kevlar.gui.internal.pipeline.arguments.SetArgument;
import uk.ac.ic.doc.kevlar.gui.internal.pipeline.arguments.SingleLineTextArgument;
import uk.ac.ic.doc.kevlar.gui.internal.popup.PopUpBox;
import uk.ac.ic.doc.kevlar.gui.internal.widgets.listbox.IListSelectionListener;
import uk.ac.ic.doc.kevlar.hial.IArgumentValuePair;
import uk.ac.ic.doc.kevlar.hial.IHialProgram;
import uk.ac.ic.doc.kevlar.hial.IRefObject;
import uk.ac.ic.doc.kevlar.publicInterfaces.INodeID;

/**
 * @author Kate
 *
 */
public class Program extends ADraggableObject implements IProgramGroup, ILayoutProgram, IListSelectionListener, IRefObject {
	
    public static final int PROGRAM_MINIMUM_WIDTH = 100;
    public static final int MOVEMENT_SIZE = 100;
    
	protected List<AProgramEntry> entries;
	protected List<AIONodeEntry> nodes;
	protected List<Argument> arguments;
	protected boolean registered;
	protected IProgramIDDescription data;
	protected ILayoutData layoutData;
	protected ProgramHeading heading;
	protected boolean updatePipes;
	protected boolean expanded;
	
	protected class ContractAnimationListener implements IAnimationListener {

        public void animationFinished() {

    		setSize(new Point(area.width, ProgramHeading.PROGRAM_HEADING_HEIGHT));
    		LayoutManager.getLayout().forceProgramSize(Program.this, getSize());
            
        }	   
	    
	}
	
	public Program(IProgramIDDescription data) {
	    
		super();
		setSize(new Point(150, ProgramHeading.PROGRAM_HEADING_HEIGHT));
		entries = new ArrayList<AProgramEntry>();
		nodes = new ArrayList<AIONodeEntry>();
		arguments = new ArrayList<Argument>();
		registered = false;
		this.layoutData = null;
		this.data = data;
		this.updatePipes = true;
		this.expanded = true;
		
		heading = new ProgramHeading(this);
		addChild(heading);
		
	}

	public String getName() {
	    
	    return data.getFriendlyName();
	    
	}
	
	public void select() {
	    
	    heading.select();
	    
	}
	
	public void deselect() {
	    
	    heading.deselect();
	    
	}
	
	public void disable() {
	    
	    heading.disable();
	    
	}
	
	public void enable() {
	    
	    heading.enable();
	    
	}
	
	public boolean isEnabled() {
	    
	    return heading.isEnabled();
	    
	}

	public void highlight() {
	    
	    heading.highlight();
	    
	}
	
	public void unhighlight() {
	    
	    heading.unhighlight();
	    
	}
		
	public boolean isExecutable() {
	    
	    for (AIONodeEntry node: nodes) {
	        
	        if (node.hasPipe()) {
	            
	            if (node.getPipe().isInvalid()) {
	                
	                return false;
	                
	            }
	            
	        }
	        
	    }
	    
	    for (Argument arg: arguments) {
	        
	        if (arg.isInvalid()) {
	            
	            return false;
	            
	        }
	        
	    }
	    
	    return true;
	    
	}
	
	public IProgramIDDescription getProgramIDDescription() {
	 
	    return data;
	    
	}
	
	public void setLayoutData(ILayoutData data) {
	    
	    layoutData = data;
	    
	}
	
	public ILayoutData getLayoutData() {
	    
	    return layoutData;
	    
	}
	
	public IProgramIDDescription getData() {
	    
	    return data;
	    
	}
	
	public void onDrag(Point offset) {
	    
	    if (isEnabled()) {
	    
	        stopUpdatingPipes();
	        CanvasManager.getSingleton().setDragObject(this, offset);
	        
	    }
		
	}
	
	public void dropped(IDropTarget target) {
	    
	    startUpdatingPipes();
	    
	    if (!(target instanceof PipeLineManager)) {
	        
	        removeProgram();
	        
	    }
	    
	}

	public boolean isUpdatingPipes() {
	    
	    return updatePipes;
	    
	}
	
	public void stopUpdatingPipes() {
	    
	    updatePipes = false;
	    
	}
	
	public void startUpdatingPipes() {
	    
	    updatePipes = true;
	    updatePipes();
	    
	}
	
	public void setRegistered(boolean value)
	{
		registered = value;
	}
	
	public boolean isRegistered()
	{
		return registered;
	}
	
	public void register(IHialProgram programInfo)
	{
	    
	    if (!expanded) {
	        
	        expand();
	        
	    }
	    
	    registered = true;
	    arguments.clear();
	    
		//populate the program with arguments and IO nodes
		int positionIndex = 0;
		
		Collection<IProgramInfoNode> nodeInfos = programInfo.getAllProgramInfoNodes();
		int i=0;
		
		while (i<entries.size()) {
		    
		    AProgramEntry entry = entries.get(i);
		    boolean found = false;
		    
		    for (IProgramInfoNode node : nodeInfos) {
		        
		        if (node.getName().equals(entry.getDataNode().getName())) {
		            
		            found = true;
		            
		        }
		        
		    }
		    
		    if (!found) {
		        
		        entry.dispose();
		        entries.remove(entry);
		        
		        if (entry instanceof AIONodeEntry) {
		            
		            nodes.remove(entry);
		            
		        }
		        
		    } else {
		        
		        i++;
		        
		    }
		    
		}
		
		for (IProgramInfoNode node : nodeInfos) {
		    
		    if (node instanceof IInputNode) {
		        
				AProgramEntry nodeEntry = getEntry(node.getName());
				
				if (nodeEntry != null) {

				    nodeEntry.setIndex(positionIndex);
				    
				} else {
				    
			        nodeEntry = new InputNodeEntry((IInputNode)node,positionIndex, this); 
			        entries.add(nodeEntry);
			        nodes.add((InputNodeEntry)nodeEntry);
			        addChild(nodeEntry);
				        
				}
				
				positionIndex++;		        
		        
		    }
		    
		}
		
		for (IProgramInfoNode node : nodeInfos) {
		    
		    if (node instanceof IOutputNode) {
		        
				AProgramEntry nodeEntry = getEntry(node.getName());
				
				if (nodeEntry != null) {

				    nodeEntry.setIndex(positionIndex);
				    
				} else {
				    
				    nodeEntry = new OutputNodeEntry((IOutputNode)node, positionIndex, this); 
					entries.add(nodeEntry);
					nodes.add((OutputNodeEntry)nodeEntry);
					addChild(nodeEntry);
				    
				}
				
				positionIndex++;
		        
		    }
		    
		}
		
		for (IProgramInfoNode node : nodeInfos) {
		    
		    if (node instanceof IArgumentValuePair) {
		        
				AProgramEntry nodeEntry = getEntry(node.getName());
				
				if (nodeEntry != null) {

				    nodeEntry.setIndex(positionIndex);
				    nodeEntry.setDataNode(node);
				    
				} else {
				    
				    nodeEntry = createArgumentEntry((IArgumentValuePair)node, positionIndex);
					entries.add(nodeEntry);
					addChild(nodeEntry);
				    
				}
						    
				arguments.add((Argument)nodeEntry);
				positionIndex++;
		        
		    }
		    
		}
		
		bringToFront(heading);
		
		setSize(new Point(area.width, ProgramHeading.PROGRAM_HEADING_HEIGHT+entries.size()*AProgramEntry.ENTRY_INTERNAL_HEIGHT));
		LayoutManager.getLayout().forceProgramSize(this, getSize());
		
	}
	
	public AIONodeEntry getNode(INodeID nodeID)
	{
		AIONodeEntry result = null;
		
		for(AIONodeEntry entry : nodes)
		{
			if (entry.getINode().getNodeID().equals(nodeID))
			{
				result = entry;
				return result;
			}
		}
		
		return result;
	}
	
	public void applyArgumentChanges() {
	    
	    ArrayList<IArgumentValuePair> argumentPairs = new ArrayList<IArgumentValuePair>();
	    
	    for (Argument arg : arguments) {
	        
	        argumentPairs.add(arg.getArgumentPair());
	        
	    }
	    
	    IHialProgram programInfo = GUIManager.getHialContext().getProgram(this);
	    
	    if (programInfo != null) {
	    
	        programInfo.updateArgumentValues(argumentPairs);
	        Collection<IBasicArgumentInfo> invalidArgs = programInfo.getInvalidArguments();
	        
	        for (Argument arg: arguments) {
	            
	            boolean found = false;
	            
	            for (IBasicArgumentInfo argInfo: invalidArgs) {
	                
	                found = found || argInfo.getName().equals(arg.getName());
	                
	            }
	            
	            if (found) {
	                
	                arg.setInvalid(true);
	                
	            } else {
	                
	                arg.setInvalid(false);
	                
	            }
	            
	        }
	        
	    }
	    
	}
	
	private Argument createArgumentEntry(IArgumentValuePair pair, int pos) {
	    
	    if (pair.getArgInfo() instanceof ISinglelineArgumentInfo) {

	        return new SingleLineTextArgument(pair, pos, this);
	        
	    } else if (pair.getArgInfo() instanceof IMultilineArgumentInfo) {

	        return new MultiLineTextArgument(pair, pos, this);
		        
	    } else if (pair.getArgInfo() instanceof IBoolArgumentInfo) {

	        return new BoolArgument(pair, pos, this);
		        
	    } else if (pair.getArgInfo() instanceof ISetArgumentInfo) {

	        return new SetArgument(pair, pos, this);
		        
	    } else {
	    
	        return new Argument(pair, pos, this);
	        
	    }
	    	    
	}
	
	public AProgramEntry getEntry(String name) {
	    
	    for (int i=0; i<entries.size(); i++) {
	        
	        AProgramEntry entry = entries.get(i);
	        
	        if (entry.getName().equals(name)) {
	            
	            return entry;
	            
	        }
	        
	    }
	    
	    return null;
	    
	}
	
	public List<AIONodeEntry> getNodes() {
	    
	    return nodes;
	    
	}
	
	public void addEntryButtonPressed(Point position) {
	    
	    IHialProgram programInfo = GUIManager.getHialContext().getProgram(this);
	    
	    if (programInfo != null) {

	        ProgramHiddenEntryListBox list = new ProgramHiddenEntryListBox(this);
	        
	        PopUpBox popup = new PopUpBox();
	        popup.setName("Add an entry");
	        popup.setContents(list);
	        popup.popUpAt(position);
	        
	    }
	    
	}
	
	public void helpButtonPressed() {
	    
	    Rectangle absArea = getAbsoluteArea();
	    HelpManager.getSingleton().displayProgramHelp(getProgramIDDescription(), absArea.x+absArea.width, absArea.y);
	    
	}

    public void entrySelected(Object entry) {
        
	    if (entry instanceof IBasicArgumentInfo) {

	        addArgument((IBasicArgumentInfo)entry);
	        
	    }
        
    }
    
    public void addArgument(IBasicArgumentInfo info) {
        
	    IHialProgram programInfo = GUIManager.getHialContext().getProgram(this);	    
        programInfo.addArgument(info);
                
    }
    
    public void resize(int newX) {
        
        if (area.width+newX > PROGRAM_MINIMUM_WIDTH) {
        
            setSize(new Point(alignedArea.width+newX, alignedArea.height));
            LayoutManager.getLayout().forceProgramSize(this, getSize());
            
        } else if (area.width != PROGRAM_MINIMUM_WIDTH) {
            
            setSize(new Point(PROGRAM_MINIMUM_WIDTH, alignedArea.height));
            LayoutManager.getLayout().forceProgramSize(this, getSize());
            
        }
        
    }
    
    public void deleteButtonPressed() {

        removeProgram();
        
    }
    
    public void removeProgram() {
        
        if (registered) {

            for (AProgramEntry entry: entries) {
                
                if (entry instanceof AIONodeEntry) {
                    
                    AIONodeEntry ionode = (AIONodeEntry)entry;
                    
                    if (ionode.hasPipe()) {
                        
                        ionode.getPipe().requestRemove();
                        
                    }
                    
/*                    if (ionode.isSelected()) {
                        
                        PipeLineManager.getSingleton().deselectNode();
                        
                    } */
                    
                }
                
            }
            
            GUIManager.getHialContext().removeProgram(this);
            
        }
        
    }
    
    public void remove() {
        
        super.remove();
        
        for (AProgramEntry entry: entries) {
            
            if (entry instanceof AIONodeEntry) {
                
                AIONodeEntry ionode = (AIONodeEntry)entry;
                
                if (ionode.hasPipe()) {
                    
                    ionode.getPipe().remove();
                    
                } 
                
                if (ionode.isSelected()) {
                    
                    PipeLineManager.getSingleton().deselectNode();
                    
                } 
                
            }
            
        }
        
        LayoutManager.getLayout().removeProgram(this);
        TabManager.getSingleton().removeFromTabList(this);
        PipeLineManager.getSingleton().updateExecutable();
        
    }
    
    public void toggleExpandContract() {
        
        if (expanded) {
            
            contract();
            
        } else {
            
            expand();
            
        }
        
    }
    
    public void contract() {
        
        heading.contract();
        ArrayList<AAnimation> anims = new ArrayList<AAnimation>();
        
        for (AProgramEntry entry : entries) {
            
            anims.add(entry.contract());
            
        }
        
        AnimationManager.getSingleton().addAnimationListener(new AnimationGroupListenerEntry(new ContractAnimationListener(), anims));
        expanded = false;
        
    }
    
    public void expand() {
        
        heading.expand();
        
        for (AProgramEntry entry : entries) {
            
            entry.expand();
            
        }
        
        bringToFront(heading);
		setSize(new Point(area.width, ProgramHeading.PROGRAM_HEADING_HEIGHT+entries.size()*AProgramEntry.ENTRY_INTERNAL_HEIGHT));
		LayoutManager.getLayout().forceProgramSize(this, getSize());
		expanded = true;        
		
    }
    
    public boolean isExpanded() {
        
        return expanded;
        
    }
    
    public void updatePipes() {
        
        for (AIONodeEntry node: nodes) {
            
            if (node.hasPipe()) {
                
                node.getPipe().update();
                
            }
            
        }
        
    }
    
    public void hidePipes() {
        
        for (AIONodeEntry node: nodes) {
            
            if (node.hasPipe()) {
                
                node.getPipe().hide();
                
            }
            
        }
        
    }
    
    public void showPipes() {
        
        for (AIONodeEntry node: nodes) {
            
            if (node.hasPipe()) {
                
                node.getPipe().show();
                
            }
            
        }
        
    }
    
    public void shiftUp() {
        
        if (area.y-MOVEMENT_SIZE >= 0) {
            
            LayoutManager.getLayout().suggestProgramPosition(this, new Point(area.x, area.y-MOVEMENT_SIZE));
            updatePipes();
            PipeLineManager.getSingleton().scrollTo(this);
            
        }
        
    }
	
    public void shiftDown() {
        
        LayoutManager.getLayout().suggestProgramPosition(this, new Point(area.x, area.y+MOVEMENT_SIZE));
        updatePipes();
        PipeLineManager.getSingleton().scrollTo(this);
        
    }
	
    public void shiftLeft() {
        
        if (area.x-MOVEMENT_SIZE >= 0) {
            
            LayoutManager.getLayout().suggestProgramPosition(this, new Point(area.x-MOVEMENT_SIZE, area.y));
            updatePipes();
            PipeLineManager.getSingleton().scrollTo(this);
            
        }
        
    }
	
    public void shiftRight() {
        
        LayoutManager.getLayout().suggestProgramPosition(this, new Point(area.x+MOVEMENT_SIZE, area.y));
        updatePipes();
        PipeLineManager.getSingleton().scrollTo(this);
        
    }
    
}
