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

import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.PositionConstants;

import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.handles.ResizableHandleKit;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.tools.ResizeTracker;

/**
 * Provides support for selecting, positioning, and resizing an edit part.
 * Selection is indicated via eight square handles along the editpart's figure,
 * and a rectangular handle that outlines the edit part with a 1-pixel black
 * line. The eight square handles will resize the current selection in the eight
 * primary directions. The rectangular handle will drag the current selection
 * using a {@link org.eclipse.gef.tools.DragEditPartsTracker}.
 * <p>
 * During feedback, a rectangle filled using XOR and outlined with dashes is
 * drawn. Subclasses may tailor the feedback.
 * 
 * @author hudsonr
 * @author msorens
 * @author anyssen
 */
public class ResizableEditPolicy extends NonResizableEditPolicy {

	private int resizeDirections = PositionConstants.NSEW;

	/**
	 * Constructs a new {@link ResizableEditPolicy}.
	 * 
	 * @since 3.7
	 */
	public ResizableEditPolicy() {
	}

	/**
	 * @see org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy#createSelectionHandles()
	 */
	protected List createSelectionHandles() {
		if (resizeDirections == PositionConstants.NONE) {
			// non resizable, so delegate to super implementation
			return super.createSelectionHandles();
		}

		// resizable in at least one direction
		List list = new ArrayList();
		createMoveHandle(list);
		createResizeHandle(list, PositionConstants.NORTH);
		createResizeHandle(list, PositionConstants.EAST);
		createResizeHandle(list, PositionConstants.SOUTH);
		createResizeHandle(list, PositionConstants.WEST);
		createResizeHandle(list, PositionConstants.SOUTH_EAST);
		createResizeHandle(list, PositionConstants.SOUTH_WEST);
		createResizeHandle(list, PositionConstants.NORTH_WEST);
		createResizeHandle(list, PositionConstants.NORTH_EAST);
		return list;
	}

	/**
	 * Creates a 'resize' handle, which uses a {@link ResizeTracker} in case
	 * resizing is allowed in the respective direction, otherwise returns a drag
	 * handle by delegating to
	 * {@link NonResizableEditPolicy#createDragHandle(List, int)}.
	 * 
	 * @param handles
	 *            The list of handles to add the resize handle to
	 * @param direction
	 *            A position constant indicating the direction to create the
	 *            handle for
	 * @since 3.7
	 */
	protected void createResizeHandle(List handles, int direction) {
		if ((resizeDirections & direction) == direction) {
			ResizableHandleKit.addHandle((GraphicalEditPart) getHost(),
					handles, direction, getResizeTracker(direction), Cursors
							.getDirectionalCursor(direction, getHostFigure()
									.isMirrored()));
		} else {
			// display 'resize' handle to allow dragging or indicate selection
			// only
			createDragHandle(handles, direction);
		}
	}

	/**
	 * Returns a resize tracker for the given direction to be used by a resize
	 * handle.
	 * 
	 * @param direction
	 *            the resize direction for the {@link ResizeTracker}.
	 * @return a new {@link ResizeTracker}
	 * @since 3.7
	 */
	protected ResizeTracker getResizeTracker(int direction) {
		return new ResizeTracker((GraphicalEditPart) getHost(), direction);
	}

	/**
	 * Dispatches erase requests to more specific methods.
	 * 
	 * @see org.eclipse.gef.EditPolicy#eraseSourceFeedback(org.eclipse.gef.Request)
	 */
	public void eraseSourceFeedback(Request request) {
		if (REQ_RESIZE.equals(request.getType()))
			eraseChangeBoundsFeedback((ChangeBoundsRequest) request);
		else
			super.eraseSourceFeedback(request);
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#getCommand(org.eclipse.gef.Request)
	 */
	public Command getCommand(Request request) {
		if (REQ_RESIZE.equals(request.getType())) {
			return getResizeCommand((ChangeBoundsRequest) request);
		}
		return super.getCommand(request);
	}

	/**
	 * Returns the command contribution for the given resize request. By
	 * default, the request is re-dispatched to the host's parent as a
	 * {@link org.eclipse.gef.RequestConstants#REQ_RESIZE_CHILDREN}. The
	 * parent's edit policies determine how to perform the resize based on the
	 * layout manager in use.
	 * 
	 * @param request
	 *            the resize request
	 * @return the command contribution obtained from the parent
	 */
	protected Command getResizeCommand(ChangeBoundsRequest request) {
		ChangeBoundsRequest req = new ChangeBoundsRequest(REQ_RESIZE_CHILDREN);
		req.setEditParts(getHost());

		req.setMoveDelta(request.getMoveDelta());
		req.setSizeDelta(request.getSizeDelta());
		req.setLocation(request.getLocation());
		req.setExtendedData(request.getExtendedData());
		req.setResizeDirection(request.getResizeDirection());
		return getHost().getParent().getCommand(req);
	}

	/**
	 * Sets the directions in which handles should allow resizing. Valid values
	 * are bit-wise combinations of:
	 * <UL>
	 * <LI>{@link PositionConstants#NORTH}
	 * <LI>{@link PositionConstants#SOUTH}
	 * <LI>{@link PositionConstants#EAST}
	 * <LI>{@link PositionConstants#WEST}
	 * </UL>
	 * 
	 * @param newDirections
	 *            the direction in which resizing is allowed
	 */
	public void setResizeDirections(int newDirections) {
		resizeDirections = newDirections;
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#showSourceFeedback(org.eclipse.gef.Request)
	 */
	public void showSourceFeedback(Request request) {
		if (REQ_RESIZE.equals(request.getType())) {
			showChangeBoundsFeedback((ChangeBoundsRequest) request);
		} else {
			super.showSourceFeedback(request);
		}
	}

	/**
	 * @see org.eclipse.gef.EditPolicy#understandsRequest(org.eclipse.gef.Request)
	 */
	public boolean understandsRequest(Request request) {
		if (REQ_RESIZE.equals(request.getType())) {
			// check all resize directions of the request are supported
			int resizeDirections = ((ChangeBoundsRequest) request)
					.getResizeDirection();
			return (resizeDirections & getResizeDirections()) == resizeDirections;
		}
		return super.understandsRequest(request);
	}

	/**
	 * Returns the directions in which resizing should be allowed
	 * 
	 * Valid values are bit-wise combinations of:
	 * <UL>
	 * <LI>{@link PositionConstants#NORTH}
	 * <LI>{@link PositionConstants#SOUTH}
	 * <LI>{@link PositionConstants#EAST}
	 * <LI>{@link PositionConstants#WEST}
	 * </UL>
	 * or {@link PositionConstants#NONE}.
	 * 
	 */
	public int getResizeDirections() {
		return resizeDirections;
	}
}
