/*******************************************************************************
 * Copyright (c) 2000, 2010 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.gef.editpolicies;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.AncestorListener;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.ConnectionLocator;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Polygon;
import org.eclipse.draw2d.geometry.PointList;

import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.handles.ConnectionEndpointHandle;
import org.eclipse.gef.requests.ReconnectRequest;

/**
 * A selection handle policy for placing handles at the two ends of a
 * ConnectionEditPart. All ConnectionEditParts should have one of these, even if
 * the ends of the connection aren't draggable, because this is the primary
 * SelectionEditPolicy for showing focus.
 * <P>
 * A connection can receive focus but not selection by pressing
 * <code>Control+/</code> on the keyboard.
 * 
 * @since 2.0
 */
public class ConnectionEndpointEditPolicy extends SelectionHandlesEditPolicy {

	private ConnectionAnchor originalAnchor;
	private FeedbackHelper feedbackHelper;
	private ConnectionFocus focus;

	class ConnectionFocus extends Polygon implements PropertyChangeListener {
		AncestorListener ancestorListener = new AncestorListener.Stub() {
			public void ancestorMoved(IFigure ancestor) {
				revalidate();
			}
		};

		ConnectionFocus() {
			setFill(false);
			setForegroundColor(ColorConstants.white);
			setXOR(true);
			setOutline(true);
		}

		public void addNotify() {
			super.addNotify();
			getConnection().addPropertyChangeListener(
					Connection.PROPERTY_POINTS, this);
			getConnection().addAncestorListener(ancestorListener);
		}

		protected void outlineShape(Graphics g) {
			g.setLineDash(new int[] { 1, 1 });
			super.outlineShape(g);
		}

		public void propertyChange(PropertyChangeEvent evt) {
			revalidate();
		}

		public void removeNotify() {
			getConnection().removePropertyChangeListener(
					Connection.PROPERTY_POINTS, this);
			getConnection().removeAncestorListener(ancestorListener);
			super.removeNotify();
		}

		public void validate() {
			if (isValid())
				return;
			PointList points = getConnection().getPoints().getCopy();
			getConnection().translateToAbsolute(points);
			points = StrokePointList.strokeList(points, 5);
			translateToRelative(points);
			setPoints(points);
		}
	}

	/**
	 * @see org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy#createSelectionHandles()
	 */
	protected List createSelectionHandles() {
		List list = new ArrayList();
		list.add(new ConnectionEndpointHandle((ConnectionEditPart) getHost(),
				ConnectionLocator.SOURCE));
		list.add(new ConnectionEndpointHandle((ConnectionEditPart) getHost(),
				ConnectionLocator.TARGET));
		return list;
	}

	/**
	 * Erases connection move feedback. This method is called when a
	 * ReconnectRequest is received.
	 * 
	 * @param request
	 *            the reconnect request.
	 */
	protected void eraseConnectionMoveFeedback(ReconnectRequest request) {
		if (originalAnchor == null)
			return;
		if (request.isMovingStartAnchor())
			getConnection().setSourceAnchor(originalAnchor);
		else
			getConnection().setTargetAnchor(originalAnchor);
		originalAnchor = null;
		feedbackHelper = null;
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#eraseSourceFeedback(org.eclipse.gef.Request)
	 */
	public void eraseSourceFeedback(Request request) {
		if (REQ_RECONNECT_TARGET.equals(request.getType())
				|| REQ_RECONNECT_SOURCE.equals(request.getType()))
			eraseConnectionMoveFeedback((ReconnectRequest) request);
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#getCommand(org.eclipse.gef.Request)
	 */
	public Command getCommand(Request request) {
		return null;
	}

	/**
	 * Convenience method for obtaining the host's <code>Connection</code>
	 * figure.
	 * 
	 * @return the Connection figure
	 */
	protected Connection getConnection() {
		return (Connection) ((GraphicalEditPart) getHost()).getFigure();
	}

	/**
	 * Lazily creates and returns the feedback helper for the given request. The
	 * helper will be configured as either moving the source or target end of
	 * the connection.
	 * 
	 * @param request
	 *            the reconnect request
	 * @return the feedback helper
	 */
	protected FeedbackHelper getFeedbackHelper(ReconnectRequest request) {
		if (feedbackHelper == null) {
			feedbackHelper = new FeedbackHelper();
			feedbackHelper.setConnection(getConnection());
			feedbackHelper.setMovingStartAnchor(request.isMovingStartAnchor());
		}
		return feedbackHelper;
	}

	/**
	 * Hides the focus indicator. The focus indicator is a dotted outline around
	 * the connection.
	 * 
	 * @see #showFocus()
	 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#hideFocus()
	 */
	protected void hideFocus() {
		if (focus != null) {
			removeFeedback(focus);
			focus = null;
		}
	}

	/**
	 * Shows or updates connection move feedback. Called whenever a show
	 * feedback request is received for reconnection.
	 * 
	 * @param request
	 *            the reconnect request
	 */
	protected void showConnectionMoveFeedback(ReconnectRequest request) {
		NodeEditPart node = null;
		if (request.getTarget() instanceof NodeEditPart)
			node = (NodeEditPart) request.getTarget();
		if (originalAnchor == null) {
			if (request.isMovingStartAnchor())
				originalAnchor = getConnection().getSourceAnchor();
			else
				originalAnchor = getConnection().getTargetAnchor();
		}
		ConnectionAnchor anchor = null;
		if (node != null) {
			if (request.isMovingStartAnchor())
				anchor = node.getSourceConnectionAnchor(request);
			else
				anchor = node.getTargetConnectionAnchor(request);
		}
		FeedbackHelper helper = getFeedbackHelper(request);
		helper.update(anchor, request.getLocation());
	}

	/**
	 * Shows focus around the connection.
	 * 
	 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#showFocus()
	 */
	protected void showFocus() {
		if (focus == null) {
			focus = new ConnectionFocus();
			addFeedback(focus);
		}
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#showSourceFeedback(org.eclipse.gef.Request)
	 */
	public void showSourceFeedback(Request request) {
		if (REQ_RECONNECT_SOURCE.equals(request.getType())
				|| REQ_RECONNECT_TARGET.equals(request.getType()))
			showConnectionMoveFeedback((ReconnectRequest) request);
	}

}
