/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.ui.editparts;

import java.util.ArrayList;
import java.util.EventObject;
import java.util.Iterator;
import java.util.List;

import org.eclipse.bpel.common.ui.layouts.AlignedFlowLayout;
import org.eclipse.bpel.model.BPELPackage;
import org.eclipse.bpel.model.EventHandler;
import org.eclipse.bpel.model.FaultHandler;
import org.eclipse.bpel.model.Process;
import org.eclipse.bpel.ui.BPELEditDomain;
import org.eclipse.bpel.ui.adapters.IContainer;
import org.eclipse.bpel.ui.adapters.IEventHandlerHolder;
import org.eclipse.bpel.ui.adapters.IFaultHandlerHolder;
import org.eclipse.bpel.ui.adapters.delegates.ActivityContainer;
import org.eclipse.bpel.ui.editparts.policies.BPELContainerEditPolicy;
import org.eclipse.bpel.ui.editparts.policies.BPELOrderedLayoutEditPolicy;
import org.eclipse.bpel.ui.editparts.util.GraphAnimation;
import org.eclipse.bpel.ui.figures.CenteredConnectionAnchor;
import org.eclipse.bpel.ui.figures.ILayoutAware;
import org.eclipse.bpel.ui.figures.ProcessHandlerLinker;
import org.eclipse.bpel.ui.uiextensionmodel.impl.StartNodeImpl;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.ImplicitLinkHandlerConnectionRouter;
import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.draw2d.AbstractBorder;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Layer;
import org.eclipse.draw2d.LayeredPane;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStackListener;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.tools.MarqueeDragTracker;
import org.eclipse.jface.viewers.StructuredSelection;


public class ProcessEditPart extends BPELEditPart implements ILayoutAware{

	private IFigure layeredPane;
	private IFigure activityAndHandlerHolder;
	private IFigure activityHolder;
	private IFigure handlerHolder;
	private Layer activityLayer;
	
	// Whether to show each of the actual handlers.
	// TODO: Initialize these from the preferences store
	private boolean showFH = false, showEH = false;
	
	// The Handler which handles the drawing of links to the handlers.
	private ProcessHandlerLinker handlerLinker;
	
	private class ProcessOrderedHorizontalLayoutEditPolicy extends ProcessEditPart.ProcessOrderedLayoutEditPolicy{
		@Override
		protected ArrayList createHorizontalConnections(BPELEditPart parent) {
			ArrayList connections = new ArrayList();
			List children = getConnectionChildren(parent);
			BPELEditPart sourcePart, targetPart;
			ConnectionAnchor sourceAnchor, targetAnchor;
			
			for(int i=0; i < children.size(); i++){
				if(i != children.size()-1){
					sourcePart = (BPELEditPart)children.get(i);
					targetPart = (BPELEditPart)children.get(i+1);
					
					sourceAnchor = sourcePart.getConnectionAnchor(CenteredConnectionAnchor.RIGHT);
					targetAnchor = targetPart.getConnectionAnchor(CenteredConnectionAnchor.LEFT);
					
					PolylineConnection connection = createConnection(sourceAnchor,targetAnchor,arrowColor);
					
					
					if(sourcePart instanceof StartNodeEditPart){
						boolean horizontal = ModelHelper.isHorizontalLayout(getHost().getModel());
						connection.setConnectionRouter(new ImplicitLinkHandlerConnectionRouter(horizontal));
					}
					
					connections.add(connection);
				}
			}
			
			return connections;
		}
	}

	public class ProcessOrderedLayoutEditPolicy extends BPELOrderedLayoutEditPolicy {
		// return list of children to create vertical connections for.
		@Override
		protected List getConnectionChildren(BPELEditPart editPart) {
			List originalChildren = getChildren();
			List newChildren = new ArrayList();
			Iterator it = originalChildren.iterator();
			while (it.hasNext()) {
				Object next = it.next();
				if (next instanceof FaultHandlerEditPart) {
					continue;
				}
				newChildren.add(next);
			}
			return newChildren;
		}

