/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.geom;

import gov.nasa.worldwind.*;

/**
 * <code>Point</code> represents an homogeneous cartesian point in 3 dimensional space.
 * <p/>
 * Instances of <code>Point</code> are immutable. </p>
 *
 * @author Tom Gaskins
 * @version $Id: Point.java 1750 2007-05-06 19:53:06Z tgaskins $
 */
public class Point implements gov.nasa.worldwind.Cacheable
{
    private final double x;
    private final double y;
    private final double z;
    private final double w;

    /**
     * Value of <code>ZERO</code> is (0,0,0)
     */
    public static final Point ZERO = new Point(0, 0, 0);
    /**
     * Value of <code>UNIT_X</code> is (1,0,0)
     */
    public static final Point UNIT_X = new Point(1, 0, 0);
    /**
     * Value of <code>UNIT_Y</code> is (0,1,0)
     */
    public static final Point UNIT_Y = new Point(0, 1, 0);
    /**
     * Value of <code>UNIT_Z</code> is (0,0,1)
     */
    public static final Point UNIT_Z = new Point(0, 0, 1);

    /**
     * Constructs a new <code>Point</code> from four parameters.
     *
     * @param x the x position of the <code>Point</code>
     * @param y the y position of the <code>Point</code>
     * @param z the z position of the <code>Point</code>
     * @param w the w position of the <code>Point</code>
     */
    public Point(double x, double y, double z, double w)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }

    /**
     * Constructs a new <code>Point</code> from three parameters. The <code>w</code> field is set to 1.
     *
     * @param x the x position of the <code>Point</code>
     * @param y the y position of the <code>Point</code>
     * @param z the z position of the <code>Point</code>
     */
    public Point(double x, double y, double z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = 1;
    }

    /**
     * Returns the w element of this Point. This method differs from <code>w()</code> in that subclasses may override
     * it.
     *
     * @return the w element of this <code>Point</code>
     * @see Point#w()
     */
    public double getW()
    {
        return w;
    }

    /**
     * Returns the x element of this Point. This method differs from <code>x()</code> in that subclasses may override
     * it.
     *
     * @return the x element of this <code>Point</code>
     * @see Point#x()
     */
    public double getX()
    {
        return x;
    }

    /**
     * Returns the y element of this Point. This method differs from <code>y()</code> in that subclasses may override
     * it.
     *
     * @return the y element of this <code>Point</code>
     * @see Point#y()
     */
    public double getY()
    {
        return y;
    }

    /**
     * Returns the y element of this Point. This method differs from <code>y()</code> in that subclasses may override
     * it.
     *
     * @return the y element of this <code>Point</code>
     * @see Point#z()
     */
    public double getZ()
    {
        return z;
    }

    /**
     * Returns the x element of this <code>Point</code>.
     *
     * @return the x element of this <code>Point</code>
     */
    public final double x()
    {
        return this.x;
    }

    /**
     * Returns the y element of this <code>Point</code>.
     *
     * @return the y element of this <code>Point</code>
     */
    public final double y()
    {
        return this.y;
    }

    /**
     * Returns the z element of this <code>Point</code>.
     *
     * @return the z element of this <code>Point</code>
     */
    public final double z()
    {
        return this.z;
    }

    /**
     * Returns the w element of this <code>Point</code>.
     *
     * @return the w element of this <code>Point</code>
     */
    public final double w()
    {
        return this.w;
    }

    /**
     * Calculates the sum of these two <code>Point</code>s. The resulting <code>Point</code> has x value equivalent to
     * <code>this.x + p.x</code>, the results for y,z and w are calculated in the same way.
     *
     * @param p the <code>Point</code> to be added to this <code>Point</code>
     * @return a <code>Point</code> resulting from the algebraic operation <code>this + p</code>
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final Point add(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return new Point(this.x + p.x, this.y + p.y, this.z + p.z, 1);
    }

    /**
     * Calculates the difference between these two <code>Point</code>s. The resulting <code>Point</code> is equivalent
     * to <code>this.x - p.x</code>, the results for y, z and w are calculated in the same way.
     *
     * @param p the <code>Point</code> to subtract from this <code>Point</code>
     * @return a <code>Point</code> resulting from the algebraic operation<code>this - p</code>
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final Point subtract(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return new Point(this.x - p.x, this.y - p.y, this.z - p.z, 1);
    }

    /**
     * Multiplies this <code>Point</code> by a scalar quantity. This method simply returns a new <code>Point</code>
     * whose values each equal the old <code>Point</code>'s corresponding value multiplied by this scalar.
     *
     * @param s the scalar to be multiplied by
     * @return a <code>Point</code> resulting from the scalar multiplication of <code>this</code> and <code>s</code>
     */
    public final Point multiply(double s)
    {
        return new Point(this.x * s, this.y * s, this.z * s, 1);
    }

    public final Point scale(double sx, double sy, double sz)
    {
        return new Point(this.x * sx, this.y * sy, this.z * sz, this.w);
    }

    public final Point normalize()
    {
        double s = 1d / this.length();
        return this.scale(s, s, s);
    }

    /**
     * Performs a dot product of the x, y and z coorinates of <code>this</code> and <code>p</code>.
     *
     * @param p the <code>Point</code> to perform a dot product with
     * @return the scalar product of <code>this</code> and <code>p</code>
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final double dot(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return this.x * p.x + this.y * p.y + this.z * p.z;
    }

    /**
     * Performs a dot product of the x, y and z coorinates of <code>this</code> with itself. This method is equivalent
     * to <code>this.dot(this)</code>.
     * <p/>
     * <p/>
     * A useful characteristic of this method is that the resulting value is the square of this <code>Point</code>'s
     * distance from <code>ZERO</code>. Finding the square of the distance from the origin in this manner is preferred
     * over finding the square by first finding the length and then squaring it because this is faster and less prone to
     * loss of precision. </p>
     *
     * @return this <code>Point</code> dotted with itself
     * @see Point#ZERO
     */
    public final double selfDot()
    {
        return this.x * this.x + this.y * this.y + this.z * this.z;
    }

    /**
     * Performs a dot product of all four components of <code>this</code> and <code>p</code>.
     *
     * @param p the <code>Point</code> to perform a dot product with
     * @return the scalar product of <code>this</code> and <code>p</code>
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final double dot4(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return this.x * p.x + this.y * p.y + this.z * p.z + this.w * this.w;
    }

    /**
     * Calculates the distance between this <code>Point</code> and the origin.
     *
     * @return the distance between this <code>Point</code> and <code>ZERO</code>
     * @see Point#ZERO
     */
    public final double length()
    {
        return Math.sqrt(this.selfDot());
    }

    /**
     * Calculates the unsigned distance between this <code>Point</code> and <code>p</code>.
     *
     * @param p the <code>Point</code> to find the distance from
     * @return the distance between these two <code>Point</code>s
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final double distanceTo(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        double dx = this.x - p.x;
        double dy = this.y - p.y;
        double dz = this.z - p.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     * Calculates the squared unsigned distance between this <code>Point</code> and <code>p</code>. This method is
     * useful when actual distances are not required, but some measure is needed for comparison purposes. It avoids the
     * square root required for computing actual distance.
     *
     * @param p the <code>Point</code> to find the square distance from
     * @return the square of the distance between these two <code>Point</code>s
     * @throws IllegalArgumentException if <code>p</code> is null
     */
    public final double distanceToSquared(Point p)
    {
        if (p == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }
        double dx = this.x - p.x;
        double dy = this.y - p.y;
        double dz = this.z - p.z;
        return (dx * dx + dy * dy + dz * dz);
    }

    /**
     * Determines the midpoint of two <code>Point</code>s.
     *
     * @param p1 the first <code>Point</code>
     * @param p2 the second <code>Point</code>
     * @return the midpoint of these two <code>Point</code>s
     * @throws IllegalArgumentException if either <code>p1</code> or <code>p2</code> is null
     */
    public static Point midPoint(Point p1, Point p2)
    {
        if (p1 == null || p2 == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return new Point(0.5 * (p1.x + p2.x), 0.5 * (p1.y + p2.y), 0.5 * (p1.z + p2.z));
    }

    /**
     * Scales a <code>Point</code> along a vector. The resulting <code>Point</code> is affected by both the scale factor
     * and the size of the vector direction. For example, a vector (2,2,2) and a vector (1,1,1) would produce a
     * different result, if all other variables remain constant. For this reason, programmers may wish to normalize
     * <code>direction</code> before calling this function.
     *
     * @param scale     the factor to be scaled by
     * @param direction the direction of scaling
     * @param origin    the original <code>Point</code>
     * @return <code>origin</code> scaled by <code>scale</code> in the direction specified
     * @throws IllegalArgumentException if <code>direction</code> or <code>origin</code> is null
     */
    public static Point fromOriginAndDirection(double scale, Point direction, Point origin)
    {
        if (direction == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.DirectionIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (origin == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.OriginIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        double x = scale * direction.x() + origin.x;
        double y = scale * direction.y() + origin.y;
        double z = scale * direction.z() + origin.z;

        return new Point(x, y, z);
    }

    /**
     * Calculate the extrema of a given array of <code>Point</code>s. The resulting array is always of length 2, with
     * the first element containing the minimum extremum, and the second containing the maximum. The minimum extremum is
     * composed by taking the smallest x, y and z values from all the <code>Point</code>s in the array. These values are
     * not necessarily taken from the same <code>Point</code>. The maximum extrema is composed in the same fashion.
     *
     * @param points any array of <code>Point</code>s
     * @return a array with length of 2, comprising the most extreme values in the given array
     * @throws IllegalArgumentException if <code>points</code> is null
     */
    public static Point[] composeExtrema(Point points[])
    {
        if (points == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointsArrayIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (points.length == 0)
            return new Point[] {Point.ZERO, Point.ZERO};

        double xmin = points[0].x;
        double ymin = points[0].y;
        double zmin = points[0].z;
        double xmax = xmin;
        double ymax = ymin;
        double zmax = zmin;

        for (int i = 1; i < points.length; i++)
        {
            double x = points[i].x;
            if (x > xmax)
            {
                xmax = x;
            }
            else if (x < xmin)
            {
                xmin = x;
            }

            double y = points[i].y;
            if (y > ymax)
            {
                ymax = y;
            }
            else if (y < ymin)
            {
                ymin = y;
            }

            double z = points[i].z;
            if (z > zmax)
            {
                zmax = z;
            }
            else if (z < zmin)
            {
                zmin = z;
            }
        }

        return new Point[] {new Point(xmin, ymin, zmin), new Point(xmax, ymax, zmax)};
    }

    /**
     * Determines the cross product of these two <code>Point</code>s. This is post multiplied by that.
     *
     * @param that the second <code>Point</code>
     * @return the cross product of two <code>Point</code>s
     * @throws IllegalArgumentException if <code>that</code> is null
     */
    public Point cross(Point that)
    {
        if (that == null)
        {
            String msg = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, msg);
            throw new IllegalArgumentException(msg);
        }
        return new Point(this.y * that.z - this.z * that.y, this.z * that.x - this.x * that.z,
            this.x * that.y - this.y * that.x);
    }

    /**
     * Compares this <code>Point</code> to <code>o</code> for equality.
     * <p/>
     * This method makes comparisons on private fields; overriding implementations should include a call to
     * <code>super.equals()</code>.
     *
     * @param o the <code>Object</code> to be compared to for equality.
     * @return true if the contents are equal, false otherwise
     */
    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final gov.nasa.worldwind.geom.Point point = (gov.nasa.worldwind.geom.Point) o;

        if (Double.compare(point.w, w) != 0)
            return false;
        if (Double.compare(point.x, x) != 0)
            return false;
        if (Double.compare(point.y, y) != 0)
            return false;
        //noinspection RedundantIfStatement
        if (Double.compare(point.z, z) != 0)
            return false;

        return true;
    }

    /**
     * Generates an integer that is always the same for identical objects, but usually different for different objects.
     * This method overrides the one in <code>Object</code>.
     * <p/>
     * This method makes comparisons on private fields; overriding implementations should include a call to
     * <code>super.hashCode()</code>.
     *
     * @return the hashCode for this <code>Point</code>.
     */
    @Override
    public int hashCode()
    {
        int result;
        long temp;
        temp = x != +0.0d ? Double.doubleToLongBits(x) : 0L;
        result = (int) (temp ^ (temp >>> 32));
        temp = y != +0.0d ? Double.doubleToLongBits(y) : 0L;
        result = 29 * result + (int) (temp ^ (temp >>> 32));
        temp = z != +0.0d ? Double.doubleToLongBits(z) : 0L;
        result = 29 * result + (int) (temp ^ (temp >>> 32));
        temp = w != +0.0d ? Double.doubleToLongBits(w) : 0L;
        result = 29 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    /**
     * Generates a string representation of this object. The returned string has the format "(x, y, z, w)" where x, y, z
     * and w correspond to their respective values in this <code>Point</code>.
     *
     * @return a string representation of this <code>Point</code>
     */
    @Override
    public final String toString()
    {
        return "(" + Double.toString(this.x) + ", " + Double.toString(this.y) + ", " + Double.toString(this.z) + ", "
            + Double.toString(this.w) + ")";
    }

    /**
     * Obtains the amount of memory this <code>Point</code> consumes.
     *
     * @return the memory footprint of this <code>Point</code> in bytes.
     */
    public final long getSizeInBytes()
    {
        // we could consider using a constant value here
        return Double.SIZE << 2;
    }
}
