/*******************************************************************************
 * Copyright (c) 2000, 2005 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.draw2d;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.geometry.Transposer;

/**
 * Used to place IFigures along the endpoint or starting point of a {@link Connection}. 
 * <code>uDistance</code> represents the distance from the Connection's owner to the 
 * IFigure. <code>vDistance</code> represents the distance from the IFigure to the
 * Connection itself.
 */
public class ConnectionEndpointLocator 
	implements Locator
{

private boolean end;
private Connection conn;
private int uDistance;
private int vDistance;
private static Rectangle figureBounds;

/**
 * Transposes the location if the connection point is along the top or bottom of its owner
 * figure.
 */
protected Transposer transposer = new Transposer();

/** 
 * Constructs a ConnectionEndpointLocator using the given {@link Connection}.  If
 * <i>isEnd</i> is <code>true</code>, the location is relative to the Connection's end (or
 * target) point. If <i>isEnd</i> is <code>false</code>, the location is relative to the
 * Connection's start (or source) point.
 *
 * @param c The Connection
 * @param isEnd <code>true</code> is location is relative to end point
 * @since 2.0
 */
public ConnectionEndpointLocator(Connection c, boolean isEnd) {
	end = isEnd;
	conn = c;
	uDistance = 14;
	vDistance = 4;
	figureBounds = new Rectangle();
}

/*
 * Returns an integer representing the side of the passed Rectangle that a point lies on.
 *  1 == Top
 *  2 == Right 
 *  3 == Bottom 
 *  4 == Left
 *  
 * @param loc The point that is to be located
 */
private int calculateConnectionLocation(Point loc, Point topLeft, Point center) {
	double m1, m2 = 0;	
	m1 = (double)(topLeft.y - center.y) / (double)(topLeft.x - center.x);
	
	if (loc.x - center.x != 0)	
		m2 = (double)(loc.y - center.y) / (double)(loc.x - center.x);
	
	if (loc.x == center.x) { 
		// Case where m2 is vertical
		if (loc.y < center.y)
			return 3;		
		else
			return 1;
	} else if (Math.abs(m2) <= Math.abs(m1)) { 
		// Connection start point along left or right side
		if (loc.x < center.x) 
			return 4;
		else
			return 2;
	} else { 
		// Connection start point along top or bottom
		if (loc.y < center.y)
			return 3;
		else
			return 1;
	}
}

/*
 * This method is used to calculate the "quadrant" value of a connection that does not
 * have an owner on its starting point. 
 * 
 * 1 == Top
 * 2 == Right
 * 3 == Bottom
 * 4 == Left
 *
 * @param startPoint The starting point of the connection.
 * @param endPoint The end point of the connection.
 */
private int calculateConnectionLocation(Point startPoint, Point endPoint) {
	if (Math.abs(endPoint.x - startPoint.x) > Math.abs(endPoint.y - startPoint.y)) {
		if (endPoint.x > startPoint.x)
			return 2;
		else
			return 4;
	} else {
		if (endPoint.y > startPoint.y)
			return 1;
		else
			return 3;
	}				
}	

/*
 * Calculates 'tan' which is used as a factor for y adjustment when placing the connection
 * label. 'tan' is capped at 1.0 in the positive direction and -1.0 in the negative
 * direction.
 * 
 * @param startPoint The starting point of the connection.
 * @param endPoint The end point of the connection.
 * @since 2.0
 */
private double calculateTan(Point startPoint, Point endPoint) {
	double tan = 0;
	if (endPoint.x == startPoint.x)
		tan = 1.0;
	else
		tan = (double)(endPoint.y - startPoint.y) 
				/ (double)(endPoint.x - startPoint.x);	      		      
	if (tan > 1)
		tan = 1.0;
	else if (tan < -1)
		tan = -1.0;
	
	return tan;
}

private int calculateYShift(int figureWidth, int figureHeight) {
	int yShift = 0;
	if (vDistance < 0)
		yShift = -figureHeight;
	else if (vDistance == 0)
		yShift = -figureHeight / 2;
	return yShift;
}

private Connection getConnection() {
	return conn;
}

private IFigure getConnectionOwner() {
	IFigure connOwner;
	if (isEnd())
		connOwner = conn.getTargetAnchor().getOwner();
	else
		connOwner = conn.getSourceAnchor().getOwner();
		
	return connOwner;	
}

/**
 * Returns the distance in pixels from the anchor's owner.
 * @return the offset distance from the endpoint figure
 */
public int getUDistance() {
	return uDistance;
}

/**
 * Returns the distance in pixels from the connection
 * @return the offset from the connection itself
 */
public int getVDistance() {
	return vDistance;
}

private boolean isEnd() {
	return end;
}

/**
 * Relocates the given IFigure at either the source or target end of the Connection, 
 * based on the <code>boolean</code> given in the constructor
 * {@link #ConnectionEndpointLocator(Connection, boolean)}.
 * 
 * @param figure The figure to relocate
 */
public void relocate(IFigure figure) {
	Connection conn = getConnection();
	Point startPoint = Point.SINGLETON;
	Point endPoint = new Point();
	
	int startPointPosition = 0;
	int endPointPosition = 1;
	if (isEnd()) {
		startPointPosition = conn.getPoints().size() - 1;
		endPointPosition = startPointPosition - 1;
	}
	
	conn.getPoints().getPoint(startPoint, startPointPosition);
	conn.getPoints().getPoint(endPoint, endPointPosition);
	
	IFigure connOwner = getConnectionOwner();
	
	int quadrant;
	if (connOwner != null) {
		Rectangle connOwnerBounds = connOwner.getBounds();
		Point connOwnerCenter = connOwnerBounds.getCenter();
		Point connOwnerTL = connOwnerBounds.getTopLeft();
		quadrant = calculateConnectionLocation(startPoint, connOwnerTL, connOwnerCenter);
	} else
		quadrant = calculateConnectionLocation(startPoint, endPoint);

	int cos = 1;
	transposer.setEnabled(false);
	
	/*
	 * Label placement calculations are done as if the connection point is along the left
	 * or right side of the  figure. If the connection point is along the top or bottom,
	 * values are transposed.
	 */
	if (quadrant == 1 || quadrant == 3)
		transposer.setEnabled(true);
	
	if (quadrant == 3 || quadrant == 4)
		cos = -1;		

	Dimension figureSize = transposer.t(figure.getPreferredSize());
	startPoint = transposer.t(startPoint);
	endPoint = transposer.t(endPoint);

	double tan = calculateTan(startPoint, endPoint);

	int figureWidth = figureSize.width;
	int figureHeight = figureSize.height;
	int yShift = calculateYShift(figureWidth, figureHeight);
	
	Point figurePoint = 
		new Point(startPoint.x + (uDistance * cos) + figureWidth * ((cos - 1) / 2),
				(int)(startPoint.y + cos * uDistance * tan + vDistance + yShift));

	figureBounds.setSize(transposer.t(figureSize));
	figureBounds.setLocation(transposer.t(figurePoint));
	figure.setBounds(figureBounds);
}

/**
 * Sets the distance in pixels from the Connection's owner.
 * 
 * @param distance Number of pixels to place the ConnectionEndpointLocator from its owner.
 * @since 2.0
 */
public void setUDistance(int distance) {
	uDistance = distance;
}

/**
 * Sets the distance in pixels from the Connection.
 * 
 * @param distance Number of pixels to place the ConnectionEndpointLocator from its
 * Connection.
 * @since 2.0
 */
public void setVDistance(int distance) {
	vDistance = distance;
}

}