		@Override
		protected Command getCreateCommand(CreateRequest request) {
			EditPart insertBefore = getInsertionReference(request);
			if (insertBefore == null) return null;
			if (insertBefore instanceof StartNodeEditPart) return null;
			return super.getCreateCommand(request);
		}
	}
	
	CommandStackListener stackListener = new CommandStackListener() {
		public void commandStackChanged(EventObject event) {
			if (!GraphAnimation.captureLayout(getFigure()))
				return;
			while (GraphAnimation.step())
				getFigure().getUpdateManager().performUpdate();
			GraphAnimation.end();
		}
	};
		
	@Override
	protected void createEditPolicies() {
		super.createEditPolicies();

		// Override the component policy, as you can't delete the process
		installEditPolicy(EditPolicy.COMPONENT_ROLE, null);

		installEditPolicy(EditPolicy.CONTAINER_ROLE, new BPELContainerEditPolicy());

		// The process must lay out its child activity
		if(ModelHelper.isHorizontalLayout(getModel()))
			installEditPolicy(EditPolicy.LAYOUT_ROLE, new ProcessOrderedHorizontalLayoutEditPolicy());
		else
			installEditPolicy(EditPolicy.LAYOUT_ROLE, new ProcessOrderedLayoutEditPolicy());
	}

	
	@Override
	protected IFigure createFigure() {
		LayeredPane result = new LayeredPane();
		this.layeredPane = result;
		
		Layer layer2 = new Layer();
		this.activityLayer = layer2;
		activityLayer.setLayoutManager(new ToolbarLayout());
		activityLayer.setOpaque(true);
		result.add(layer2, "activityLayer", 0); //$NON-NLS-1$
		
		this.activityAndHandlerHolder = new Figure();
		AlignedFlowLayout layout = new AlignedFlowLayout();
		layout.setHorizontalAlignment(AlignedFlowLayout.ALIGN_CENTER);
		layout.setVerticalAlignment(AlignedFlowLayout.ALIGN_BEGIN);
		layout.setSecondaryAlignment(AlignedFlowLayout.ALIGN_CENTER);
		this.activityAndHandlerHolder.setLayoutManager(layout);
		
		this.handlerHolder = new Figure();
		layout = new AlignedFlowLayout(true);
		layout.setHorizontalAlignment(AlignedFlowLayout.ALIGN_BEGIN);;
		this.handlerHolder.setLayoutManager(layout);
		
		this.activityHolder = new Figure();
		layout = new AlignedFlowLayout();
		layout.setHorizontalAlignment(AlignedFlowLayout.ALIGN_CENTER);
		layout.setVerticalAlignment(AlignedFlowLayout.ALIGN_BEGIN);
		layout.setSecondaryAlignment(AlignedFlowLayout.ALIGN_CENTER);
		layout.setVerticalSpacing(SPACING);
		activityHolder.setLayoutManager(layout);
		activityHolder.setOpaque(true);
		activityAndHandlerHolder.add(handlerHolder);
		activityAndHandlerHolder.add(activityHolder);
		layer2.add(activityAndHandlerHolder);
		
		// It's not a reals switch - just apply all layout setting here
		// to avoid duplicate code
		switchLayout(ModelHelper.isHorizontalLayout(getModel()));
		
		return layeredPane;
	}

	@Override
	public void setLayoutConstraint(EditPart child, IFigure childFigure, Object constraint) {
		getContentPane(child).setConstraint(childFigure, constraint);
	}
	
	protected IFigure getContentPane(EditPart childEditPart) {
		if (childEditPart instanceof StartNodeEditPart) {
			return handlerHolder;
		} else if (childEditPart instanceof FaultHandlerEditPart) {
			return handlerHolder;
		}
		return activityHolder;
	}
	
	@Override
	public IFigure getContentPane() {
        return activityHolder;
    }

