/**
 * Copyright (c) 2013 committers of YAKINDU 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:
 * 	committers of YAKINDU - initial API and implementation
 * 
 */
package org.yakindu.base.xtext.utils.gmf.commands;

import java.util.List;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gmf.runtime.common.core.command.CommandResult;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramRootEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.gef.ui.figures.SlidableAnchor;
import org.eclipse.gmf.runtime.notation.Anchor;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.IdentityAnchor;
import org.eclipse.gmf.runtime.notation.NotationFactory;
import org.eclipse.gmf.runtime.notation.View;

/**
 * 
 * Recalculates the {@link IdentityAnchor}s to recalculate connection anchors
 * when a node is resized
 * 
 * Parts are copied from Sirius ShiftEdgeIdentityAnchorOperation
 * 
 */
public class AdjustIdentityAnchorCommand extends AbstractTransactionalCommand {

	final private static char TERMINAL_START_CHAR = '(';
	final private static char TERMINAL_DELIMITER_CHAR = ',';
	final private static char TERMINAL_END_CHAR = ')';

	private final static PrecisionPoint DEFAULT_POINT = new PrecisionPoint(0.5d, 0.5d);

	private ChangeBoundsRequest request;

	private Dimension futureSize;

	private PrecisionPoint delta;

	public AdjustIdentityAnchorCommand(TransactionalEditingDomain domain, ChangeBoundsRequest request) {
		super(domain, "Adjusting anchors", null);
		this.request = request;

	}

	@Override
	protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
		@SuppressWarnings("unchecked")
		List<IGraphicalEditPart> editParts = request.getEditParts();
		for (IGraphicalEditPart editPart : editParts) {
			adjustAnchors(editPart);
		}
		return CommandResult.newOKCommandResult();
	}

	@SuppressWarnings("unchecked")
	protected void adjustAnchors(IGraphicalEditPart editPart) {
		if (editPart instanceof IGraphicalEditPart) {
			View view = ((IGraphicalEditPart) editPart).getNotationView();
			List<Edge> targetEdges = view.getTargetEdges();
			for (Edge edge : targetEdges) {
				handleEdge(edge, editPart, false);
			}
			List<Edge> sourceEdges = view.getSourceEdges();
			for (Edge edge : sourceEdges) {
				handleEdge(edge, editPart, true);
			}
		}
	}

	private void handleEdge(Edge edge, EditPart editPart, boolean sourceAnchor) {
		Anchor anchorToModify;
		if (sourceAnchor) {
			anchorToModify = edge.getSourceAnchor();
		} else {
			anchorToModify = edge.getTargetAnchor();
		}
		String terminalString = composeTerminalString(DEFAULT_POINT);
		if (anchorToModify instanceof IdentityAnchor) {
			terminalString = ((IdentityAnchor) anchorToModify).getId();
		}
		PrecisionPoint anchorPoint = BaseSlidableAnchor.parseTerminalString(terminalString);
		PrecisionPoint newPoint = computeNewAnchor(anchorPoint, editPart);
		String newTerminalString = new SlidableAnchor(null, newPoint).getTerminal();
		if (anchorToModify instanceof IdentityAnchor) {
			((IdentityAnchor) anchorToModify).setId(newTerminalString);
		} else if (anchorToModify == null) {
			// Create a new one
			IdentityAnchor newAnchor = NotationFactory.eINSTANCE.createIdentityAnchor();
			newAnchor.setId(newTerminalString);
			if (sourceAnchor) {
				edge.setSourceAnchor(newAnchor);
			} else {
				edge.setTargetAnchor(newAnchor);
			}
		}
	}

	protected double getScale(EditPart part) {
		double scale = 1.0;
		if (part.getRoot() instanceof DiagramRootEditPart) {
			DiagramRootEditPart rootEditPart = (DiagramRootEditPart) part.getRoot();
			scale = rootEditPart.getZoomManager().getZoom();
		}
		return scale;
	}

	private PrecisionPoint computeNewAnchor(PrecisionPoint currentAnchorPoint, EditPart editPart) {

		double scale = getScale(editPart);
		IFigure figure = ((IGraphicalEditPart) editPart).getFigure();
		Rectangle bounds = figure.getBounds();
		if (figure instanceof HandleBounds) {
			bounds = ((HandleBounds) figure).getHandleBounds();
		}

		Point currentRelativePoint = getAnchorRelativePoint(currentAnchorPoint, bounds);

		if (futureSize != null && delta != null) {
			// In case of border node, the real location is computed earlier
			// (according to BorderItemLocator). The corresponding futureSize
			// and delta are used instead of the request data.
			return new PrecisionPoint(((double) (currentRelativePoint.x - delta.x)) / futureSize.width,
					((double) (currentRelativePoint.y - delta.y)) / futureSize.height);
		} else {

			double logicalWidthDelta = request.getSizeDelta().width / scale;
			double logicalHeightDelta = request.getSizeDelta().height / scale;

			int direction = request.getResizeDirection();

			double newRelativeX = computeNewXRelativeLocation(direction, currentRelativePoint, logicalWidthDelta);
			double newRelativeY = computeNewYRelativeLocation(direction, currentRelativePoint, logicalHeightDelta);

			return new PrecisionPoint(newRelativeX / (bounds.width() + logicalWidthDelta),
					newRelativeY / (bounds.height() + logicalHeightDelta));
		}
	}

	protected Point getAnchorRelativePoint(PrecisionPoint currentAnchorPoint, Rectangle bounds) {
		return new PrecisionPoint(bounds.width() * currentAnchorPoint.preciseX(),
				bounds.height() * currentAnchorPoint.preciseY());
	}

	private double computeNewXRelativeLocation(int direction, Point currentRelativePoint, double logicalWidthDelta) {

		if (direction == PositionConstants.NORTH_WEST || direction == PositionConstants.WEST
				|| direction == PositionConstants.SOUTH_WEST) {
			return currentRelativePoint.preciseX() + logicalWidthDelta;
		} else {

			return currentRelativePoint.preciseX();
		}
	}

	protected double computeNewYRelativeLocation(int direction, Point currentRelativePoint, double logicalHeightDelta) {

		if (direction == PositionConstants.NORTH_WEST || direction == PositionConstants.NORTH
				|| direction == PositionConstants.NORTH_EAST) {
			return currentRelativePoint.preciseY() + logicalHeightDelta;
		} else {
			return currentRelativePoint.preciseY();
		}
	}

	protected String composeTerminalString(PrecisionPoint p) {
		StringBuffer s = new StringBuffer(24);
		s.append(TERMINAL_START_CHAR);
		s.append(p.preciseX());
		s.append(TERMINAL_DELIMITER_CHAR);
		s.append(p.preciseY());
		s.append(TERMINAL_END_CHAR);
		return s.toString();
	}
}