/*******************************************************************************
 * Copyright (c) 2003, 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.internal.ui.rulers;

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

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;

import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.editpolicies.GraphicalEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;

/**
 * @author Pratik Shah
 */
public class DragGuidePolicy extends GraphicalEditPolicy {

	private List attachedEditParts = null;
	private IFigure dummyGuideFigure, dummyLineFigure;
	private boolean dragInProgress = false;

	protected IFigure createDummyLineFigure() {
		return new Figure();
	}

	protected GuideFigure createDummyGuideFigure() {
		return new GuidePlaceHolder(getGuideEditPart().isHorizontal());
	}

	/*
	 * Fix for Bug# 65885 If you undo guide creation while dragging that guide,
	 * it was leaving behind drag feedback. This was because by the time
	 * eraseSourceFeedback() was being called, the guide edit part had been
	 * deactivated (and hence eraseSourceFeedback is never called on this
	 * policy). So we make sure that this policy cleans up when it is
	 * deactivated.
	 */
	public void deactivate() {
		removeFeedback();
		super.deactivate();
	}

	private void eraseAttachedPartsFeedback(Request request) {
		if (attachedEditParts != null) {
			ChangeBoundsRequest req = new ChangeBoundsRequest(request.getType());
			req.setEditParts(attachedEditParts);

			Iterator i = attachedEditParts.iterator();

			while (i.hasNext())
				((EditPart) i.next()).eraseSourceFeedback(req);
			attachedEditParts = null;
		}
	}

	public void eraseSourceFeedback(Request request) {
		getGuideEditPart().updateLocationOfFigures(
				getGuideEditPart().getZoomedPosition());
		getHostFigure().setVisible(true);
		getGuideEditPart().getGuideLineFigure().setVisible(true);
		removeFeedback();
		getGuideEditPart().setCurrentCursor(null);
		dragInProgress = false;

		eraseAttachedPartsFeedback(request);
	}

	private List getAttachedEditParts() {
		if (attachedEditParts == null)
			attachedEditParts = getGuideEditPart().getRulerProvider()
					.getAttachedEditParts(
							getHost().getModel(),
							((RulerEditPart) getHost().getParent())
									.getDiagramViewer());
		return attachedEditParts;
	}

	public Command getCommand(Request request) {
		Command cmd;
		final ChangeBoundsRequest req = (ChangeBoundsRequest) request;
		if (isDeleteRequest(req)) {
			cmd = getGuideEditPart().getRulerProvider().getDeleteGuideCommand(
					getHost().getModel());
		} else {
			int pDelta;
			if (getGuideEditPart().isHorizontal()) {
				pDelta = req.getMoveDelta().y;
			} else {
				pDelta = req.getMoveDelta().x;
			}
			if (isMoveValid(getGuideEditPart().getZoomedPosition() + pDelta)) {
				ZoomManager zoomManager = getGuideEditPart().getZoomManager();
				if (zoomManager != null) {
					pDelta = (int) Math.round(pDelta / zoomManager.getZoom());
				}
				cmd = getGuideEditPart().getRulerProvider()
						.getMoveGuideCommand(getHost().getModel(), pDelta);
			} else {
				cmd = UnexecutableCommand.INSTANCE;
			}
		}
		return cmd;
	}

	protected IFigure getDummyGuideFigure() {
		if (dummyGuideFigure == null) {
			dummyGuideFigure = createDummyGuideFigure();
		}
		return dummyGuideFigure;
	}

	protected IFigure getDummyLineFigure() {
		if (dummyLineFigure == null) {
			dummyLineFigure = createDummyLineFigure();
		}
		return dummyLineFigure;
	}

	protected GuideEditPart getGuideEditPart() {
		return (GuideEditPart) getHost();
	}

	protected boolean isDeleteRequest(ChangeBoundsRequest req) {
		int pos, max, min;
		if (getGuideEditPart().isHorizontal()) {
			pos = req.getLocation().x;
			Rectangle zone = getHostFigure().getBounds().getExpanded(
					GuideEditPart.DELETE_THRESHOLD, 0);
			min = zone.x;
			max = min + zone.width;
		} else {
			pos = req.getLocation().y;
			Rectangle zone = getHostFigure().getBounds().getExpanded(0,
					GuideEditPart.DELETE_THRESHOLD);
			min = zone.y;
			max = min + zone.height;
		}
		return pos < min || pos > max;
	}

