/******************************************************************************
 * Copyright (c) 2006, Intalio Inc.
 * 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:
 *     Intalio Inc. - initial API and implementation
 *******************************************************************************/

/** 
 * Date          	Author             Changes 
 * 26 Oct 2006   	BIlchyshyn         Created
 **/

package org.eclipse.stp.bpmn.policies;

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

import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.Tool;
import org.eclipse.gef.tools.SelectionTool;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy;
import org.eclipse.gmf.runtime.diagram.ui.handles.ConnectionHandle;
import org.eclipse.gmf.runtime.diagram.ui.handles.ConnectionHandleLocator;
import org.eclipse.gmf.runtime.diagram.ui.handles.ConnectionHandle.HandleDirection;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.preferences.IPreferenceConstants;
import org.eclipse.gmf.runtime.emf.ui.services.modelingassistant.ModelingAssistantService;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.stp.bpmn.diagram.edit.parts.ActivityEditPart;
import org.eclipse.stp.bpmn.diagram.part.BpmnDiagramPreferenceInitializer;
import org.eclipse.stp.bpmn.handles.ConnectionHandleEx;
import org.eclipse.stp.bpmn.tools.EdgeConnectionValidator;
import org.eclipse.stp.bpmn.tools.MessageConnectionValidator;

/**
 * Derived from org.eclipse.gmf.runtime.diagram.ui.editpolicies.
 * ConnectionHandlerEditPolicy.
 * There was issues overriding enough things.
 * 
 * @author BIlchyshyn Bohdan Ilchyshyn
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 */
public class ConnectionHandleEditPolicyEx extends DiagramAssistantEditPolicy {

	/**
	 * Listens to the owner figure being moved so the handles can be removed
	 * when this occurs.
	 */
	private class OwnerMovedListener implements FigureListener {

		/**
		 * @see org.eclipse.draw2d.FigureListener#figureMoved(org.eclipse.draw2d.IFigure)
		 */
		public void figureMoved(IFigure source) {
			hideDiagramAssistant();
		}
	}

	/** listener for owner shape movement */
	private OwnerMovedListener ownerMovedListener = new OwnerMovedListener();

	/** list of connection handles currently being displayed */
	private List handles = null;

	/* (non-Javadoc)
	 * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy#isDiagramAssistant(java.lang.Object)
	 */
	protected boolean isDiagramAssistant(Object object) {
		return object instanceof ConnectionHandle;
	}

	/**
	 * Gets the two connection handle figures to be added to this shape if they
	 * support user gestures.
	 * @return a list of <code>ConnectionHandle</code> objects
	 */
	@SuppressWarnings("unchecked")
    protected List getHandleFigures() {
		List list = new ArrayList(2);
		
		String tooltip;
		tooltip = buildTooltip(HandleDirection.INCOMING);
		if (tooltip != null) {
			list.add(new ConnectionHandleEx((IGraphicalEditPart) getHost(),
				HandleDirection.INCOMING, tooltip));
		}

		tooltip = buildTooltip(HandleDirection.OUTGOING);
		if (tooltip != null) {
			list.add(new ConnectionHandleEx((IGraphicalEditPart) getHost(),
				HandleDirection.OUTGOING, tooltip));
		}

		return list;
	}

	/**
	 * Builds the applicable tooltip string based on whether the Modeling
	 * Assistant Service supports handle gestures on this element. If no
	 * gestures are supported, the tooltip returned will be null.
	 * 
	 * @param direction
	 *            the handle direction.
	 * @return tooltip the tooltip string; if null, the handle should be not be
	 *         displayed
	 */
	protected String buildTooltip(HandleDirection direction) {
		ModelingAssistantService service = ModelingAssistantService
			.getInstance();

		boolean supportsCreation = (direction == HandleDirection.OUTGOING) ? !service
			.getRelTypesOnSource(getHost()).isEmpty()
			: !service.getRelTypesOnTarget(getHost()).isEmpty();

		boolean supportsSRE = (direction == HandleDirection.OUTGOING) ? !service
			.getRelTypesForSREOnSource(getHost()).isEmpty()
			: !service.getRelTypesForSREOnTarget(getHost()).isEmpty();

		if (supportsSRE) {
			if (supportsCreation) {
				return DiagramUIMessages.ConnectionHandle_ToolTip_ShowRelatedElementsAndCreateRelationship;
			} else {
				return DiagramUIMessages.ConnectionHandle_ToolTip_ShowRelatedElementsOnly;
			}
		} else if (supportsCreation) {
			return DiagramUIMessages.ConnectionHandle_ToolTip_CreateRelationshipOnly;
		}
		return null;
	}

	public void activate() {
		super.activate();
		
		((IGraphicalEditPart) getHost()).getFigure().addFigureListener(
			ownerMovedListener);
	}

	public void deactivate() {
		((IGraphicalEditPart) getHost()).getFigure().removeFigureListener(
			ownerMovedListener);

		super.deactivate();
	}
	
