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

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.Shape;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Translatable;

import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartListener;
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.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;

/**
 * Provides support for interacting with children
 * <code>GraphicalEditParts</code> with the host figure's current
 * {@link org.eclipse.draw2d.LayoutManager}.
 * <P>
 * LayoutEditPolicies are responsible for moving, resizing, re-parenting, and
 * creating children. The should provide <code>Commands</code> for all of these
 * operations. Feedback on the container can also be useful for some layouts
 * like grids.
 * <P>
 * LayoutEditPolicies will decorate the host's children with "satellite"
 * EditPolicies. These policies are installed using the
 * {@link EditPolicy#PRIMARY_DRAG_ROLE}. Simple layouts will use either
 * {@link ResizableEditPolicy} or {@link NonResizableEditPolicy}, depending on
 * how the LayoutManager works, and/or attributes of the child EditPart.
 * 
 * @author rhudson
 * @author msorens
 * @author anyssen
 */
public abstract class LayoutEditPolicy extends GraphicalEditPolicy {

	private IFigure sizeOnDropFeedback;

	private EditPartListener listener;

	/**
	 * Extends activate() to allow proper decoration of children.
	 * 
	 * @see org.eclipse.gef.EditPolicy#activate()
	 */
	public void activate() {
		setListener(createListener());
		decorateChildren();
		super.activate();
	}

	/**
	 * Returns the "satellite" EditPolicy used to decorate the child.
	 * 
	 * @param child
	 *            the child EditPart
	 * @return an EditPolicy to be installed as the
	 *         {@link EditPolicy#PRIMARY_DRAG_ROLE}
	 */
	protected abstract EditPolicy createChildEditPolicy(EditPart child);

	/**
	 * creates the EditPartListener for observing when children are added to the
	 * host.
	 * 
	 * @return EditPartListener
	 */
	protected EditPartListener createListener() {
		return new EditPartListener.Stub() {
			public void childAdded(EditPart child, int index) {
				decorateChild(child);
			}
		};
	}

	/**
	 * Override to provide custom feedback figure for the given create request.
	 * 
	 * @param createRequest
	 *            the create request
	 * @return custom feedback figure
	 */
	protected IFigure createSizeOnDropFeedback(CreateRequest createRequest) {
		return null;
	}

	/**
	 * Overrides deactivate to remove the EditPartListener.
	 * 
	 * @see org.eclipse.gef.EditPolicy#deactivate()
	 */
	public void deactivate() {
		if (sizeOnDropFeedback != null) {
			removeFeedback(sizeOnDropFeedback);
			sizeOnDropFeedback = null;
		}
		setListener(null);
		super.deactivate();
	}

	/**
	 * Decorates the child with a {@link EditPolicy#PRIMARY_DRAG_ROLE} such as
	 * {@link ResizableEditPolicy}.
	 * 
	 * @param child
	 *            the child EditPart being decorated
	 */
	protected void decorateChild(EditPart child) {
		EditPolicy policy = createChildEditPolicy(child);
		child.installEditPolicy(EditPolicy.PRIMARY_DRAG_ROLE, policy);
	}

	/**
	 * Decorates all existing children. This method is called on activation.
	 */
	protected void decorateChildren() {
		List children = getHost().getChildren();
		for (int i = 0; i < children.size(); i++)
			decorateChild((EditPart) children.get(i));
	}

	/**
	 * Erases target layout feedback. This method is the inverse of
	 * {@link #showLayoutTargetFeedback(Request)}.
	 * 
	 * @param request
	 *            the Request
	 */
	protected void eraseLayoutTargetFeedback(Request request) {
	}

	/**
	 * Erases size-on-drop feedback used during creation.
	 * 
	 * @param request
	 *            the Request
	 */
	protected void eraseSizeOnDropFeedback(Request request) {
		if (sizeOnDropFeedback != null) {
			removeFeedback(sizeOnDropFeedback);
			sizeOnDropFeedback = null;
		}
	}

	/**
	 * Calls two more specific methods depending on the Request.
	 * 
	 * @see org.eclipse.gef.EditPolicy#eraseTargetFeedback(Request)
	 */
	public void eraseTargetFeedback(Request request) {
		if (REQ_ADD.equals(request.getType())
				|| REQ_MOVE.equals(request.getType())
				|| REQ_RESIZE_CHILDREN.equals(request.getType())
				|| REQ_CREATE.equals(request.getType())
				|| REQ_CLONE.equals(request.getType()))
			eraseLayoutTargetFeedback(request);

		if (REQ_CREATE.equals(request.getType()))
			eraseSizeOnDropFeedback(request);
	}

	/**
	 * Override to return the <code>Command</code> to perform an
	 * {@link RequestConstants#REQ_ADD ADD}. By default, <code>null</code> is
	 * returned.
	 * 
	 * @param request
	 *            the ADD Request
	 * @return A command to perform the ADD.
	 */
	protected Command getAddCommand(Request request) {
		return null;
	}

