/*******************************************************************************
 * 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
 *     Research Group Software Construction,
 *     RWTH Aachen University, Germany - Contribution for Bugzilla 245182
 *
 *******************************************************************************/
package org.eclipse.draw2d.geometry;

/**
 * Represents a vector within 2-dimensional Euclidean space.
 * 
 * @since 3.6
 */
public class Vector {

	/** the X value */
	public double x;
	/** the Y value */
	public double y;

	// internal constant used for comparisons.
	private static final Vector NULL = new Vector(0, 0);

	/**
	 * Constructs a Vector pointed in the specified direction.
	 * 
	 * @param x
	 *            X value.
	 * @param y
	 *            Y value.
	 */
	public Vector(double x, double y) {
		this.x = x;
		this.y = y;
	}

	/**
	 * Constructs a Vector pointed in the direction specified by a Point.
	 * 
	 * @param p
	 *            the point
	 */
	public Vector(PrecisionPoint p) {
		x = p.preciseX();
		y = p.preciseY();
	}

	/**
	 * Constructs a Vector representing the direction and magnitude between to
	 * provided Points.
	 * 
	 * @param start
	 *            starting point
	 * @param end
	 *            End Point
	 */
	public Vector(PrecisionPoint start, PrecisionPoint end) {
		x = end.preciseX() - start.preciseX();
		y = end.preciseY() - start.preciseY();
	}

	/**
	 * Constructs a Vector representing the difference between two provided
	 * Vectors.
	 * 
	 * @param start
	 *            The start Ray
	 * @param end
	 *            The end Ray
	 */
	public Vector(Vector start, Vector end) {
		x = end.x - start.x;
		y = end.y - start.y;
	}

	/**
	 * Calculates the magnitude of the cross product of this Vector with
	 * another. Represents the amount by which two Vectors are directionally
	 * different. Parallel Vectors return a value of 0.
	 * 
	 * @param other
	 *            Vector being compared
	 * @return The dissimilarity
	 */
	public double getDissimilarity(Vector other) {
		return PrecisionGeometry.preciseAbs(getCrossProduct(other));
	}

	/**
	 * Calculates whether this Vector and the provided one are parallel to each
	 * other.
	 * 
	 * @param other
	 *            The Vector to test for parallelism
	 * @return true if this Vector and the provided one are parallel, false
	 *         otherwise.
	 */
	public boolean isParallelTo(Vector other) {
		return getDissimilarity(other) == 0;
	}

	/**
	 * Calculates the dot product of this Vector with another.
	 * 
	 * @param other
	 *            the Vector used to calculate the dot product
	 * @return The dot product
	 */
	public double getDotProduct(Vector other) {
		return PrecisionGeometry.preciseAdd(
				PrecisionGeometry.preciseMultiply(x, other.x),
				PrecisionGeometry.preciseMultiply(y, other.y));
	}

	/**
	 * Calculates the cross product of this Vector with another.
	 * 
	 * @param other
	 *            the Vector used to calculate the cross product
	 * @return The cross product.
	 */
	public double getCrossProduct(Vector other) {
		return PrecisionGeometry.preciseSubtract(
				PrecisionGeometry.preciseMultiply(x, other.y),
				PrecisionGeometry.preciseMultiply(y, other.x));
	}

	/**
	 * Creates a new Vector which is the sum of this Vector with another.
	 * 
	 * @param other
	 *            Vector to be added to this Vector
	 * @return a new Vector representing the sum
	 */
	public Vector getAdded(Vector other) {
		return new Vector(PrecisionGeometry.preciseAdd(x, other.x),
				PrecisionGeometry.preciseAdd(y, other.y));
	}

	/**
	 * Creates a new Vector which is the difference of this Vector with the
	 * provided Vector.
	 * 
	 * @param other
	 *            Vector to be subtracted from this Vector
	 * @return a new Vector representing the difference.
	 */
	public Vector getSubtracted(Vector other) {
		return new Vector(PrecisionGeometry.preciseSubtract(x, other.x),
				PrecisionGeometry.preciseSubtract(y, other.y));
	}