	/**
	 * @see org.eclipse.gef.editparts.AbstractEditPart#getModelChildren()
	 */
	@Override
	protected List getModelChildren() {
		Process process = getProcess();
		BPELEditDomain domain = (BPELEditDomain)getViewer().getEditDomain();
		List list = new ArrayList();
		
		/* NOTE: The layoutEditPolicy relies on this order to identify the
		 * area the user is mousing over. Do not change the order in which these
		 * children are added unless you change the layoutEditPolicy too!!!!!!!!!!!!!!!!!!!!
		 */

		// TODO: this is way too magic.  can we get rid of this?
		
		list.add(domain.getStartNode());
		IContainer container = new ActivityContainer(BPELPackage.eINSTANCE.getProcess_Activity());
		list.addAll(container.getChildren(process));
		list.add(domain.getEndNode());
		
		/* END OF NOTE */
		
		if (showFH) {
			FaultHandler faultHandler = process.getFaultHandlers();
			if (faultHandler != null) list.add(faultHandler);
		}
		if (showEH) {
			EventHandler eventHandler = process.getEventHandlers();
			if (eventHandler != null) list.add(eventHandler);
		}
    	
		return list;
	}
	
	public boolean isShowFH() {
		return showFH;
	}

	public boolean isShowEH() {
		return showEH;
	}
	
	public FaultHandler getFaultHandler() {
		IFaultHandlerHolder holder = BPELUtil.adapt(getModel(), IFaultHandlerHolder.class);
		if (holder != null) {
			return holder.getFaultHandler(getModel());
		}
		return null;
	}
	
	public EventHandler getEventHandler() {
		IEventHandlerHolder holder = BPELUtil.adapt(getModel(), IEventHandlerHolder.class);
		if (holder != null) {
			return holder.getEventHandler(getModel());
		}
		return null;
	}

	protected Process getProcess() {
		return (Process)getModel();
	}

	@Override
	protected void addChildVisual(EditPart childEditPart, int index) {
		IFigure child = ((GraphicalEditPart) childEditPart).getFigure();
		IFigure content = getContentPane(childEditPart);
		if (content != null) {
			if (content == activityHolder) {
				// The index includes the start node, which isn't in this
				// content pane. Subtract one.
				content.add(child, index - 1);
			} else if(content == handlerHolder){
				content.add(child, getIndexForChild(content, childEditPart));
			}
		}
	}
	
	/**
	 * Gets the index for the given <code>child</code> which should be
	 * inserted into the <code>container</code> afterwards. This method
	 * does a kind of really simple sorting.
	 * @param container	The container where the child will be inserted.
	 * @param child		The child to get the index for
	 * @return 			The index for inserting the child into the container
	 */
	private int getIndexForChild(IFigure container, EditPart child) {
		int result = 0;
		if(!(child.getModel() instanceof StartNodeImpl)){
			int i = container.getChildren().size();
			if (i <= 1 || child.getModel() instanceof FaultHandler) {
				if (i > 1)
					result = 2;
				else
					result = 1;
			}else
				result = 1;
		}
		
		return result;
	}
	
	@Override
	protected void removeChildVisual(EditPart childEditPart) {
		IFigure child = ((GraphicalEditPart) childEditPart).getFigure();
		IFigure content = getContentPane(childEditPart);
		if (content != null)
			content.remove(child);
	}

	/**
	 * Also overridden to call getContentPane(child) in the appropriate place.
	 */
	@Override
	protected void reorderChild(EditPart child, int index) {
		// Save the constraint of the child so that it does not
		// get lost during the remove and re-add.
		IFigure childFigure = ((GraphicalEditPart) child).getFigure();
		LayoutManager layout = getContentPane(child).getLayoutManager();
		Object constraint = null;
		if (layout != null)
			constraint = layout.getConstraint(childFigure);

		removeChildVisual(child);
		List children = getChildren();
		children.remove(child);
		children.add(index, child);
		addChildVisual(child, index);
		
		setLayoutConstraint(child, childFigure, constraint);
	}
	