	/**
	 * Override to contribute to clone requests.
	 * 
	 * @param request
	 *            the clone request
	 * @return the command contribution to the clone
	 */
	protected Command getCloneCommand(ChangeBoundsRequest request) {
		return null;
	}

	/**
	 * Factors incoming requests into various specific methods.
	 * 
	 * @see org.eclipse.gef.EditPolicy#getCommand(Request)
	 */
	public Command getCommand(Request request) {
		if (REQ_DELETE_DEPENDANT.equals(request.getType()))
			return getDeleteDependantCommand(request);

		if (REQ_ADD.equals(request.getType()))
			return getAddCommand(request);

		if (REQ_ORPHAN_CHILDREN.equals(request.getType()))
			return getOrphanChildrenCommand(request);

		if (REQ_MOVE_CHILDREN.equals(request.getType()))
			return getMoveChildrenCommand(request);

		if (REQ_CLONE.equals(request.getType()))
			return getCloneCommand((ChangeBoundsRequest) request);

		if (REQ_CREATE.equals(request.getType()))
			return getCreateCommand((CreateRequest) request);

		return null;
	}

	/**
	 * Returns the <code>Command</code> to perform a create.
	 * 
	 * @param request
	 *            the CreateRequest
	 * @return a Command to perform a create
	 */
	protected abstract Command getCreateCommand(CreateRequest request);

	/**
	 * Returns any insets that need to be applied to the creation feedback's
	 * bounds.
	 * 
	 * @param request
	 *            the create request
	 * @return insets, if necessary
	 */
	protected Insets getCreationFeedbackOffset(CreateRequest request) {
		return new Insets();
	}

	/**
	 * Returns the <code>Command</code> to delete a child. This method does not
	 * get called unless the child forwards an additional request to the
	 * container editpart.
	 * 
	 * @param request
	 *            the Request
	 * @return the Command to delete the child
	 */
	protected Command getDeleteDependantCommand(Request request) {
		return null;
	}

	/**
	 * Returns the host's {@link GraphicalEditPart#getContentPane() contentPane}
	 * . The contentPane is the Figure which parents the childrens' figures. It
	 * is also the figure which has the LayoutManager that corresponds to this
	 * EditPolicy. All operations should be interpreted with respect to this
	 * figure.
	 * 
	 * @return the Figure that owns the corresponding <code>LayoutManager</code>
	 */
	protected IFigure getLayoutContainer() {
		return ((GraphicalEditPart) getHost()).getContentPane();
	}

	/**
	 * Returns the <code>Command</code> to move a group of children.
	 * 
	 * @param request
	 *            the Request
	 * @return the Command to perform the move
	 */
	protected abstract Command getMoveChildrenCommand(Request request);

	/**
	 * Returns the <code>Command</code> to orphan a group of children. The
	 * contribution to orphan might contain two parts, both of which are
	 * optional. The first part is to actually remove the children from their
	 * existing parent. Some application models will perform an orphan
	 * implicitly when the children are added to their new parent. The second
	 * part is to perform some adjustments on the remaining children. For
	 * example, a Table layout might simplify itself by collapsing any unused
	 * columns and rows.
	 * 
	 * @param request
	 *            the Request
	 * @return <code>null</code> or a Command to perform an orphan
	 */
	protected Command getOrphanChildrenCommand(Request request) {
		return null;
	}

	/**
	 * Lazily creates and returns the Figure to use for size-on-drop feedback.
	 * 
	 * @param createRequest
	 *            the createRequest
	 * @return the size-on-drop feedback figure
	 */
	protected IFigure getSizeOnDropFeedback(CreateRequest createRequest) {
		if (sizeOnDropFeedback == null)
			sizeOnDropFeedback = createSizeOnDropFeedback(createRequest);

		return getSizeOnDropFeedback();
	}

	/**
	 * Lazily creates and returns the Figure to use for size-on-drop feedback.
	 * 
	 * @return the size-on-drop feedback figure
	 */
	protected IFigure getSizeOnDropFeedback() {
		if (sizeOnDropFeedback == null) {
			sizeOnDropFeedback = new RectangleFigure();
			FigureUtilities.makeGhostShape((Shape) sizeOnDropFeedback);
			((Shape) sizeOnDropFeedback).setLineStyle(Graphics.LINE_DASHDOT);
			sizeOnDropFeedback.setForegroundColor(ColorConstants.white);
			addFeedback(sizeOnDropFeedback);
		}
		return sizeOnDropFeedback;
	}

	/**
	 * Returns the <i>host</i> if the Request is an ADD, MOVE, or CREATE.
	 * 
	 * @see org.eclipse.gef.EditPolicy#getTargetEditPart(Request)
	 */
	public EditPart getTargetEditPart(Request request) {
		if (REQ_ADD.equals(request.getType())
				|| REQ_MOVE.equals(request.getType())
				|| REQ_CREATE.equals(request.getType())
				|| REQ_CLONE.equals(request.getType()))
			return getHost();
		return null;
	}