	/**
	 * Returns the angle (in degrees) between this Vector and the provided
	 * Vector.
	 * 
	 * @param other
	 *            Vector to calculate the angle.
	 * @return the angle between the two Vectors in degrees.
	 */
	public double getAngle(Vector other) {
		double cosAlpha = PrecisionGeometry.preciseDivide(
				getDotProduct(other),
				(PrecisionGeometry.preciseMultiply(getLength(),
						other.getLength())));
		return Math.toDegrees(Math.acos(cosAlpha));
	}

	/**
	 * Creates a new Vector which represents the average of this Vector with
	 * another.
	 * 
	 * @param other
	 *            Vector to calculate the average.
	 * @return a new Vector
	 */
	public Vector getAveraged(Vector other) {
		return new Vector(PrecisionGeometry.preciseDivide(
				PrecisionGeometry.preciseAdd(x, other.x), 2),
				PrecisionGeometry.preciseDivide(
						PrecisionGeometry.preciseAdd(y, other.y), 2));
	}

	/**
	 * Creates a new Vector which represents this Vector multiplied by the
	 * provided scalar factor.
	 * 
	 * @param factor
	 *            Value providing the amount to scale.
	 * @return a new Vector
	 */
	public Vector getMultiplied(double factor) {
		return new Vector(PrecisionGeometry.preciseMultiply(x, factor),
				PrecisionGeometry.preciseMultiply(y, factor));
	}

	/**
	 * Creates a new Vector which represents this Vector divided by the provided
	 * scalar factor.
	 * 
	 * @param factor
	 *            Value providing the amount to scale.
	 * @return a new Vector
	 */
	public Vector getDivided(double factor) {
		return new Vector(PrecisionGeometry.preciseDivide(x, factor),
				PrecisionGeometry.preciseDivide(y, factor));
	}

	/**
	 * Returns the orthogonal complement of this Vector, which is defined to be
	 * (-y, x).
	 * 
	 * @return the orthogonal complement of this Vector
	 */
	public Vector getOrthogonalComplement() {
		return new Vector(PrecisionGeometry.preciseNegate(y), x);
	}

	/**
	 * Returns the length of this Vector.
	 * 
	 * @return Length of this Vector
	 */
	public double getLength() {
		return Math.sqrt(getDotProduct(this));
	}

	/**
	 * Calculates the similarity of this Vector with another. Similarity is
	 * defined as the absolute value of the dotProduct(). Orthogonal vectors
	 * return a value of 0.
	 * 
	 * @param other
	 *            Vector being tested for similarity
	 * @return the Similarity
	 * @see #getDissimilarity(Vector)
	 */
	public double getSimilarity(Vector other) {
		return PrecisionGeometry.preciseAbs(getDotProduct(other));
	}

	/**
	 * Calculates whether this Vector and the provided one are orthogonal to
	 * each other.
	 * 
	 * @param other
	 *            Vector being tested for orthogonality
	 * @return true, if this Vector and the provide one are orthogonal, false
	 *         otherwise
	 */
	public boolean isOrthogonalTo(Vector other) {
		return getSimilarity(other) == 0;
	}

	/**
	 * Checks whether this vector has a horizontal component.
	 * 
	 * @return true if x != 0, false otherwise.
	 */
	public boolean isHorizontal() {
		return x != 0;
	}

	/**
	 * Checks whether this vector has a vertical component.
	 * 
	 * @return true if y != 0, false otherwise.
	 */
	public boolean isVertical() {
		return y != 0;
	}

	/**
	 * Checks whether this vector equals (0,0);
	 * 
	 * @return true if x == 0 and y == 0.
	 */
	public boolean isNull() {
		return equals(NULL);
	}

	/**
	 * Returns a point representation of this Vector.
	 * 
	 * @return a PrecisionPoint representation
	 */
	public PrecisionPoint toPoint() {
		return new PrecisionPoint(x, y);
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return "(" + x + "," + y + ")";//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
	}

	/**
	 * @see java.lang.Object#equals(Object)
	 */
	public boolean equals(Object obj) {
		if (obj == this)
			return true;
		if (obj instanceof Vector) {
			Vector r = (Vector) obj;
			return x == r.x && y == r.y;
		}
		return false;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode() {
		return (int) x + (int) y;
	}

}