	/**
	 * Override to handle direct edit requests
	 */
	@Override
	public void performRequest(Request request) {
		if (request.getType() == RequestConstants.REQ_DIRECT_EDIT) {
			// let's not activate the rename or direct edit functionality because it's annoying when
			// the user clicks on the canvas and the rename box comes up.
			//	performDirectEdit();
			return;
		}
		super.performRequest(request);
	}
	
	public void setShowFaultHandler(boolean showFaultHandler) {
		this.showFH = showFaultHandler;
		// Call refresh so that both refreshVisuals and refreshChildren will be called.
		refresh();
	}

	public void setShowEventHandler(boolean showEventHandler) {
		this.showEH = showEventHandler;
		// Call refresh so that both refreshVisuals and refreshChildren will be called.
		refresh();
	}
	
	@Override
	public DragTracker getDragTracker(Request request) {
		return new MarqueeDragTracker() {
			@Override
			protected void handleFinished() {
				if (getViewer().getSelection().isEmpty()) {
					// if nothing has been select we should select the process
					getViewer().setSelection(new StructuredSelection(ProcessEditPart.this));
				}
			}
		};
	}


	public void switchLayout(boolean horizontal) {
		AlignedFlowLayout handlerLayout = (AlignedFlowLayout)handlerHolder.getLayoutManager();
		handlerLayout.setHorizontal(!horizontal);
		
		// Adjust the layout of the activityAndHandlerHolder
		((AlignedFlowLayout)activityAndHandlerHolder.getLayoutManager()).setHorizontal(horizontal);
		
		// Adjust the layout of the activityHolder
		((AlignedFlowLayout)activityHolder.getLayoutManager()).setHorizontal(horizontal);
		
		// Remove the LayoutPolicy which is responsible for creating the implicit links
		removeEditPolicy(EditPolicy.LAYOUT_ROLE);
		
		if(horizontal){
			// Handler Holder layout
			handlerLayout.setSecondaryAlignment(AlignedFlowLayout.ALIGN_BEGIN);
			
			// If we are in horizontal mode we add a top margin 
			activityAndHandlerHolder.setBorder( new MarginBorder(20,20,0,20));
			
			this.handlerHolder.setBorder(new MarginBorder(0,20,0,0));

			installEditPolicy(EditPolicy.LAYOUT_ROLE, new ProcessOrderedHorizontalLayoutEditPolicy());
		}else{
			// Handler Holder layout
			handlerLayout.setSecondaryAlignment(AlignedFlowLayout.ALIGN_BEGIN);
			
			// If we are in horizontal mode we remove the border 
			activityAndHandlerHolder.setBorder(null);
			
			this.handlerHolder.setBorder(new AbstractBorder() {
				public Insets getInsets(IFigure arg0) {
					return new Insets(20, 0, 10, 0);
				}
				public void paint(IFigure arg0, Graphics arg1, Insets arg2) {
				}
			});
			
			installEditPolicy(EditPolicy.LAYOUT_ROLE, new ProcessOrderedLayoutEditPolicy());
		}
	}
	
	@Override
	protected void clearConnections() {
		super.clearConnections();
		getHandlerLinker().clearHandlerConnections();
	}
	
	/**
	 * Overridden to refresh the handlerLinks as needed.
	 */
	@Override
	public void refresh() {
		super.refresh();
		getHandlerLinker().refreshHandlerLinks();
	}
	
	private ProcessHandlerLinker getHandlerLinker() {
		if(handlerLinker == null)
			handlerLinker = new ProcessHandlerLinker(this);
		return handlerLinker;
	}
	
	@Override
	protected void handleModelChanged() {
		super.handleModelChanged();
		
		this.showFH = getFaultHandler() != null ? true : false;
		this.showEH = getEventHandler() != null ? true : false;
		
		refresh();
	}
}
