/*******************************************************************************
 * 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.List;

import org.eclipse.bpel.model.Activity;
import org.eclipse.bpel.model.Sequence;
import org.eclipse.bpel.ui.BPELUIPlugin;
import org.eclipse.bpel.ui.IHoverHelper;
import org.eclipse.bpel.ui.IHoverHelperSupport;
import org.eclipse.bpel.ui.adapters.AdapterNotification;
import org.eclipse.bpel.ui.adapters.IContainer;
import org.eclipse.bpel.ui.editparts.policies.BPELComponentEditPolicy;
import org.eclipse.bpel.ui.editparts.policies.BPELDirectEditPolicy;
import org.eclipse.bpel.ui.editparts.policies.BPELOrderedLayoutEditPolicy;
import org.eclipse.bpel.ui.extensions.BPELUIRegistry;
import org.eclipse.bpel.ui.figures.CenteredConnectionAnchor;
import org.eclipse.bpel.ui.util.BPELCellEditorLocator;
import org.eclipse.bpel.ui.util.BPELDirectEditManager;
import org.eclipse.bpel.ui.util.BPELDragEditPartsTracker;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.bpel.ui.util.MultiObjectAdapter;
import org.eclipse.bpel.ui.util.marker.BPELEditPartMarkerDecorator;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.AccessibleAnchorProvider;
import org.eclipse.gef.AccessibleEditPart;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.tools.DirectEditManager;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.viewers.TextCellEditor;


/**
 * The superclass of all BPEL edit parts. Provides a listener on the model
 * which calls the handleModelChanged() method.
 */
@SuppressWarnings("nls")
public abstract class BPELEditPart extends AbstractGraphicalEditPart implements IHoverHelperSupport {

	/**
	 *  The amount of spacing to place between child items. Subclasses
	 */
	public static final int SPACING = 14;
	
	static protected String EMPTY_STRING = "";
	
	protected AccessibleEditPart acc;

	protected MultiObjectAdapter adapter;
	
	protected BPELEditPartMarkerDecorator.MarkerMotionListener markerMotionListener;
	
	// The direct edit manager handles the in-place editing of node names on the graphical canvas.
	private DirectEditManager manager;

	// Mouse motion hover help support
	protected int mouseLocation = 0; // 0 == no marker, 1 == top drawer, 2 == bottom drawer, 3 == marker on main figure
	
	/**
	 * Create a new BPELEditPart.
	 * 
	 * Construct the model adapter.
	 */
	public BPELEditPart() {
		adapter = new MultiObjectAdapter() {
			
			@Override
			public void notify(Notification n) {
				int eventGroup = n.getEventType() / 100;
				if (eventGroup == AdapterNotification.NOTIFICATION_MARKERS_CHANGED_GROUP ) {					
					refreshVisuals();
					return ;
				}
				
				
				// TODO: check if we care about this notification
				if (isActive()) {
					handleModelChanged();
				}
				refreshAdapters();
			}
		};
	}
	
	/**
	 * Default implementation based on IContainer.  Should be sufficient except in
	 * special cases (such as ProcessEditPart?). 
	 */
	@Override
	protected List getModelChildren() {
		IContainer container = BPELUtil.adapt(getModel(), IContainer.class);
		if (container != null) {
			return container.getChildren(getModel());
		}
		return super.getModelChildren();
	}

	protected void addAllAdapters() {
		EObject modelObject = (EObject)getModel();  
		adapter.addToObject(modelObject);
		
		// if the object has an extension, add adapter to that too
		EObject extension = ModelHelper.getExtension(modelObject);
		if (extension != null) adapter.addToObject(extension);
		// if the object contains an implicit sequence, add adapter to that also
		try {
			Activity activity = ModelHelper.getActivity(modelObject);
			if (activity instanceof Sequence) {
				// TODO: perhaps we should check and make sure it's an implicit sequence!
				// for now, don't worry about it.
				adapter.addToObject(activity);
			}
		} catch (IllegalArgumentException e) {
			// it's not a single-activity container.  ignore.
		}
		// ..but we probably don't need an adapter on the implicit sequence...
	}
	