	/**
	 * Sets the EditPartListener used to decorate new children. If the listener
	 * is currently set, it will be unhooked. If the new value is not
	 * <code>null</code>, it will be hooked.
	 * <P>
	 * The listener must be remembered in case this EditPolicy is removed from
	 * the host and replaced with another LayoutEditPolicy.
	 * 
	 * @param listener
	 *            <code>null</code> or the listener.
	 */
	protected void setListener(EditPartListener listener) {
		if (this.listener != null)
			getHost().removeEditPartListener(this.listener);
		this.listener = listener;
		if (this.listener != null)
			getHost().addEditPartListener(this.listener);
	}

	/**
	 * Shows target layout feedback. During <i>moves</i>, <i>reparents</i>, and
	 * <i>creation</i>, this method is called to allow the LayoutEditPolicy to
	 * temporarily show features of its layout that will help the User
	 * understand what will happen if the operation is performed in the current
	 * location.
	 * <P>
	 * By default, no feedback is shown.
	 * 
	 * @param request
	 *            the Request
	 * @see #eraseLayoutTargetFeedback(Request)
	 */
	protected void showLayoutTargetFeedback(Request request) {
	}

	/**
	 * Shows size-on-drop feedback during creation.
	 * 
	 * @param request
	 *            the CreateRequest
	 */
	protected void showSizeOnDropFeedback(CreateRequest request) {
	}

	/**
	 * Factors feedback requests into two more specific methods.
	 * 
	 * @see org.eclipse.gef.EditPolicy#showTargetFeedback(Request)
	 */
	public void showTargetFeedback(Request request) {
		if (REQ_ADD.equals(request.getType())
				|| REQ_CLONE.equals(request.getType())
				|| REQ_MOVE.equals(request.getType())
				|| REQ_RESIZE_CHILDREN.equals(request.getType())
				|| REQ_CREATE.equals(request.getType()))
			showLayoutTargetFeedback(request);

		if (REQ_CREATE.equals(request.getType())) {
			CreateRequest createReq = (CreateRequest) request;
			if (createReq.getSize() != null) {
				showSizeOnDropFeedback(createReq);
			}
		}
	}

	/**
	 * Removes the decoration added in {@link #decorateChild(EditPart)}.
	 * 
	 * @param child
	 *            the child whose decoration is being removed.
	 */
	protected void undecorateChild(EditPart child) {
		child.removeEditPolicy(EditPolicy.PRIMARY_DRAG_ROLE);
	}

	/**
	 * Removes all decorations added by {@link #decorateChildren()}.
	 */
	protected void undecorateChildren() {
		List children = getHost().getChildren();
		for (int i = 0; i < children.size(); i++)
			undecorateChild((EditPart) children.get(i));
	}

	/**
	 * Returns the layout's origin relative to the
	 * {@link LayoutEditPolicy#getLayoutContainer()}. In other words, what Point
	 * on the parent Figure does the LayoutManager use a reference when
	 * generating the child figure's bounds from the child's constraint.
	 * <P>
	 * By default, it is assumed that the layout manager positions children
	 * relative to the client area of the layout container. Thus, when
	 * processing Viewer-relative Points or Rectangles, the clientArea's
	 * location (top-left corner) will be subtracted from the Point/Rectangle,
	 * resulting in an offset from the LayoutOrigin.
	 * 
	 * @return Point
	 * @since 3.7 Moved up from ConstrainedLayoutEditPolicy
	 */
	protected Point getLayoutOrigin() {
		return getLayoutContainer().getClientArea().getLocation();
	}

	/**
	 * Translates a {@link Translatable} in absolute coordinates to be
	 * layout-relative, i.e. relative to the {@link #getLayoutContainer()}'s
	 * origin, which is obtained via {@link #getLayoutOrigin()}.
	 * 
	 * @param t
	 *            the Translatable in absolute coordinates to be translated to
	 *            layout-relative coordinates.
	 * @since 3.7
	 */
	protected void translateFromAbsoluteToLayoutRelative(Translatable t) {
		IFigure figure = getLayoutContainer();
		figure.translateToRelative(t);
		figure.translateFromParent(t);
		Point negatedLayoutOrigin = getLayoutOrigin().getNegated();
		t.performTranslate(negatedLayoutOrigin.x, negatedLayoutOrigin.y);
	}

	/**
	 * Translates a {@link Translatable} in layout-relative coordinates, i.e.
	 * relative to {@link #getLayoutContainer()}'s origin which is obtained via
	 * {@link #getLayoutOrigin()}, into absolute coordinates.
	 * 
	 * @param t
	 *            the Translatable in layout-relative coordinates to be
	 *            translated into absolute coordinates.
	 * @since 3.7
	 */
	protected void translateFromLayoutRelativeToAbsolute(Translatable t) {
		IFigure figure = getLayoutContainer();
		Point layoutOrigin = getLayoutOrigin();
		t.performTranslate(layoutOrigin.x, layoutOrigin.y);
		figure.translateToParent(t);
		figure.translateToAbsolute(t);
	}

}