	protected boolean isMoveValid(int zoomedPosition) {
		boolean result = true;
		ZoomManager zoomManager = getGuideEditPart().getZoomManager();
		int position = zoomedPosition;
		if (zoomManager != null) {
			position = (int) Math.round(position / zoomManager.getZoom());
		}
		Iterator guides = getGuideEditPart().getRulerProvider().getGuides()
				.iterator();
		while (guides.hasNext()) {
			Object guide = guides.next();
			if (guide != getGuideEditPart().getModel()) {
				int guidePos = getGuideEditPart().getRulerProvider()
						.getGuidePosition(guide);
				if (Math.abs(guidePos - position) < GuideEditPart.MIN_DISTANCE_BW_GUIDES) {
					result = false;
					break;
				}
			}
		}

		return result;
	}

	private void removeFeedback() {
		if (getDummyGuideFigure().getParent() != null) {
			getDummyGuideFigure().getParent().remove(getDummyGuideFigure());
		}
		if (getDummyLineFigure().getParent() != null) {
			getDummyLineFigure().getParent().remove(getDummyLineFigure());
		}
	}

	private void showAttachedPartsFeedback(ChangeBoundsRequest request) {
		ChangeBoundsRequest req = new ChangeBoundsRequest(request.getType());
		req.setEditParts(getAttachedEditParts());

		if (getGuideEditPart().isHorizontal())
			req.setMoveDelta(new Point(0, request.getMoveDelta().y));
		else
			req.setMoveDelta(new Point(request.getMoveDelta().x, 0));

		Iterator i = getAttachedEditParts().iterator();

		while (i.hasNext())
			((EditPart) i.next()).showSourceFeedback(req);
	}

	public void showSourceFeedback(Request request) {
		if (!dragInProgress) {
			dragInProgress = true;
			// add the placeholder guide figure to the ruler
			getHostFigure().getParent().add(getDummyGuideFigure(), 0);
			((GraphicalEditPart) getHost().getParent()).setLayoutConstraint(
					getHost(), getDummyGuideFigure(), new Integer(
							getGuideEditPart().getZoomedPosition()));
			getDummyGuideFigure().setBounds(getHostFigure().getBounds());
			// add the invisible placeholder line figure to the primary viewer
			getGuideEditPart().getGuideLayer().add(getDummyLineFigure(), 0);
			getGuideEditPart().getGuideLayer().setConstraint(
					getDummyLineFigure(),
					new Boolean(getGuideEditPart().isHorizontal()));
			getDummyLineFigure().setBounds(
					getGuideEditPart().getGuideLineFigure().getBounds());
			// move the guide being dragged to the last index so that it's drawn
			// on
			// top of other guides
			List children = getHostFigure().getParent().getChildren();
			children.remove(getHostFigure());
			children.add(getHostFigure());
		}
		ChangeBoundsRequest req = (ChangeBoundsRequest) request;
		if (isDeleteRequest(req)) {
			getHostFigure().setVisible(false);
			getGuideEditPart().getGuideLineFigure().setVisible(false);
			getGuideEditPart().setCurrentCursor(SharedCursors.ARROW);
			eraseAttachedPartsFeedback(request);
		} else {
			int newPosition;
			if (getGuideEditPart().isHorizontal()) {
				newPosition = getGuideEditPart().getZoomedPosition()
						+ req.getMoveDelta().y;
			} else {
				newPosition = getGuideEditPart().getZoomedPosition()
						+ req.getMoveDelta().x;
			}
			getHostFigure().setVisible(true);
			getGuideEditPart().getGuideLineFigure().setVisible(true);
			if (isMoveValid(newPosition)) {
				getGuideEditPart().setCurrentCursor(null);
				getGuideEditPart().updateLocationOfFigures(newPosition);
				showAttachedPartsFeedback(req);
			} else {
				getGuideEditPart().setCurrentCursor(SharedCursors.NO);
				getGuideEditPart().updateLocationOfFigures(
						getGuideEditPart().getZoomedPosition());
				eraseAttachedPartsFeedback(request);
			}
		}
	}

	public boolean understandsRequest(Request req) {
		return req.getType().equals(REQ_MOVE);
	}

}