	protected void removeAllAdapters() {
		adapter.removeFromAll();
	}
	
	protected void refreshAdapters() {
		removeAllAdapters();
		addAllAdapters();
	}
	
	/**
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#activate()
	 */
	@Override
	public void activate() {
		if (isActive()) {
			return;
		}
		super.activate();
		addAllAdapters();
	}

	/**
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#deactivate()
	 */
	@Override
	public void deactivate() {
		if (!isActive()) {
			return;		
		}
		removeAllAdapters();
		super.deactivate();
		clearConnections();					
	}

	
	@Override
	protected void createEditPolicies() {
		// The COMPONENT_ROLE policy determines how an edit part is deleted.
		installEditPolicy(EditPolicy.COMPONENT_ROLE, new BPELComponentEditPolicy());

		// The DIRECT_EDIT_ROLE policy determines how in-place editing takes place.
		installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new BPELDirectEditPolicy());
	}
	
	
	/**
	 * The model has changed. Perform any actions necessary to ensure that the
	 * edit part, model and graphical representation are in sync.
	 * 
	 * Subclasses may override but should call super.
	 */
	protected void handleModelChanged() {
		// If property name is children, refresh children.
		// If property name is size or location, refresh visuals.
		// TODO: refresh connections in there somewhere too!
		refreshChildren();
		refreshVisuals();
		
		refresh();
	}
	
	/**
	 * Get an anchor at the given location for this edit part.
	 * 
	 * This must be called after the figure for this edit part has been created.
	 * @param location 
	 * @return 
	 */
	public ConnectionAnchor getConnectionAnchor(int location) {
		return new CenteredConnectionAnchor(getFigure(), location, 0);
	}

	/**
	 * Return whether or not the edit part can execute the given request.
	 * The answer is determined by asking each edit policy and returning
	 * true if any policy can execute the request.
	 * @param request 
	 * @return 
	 */
	public boolean canExecuteRequest(Request request) {
		EditPolicyIterator i = getEditPolicyIterator();
		while (i.hasNext()) {
			Command cm= i.next().getCommand(request);
			if (cm != null)
				if (cm.canExecute())
					return true;
		}
		return false;
	}

	/**
	 * Override to handle direct edit requests
	 */
	@Override
	public void performRequest(Request request) {
		if (request.getType() == RequestConstants.REQ_DIRECT_EDIT) {
			performDirectEdit();
		} else {
			super.performRequest(request);
		}
	}

	/**
	 * Return whether the receiver can perform direct edit.
	 */
	public boolean canPerformDirectEdit() {
		return getLabelFigure() != null;
	}
	
	/**
	 * Invoke direct edit on the receiver
	 */
	public void performDirectEdit() {
		if (getLabelFigure() != null) {
			// make sure we can execute it through
			Command cmd = BPELDirectEditPolicy.getFinalizeCommand(this.getModel(), "blahblah"); //$NON-NLS-1$
			if (cmd == null || !cmd.canExecute())
				return;
			
			if (manager == null) {
				manager = new BPELDirectEditManager(this, TextCellEditor.class,
						new BPELCellEditorLocator(getLabelFigure()), getLabelValidator());
			}
			manager.show();
		}
	}
	
	/**
	 * Return the label that should be used for direct edit.
	 * 
	 * The default implementation returns null.
	 * 
	 * Subclasses should override to return the appropriate label.
	 */
	public Label getLabelFigure() {
		return null;
	}

	/**
	 * Return the text to display in the label for the edit part.
	 * 
	 * The default implementation returns null.
	 * 
	 * Subclasses should override to return the appropriate text.
	 */
	public String getLabelContent() {
		return null;
	}
	
	/**
	 * Set the text to display in the label for the edit part.
	 * 
	 * The default implementation does nothing.
	 * 
	 * Subclasses should override to set the label to the given string.
	 */
	public void setLabelContent(String str) {
	}
	
	/**
	 * TODO: visibility increased (from protected) so I could move the edit
	 * policies to a separate package.  In future, we should change it back.
	 */
	
	@Override
	public void refreshVisuals() {
		super.refreshVisuals();
		refreshHoverHelp();
	}
	
	@Override
	protected void refreshChildren() {		
		super.refreshChildren();
		
		// Not the most logical place to refresh connections, but it
		// needs to be refreshed after the children are refreshed		
		//TODO: May make more sense to put connections into adapter
		// so connections are not refreshed unnecessarily
		refreshConnections();
	}
	
	protected void refreshConnections(){
		EditPolicy policy = getEditPolicy(EditPolicy.LAYOUT_ROLE);
		if (policy instanceof BPELOrderedLayoutEditPolicy){
			((BPELOrderedLayoutEditPolicy)policy).refreshConnections();
		}
	}
	
	protected void clearConnections(){
		EditPolicy policy = getEditPolicy(EditPolicy.LAYOUT_ROLE);
		if (policy instanceof BPELOrderedLayoutEditPolicy){
			((BPELOrderedLayoutEditPolicy)policy).clearConnections();
		}
	}
	
	@Override
	public Object getAdapter(Class key) {
		if (key.isInstance(getModel())) {
			return getModel();
		}
		
		if (key == AccessibleAnchorProvider.class) {
			return new DefaultAccessibleAnchorProvider() {
				private List<Point> getDefaultLocations() {
					List<Point> list = new ArrayList<Point>();
					Rectangle r = getFigure().getBounds();
					
					// when calculating the target in connection tools, it was targettig
					// the object behind the figure we were interested in, so adding an addition
					// fudge factor to -2 helps make sure we hit the right target
					Point p = r.getTopRight().translate(-r.width / 2, r.height / 3);
					getFigure().translateToAbsolute(p);
					list.add(p);
					return list;
				}
				/**
				 * @see AccessibleAnchorProvider#getSourceAnchorLocations()
				 */
				@Override
				public List<Point> getSourceAnchorLocations() {
					return getDefaultLocations();
				}
				/**
				 * @see AccessibleAnchorProvider#getTargetAnchorLocations()
				 */
				@Override
				public List<Point> getTargetAnchorLocations() {
					return getDefaultLocations();
				}
			};
		}
		return super.getAdapter(key);
	}
	
	public void regenerateVisuals() {
	}
	
	// increase visibility!
	@Override
	public void refreshSourceConnections() { super.refreshSourceConnections(); }
	@Override
	public void refreshTargetConnections() { super.refreshTargetConnections(); }
	
	public void refreshHoverHelp() {
		// Refresh the tooltip if we can find a helper.
		try {
			IHoverHelper helper = BPELUIRegistry.getInstance().getHoverHelper();
			if (helper != null) {
				IFigure tooltip = helper.getHoverFigure((EObject)getModel());
				getFigure().setToolTip(tooltip);
			}
		} catch (CoreException e) {
			getFigure().setToolTip(null);
			BPELUIPlugin.log(e);
		}		
	}
	
	@Override
	protected IFigure createFigure() {
		return null;
	}

	@Override
	protected AccessibleEditPart getAccessibleEditPart() {
		if (acc == null) acc = createAccessible();
		return acc;
	}
	
	protected AccessibleEditPart createAccessible() {
		return BPELUtil.getAccessibleEditPart(this);
	}

	protected BPELEditPartMarkerDecorator.MarkerMotionListener getMarkerMotionListener() {
		return markerMotionListener = new BPELEditPartMarkerDecorator.MarkerMotionListener() {
			public void markerEntered(IMarker marker) {
				// refresh the hover help for this marker.
				mouseLocation = 3;
				refreshHoverHelp();
			}
		};	
	}
	
	@Override
	public DragTracker getDragTracker(Request request) {
		return new BPELDragEditPartsTracker(this);
	}
	
	/**
	 * Returns the label validator for this part or <code>null</code> if not applicable. The
	 * default behavior is to return <code>null</code>. Subclasses may override this method.
	 */
	protected IInputValidator getLabelValidator() {
		return null;
	}
}