	@SuppressWarnings("unchecked")
    protected void showDiagramAssistant(Point referencePoint) {
		if (referencePoint == null) {
			referencePoint = getHostFigure().getBounds().getRight();
		}

		handles = getHandleFigures();
		if (handles == null) {
			return;
		}

		ConnectionHandleLocator locator = getConnectionHandleLocator(referencePoint);
		EdgeConnectionValidator edgeValidator = new EdgeConnectionValidator();
		MessageConnectionValidator messageValidator = new MessageConnectionValidator();
		
		//set handle position to host
		EditPart host = getHost();
		if (host instanceof ActivityEditPart) {
			((ActivityEditPart) host).setHandlePosition(locator.getBorderSide());
		}
		
		//determine what handles to show
		if (locator.getBorderSide() == PositionConstants.EAST) {
			handles.remove(0);
			if (!edgeValidator.canStart(getHost())) {
				handles.removeAll(handles);
			}
		} else if (locator.getBorderSide() == PositionConstants.WEST) {
			handles.remove(1);
			if (!edgeValidator.canEnd(getHost())) {
				handles.removeAll(handles);
			}
		} else if ((locator.getBorderSide() == PositionConstants.NORTH) 
					|| (locator.getBorderSide() == PositionConstants.SOUTH)) {
			if (!messageValidator.canStart(getHost())) {
				handles.remove(1);
			}
				
			if (!messageValidator.canEnd(getHost())) {
				handles.remove(0);
			}				
		}
		
		IFigure layer = getLayer(LayerConstants.HANDLE_LAYER);
		for (Iterator iter = handles.iterator(); iter.hasNext();) {
			ConnectionHandle handle = (ConnectionHandle) iter.next();
			
			handle.setLocator(locator);
			locator.addHandle(handle);

			handle.addMouseMotionListener(this);
			layer.add(handle);

			// Register this figure with it's host editpart so mouse events
			// will be propagated to it's host.
			getHost().getViewer().getVisualPartMap().put(handle, getHost());
		}
		
		if(!shouldAvoidHidingDiagramAssistant())
		{
			// dismiss the handles after a delay
			hideDiagramAssistantAfterDelay(getDisappearanceDelay());
		}
	}
	
    /**
     * Copied from super as we cannot override the getPreferenceName() method
     * Returns true if the preference to show this diagram assistant is on or if
     * there is no applicable preference; false otherwise.
     */
    protected boolean isPreferenceOn() {
        String prefName = getThePreferenceName();
        if (prefName == null) {
            return true;
        }
        IPreferenceStore preferenceStore = (IPreferenceStore) ((IGraphicalEditPart) getHost())
            .getDiagramPreferencesHint().getPreferenceStore();
        return preferenceStore.getBoolean(prefName);
    }

    
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy#getPreferenceName()
	 */
	protected String getThePreferenceName() {
		return IPreferenceConstants.PREF_SHOW_CONNECTION_HANDLES;
	}

	/**
	 * Removes the connection handles.
	 */
	protected void hideDiagramAssistant() {
		if (handles == null) {
			return;
		}
		IFigure layer = getLayer(LayerConstants.HANDLE_LAYER);
		for (Iterator iter = handles.iterator(); iter.hasNext();) {
			IFigure handle = (IFigure) iter.next();
			handle.removeMouseMotionListener(this);
			layer.remove(handle);
			getHost().getViewer().getVisualPartMap().remove(handle);
		}
		handles = null;
		
		//unset handle position to host
		EditPart host = getHost();
		if (host instanceof ActivityEditPart) {
			((ActivityEditPart) host).setHandlePosition(0);
		}
	}
		
	private boolean isSelectionToolActive()
	{
		// getViewer calls getParent so check for null
		if(getHost().getParent() != null)
		{
			Tool theTool = getHost().getViewer().getEditDomain().getActiveTool();
			if((theTool != null) && theTool instanceof SelectionTool)
			{
				return true;
			}
		}
		return false;		
	}
	
	/* (non-Javadoc)
	 * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy#shouldShowDiagramAssistant()
	 */
	protected boolean shouldShowDiagramAssistant(){
        if (getAppearanceDelay() < 0) {
            return false;
        }
		if (!super.shouldShowDiagramAssistant()) {
			return false;
		}
		if (handles != null || !isSelectionToolActive()) {
			return false;
		}
		return true;
	}
	
	/**
	 * get the connection handle locator using the host and the passed reference
	 * point
	 * 
	 * @param referencePoint
	 * @return <code>ConnectionHandleLocator</code>
	 */
	protected ConnectionHandleLocator getConnectionHandleLocator(Point referencePoint){
		return new ConnectionHandleLocator(getHostFigure(), referencePoint);		
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy#isDiagramAssistantShowing()
	 */
	protected boolean isDiagramAssistantShowing() {
		return handles != null;
	}
    
    /**
     * Gets the amount of time to wait before showing the diagram assistant.
     * Read from preferences.
     * @return the time to wait in milliseconds
     */
    protected int getAppearanceDelay() {
        IPreferenceStore preferenceStore = (IPreferenceStore) 
        ((IGraphicalEditPart) getHost())
            .getDiagramPreferencesHint().getPreferenceStore();
        int delay = preferenceStore.getInt(
                 BpmnDiagramPreferenceInitializer
                     .PREF_CONN_DIAG_ASSISTANT_DELAY_MS);
        return delay > -1 ? delay : 60;
    }

}
