/*
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.*;

/**
 * Represents a point in space defined by a latitude, longitude and distance from the origin.
 * <p/>
 * Instances of <code>PolarPoint</code> are immutable.
 *
 * @author Tom Gaskins
 * @version $Id: PolarPoint.java 1754 2007-05-06 23:19:22Z tgaskins $
 */
public class PolarPoint
{
    public static final PolarPoint ZERO = new PolarPoint(Angle.ZERO, Angle.ZERO, 0d);
    
    private final Angle latitude;
    private final Angle longitude;
    private final double radius;

    /**
     * Obtains a <code>PolarPoint</code> from radians and a radius.
     *
     * @param latitude  the latitude in radians
     * @param longitude the longitude in radians
     * @param radius    the distance form the center
     * @return a new <code>PolarPoint</code>
     */
    public static PolarPoint fromRadians(double latitude, double longitude, double radius)
    {
        return new PolarPoint(Angle.fromRadians(latitude), Angle.fromRadians(longitude), radius);
    }

    /**
     * Obtains a <code>PolarPoint</code> from degrees and a radius.
     *
     * @param latitude  the latitude in degrees
     * @param longitude the longitude in degrees
     * @param radius    the distance form the center
     * @return a new <code>PolarPoint</code>
     */
    public static PolarPoint fromDegrees(double latitude, double longitude, double radius)
    {
        return new PolarPoint(Angle.fromDegrees(latitude), Angle.fromDegrees(longitude), radius);
    }

    /**
     * Obtains a <code>PolarPoint</code> from a cartesian point.
     *
     * @param cartesianPoint the point to convert
     * @return the cartesian point expressed as a polar point
     * @throws IllegalArgumentException if <code>cartesianPoint</code> is null
     */
    public static PolarPoint fromCartesian(Point cartesianPoint)
    {
        if (cartesianPoint == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.PointIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        return PolarPoint.fromCartesian(cartesianPoint.x(), cartesianPoint.y(), cartesianPoint.z());
    }

    /**
     * Obtains a <code>PolarPoint</code> from cartesian coordinates.
     *
     * @param x the x coordinate of the cartesian point
     * @param y the y coordinate of the cartesian point
     * @param z the z coordinate of the cartesian point
     * @return a polar point located at (x,y,z) in cartesian space
     */
    public static PolarPoint fromCartesian(double x, double y, double z)
    {
        double radius = Math.sqrt(x * x + y * y + z * z);
        double latRads = Math.atan2(y, Math.sqrt(x * x + z * z));
        double lonRads = Math.atan2(x, z);
        return PolarPoint.fromRadians(latRads, lonRads, radius);
    }

    /**
     * Obtains a <code>PolarPoint</code> from two <code>angles</code> and a radius.
     *
     * @param latitude  the latitude
     * @param longitude the longitude
     * @param radius    the distance from the center
     * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
     */
    public PolarPoint(Angle latitude, Angle longitude, double radius)
    {
        if (latitude == null || longitude == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        this.latitude = latitude;
        this.longitude = longitude;
        this.radius = radius;
    }

    /**
     * Obtains the latitude of this polar point
     *
     * @return this polar point's latitude
     */
    public final Angle getLatitude()
    {
        return this.latitude;
    }

    /**
     * Obtains the longitude of this polar point
     *
     * @return this polar point's longitude
     */
    public final Angle getLongitude()
    {
        return this.longitude;
    }

    /**
     * Obtains the radius of this polar point
     *
     * @return the distance from this polar point to its origin
     */
    public final double getRadius()
    {
        return radius;
    }

    /**
     * Obtains a cartesian point equivalent to this <code>PolarPoint</code>, except in cartesian space.
     *
     * @return this polar point in cartesian coordinates
     */
    public final Point toCartesian()
    {
        return toCartesian(this.latitude, this.longitude, this.radius);
    }

    /**
     * Obtains a cartesian point from a given latitude, longitude and distance from center. This method is equivalent
     * to, but may perform faster than <code>Point p = new PolarPoint(latitude, longitude, radius).toCartesian()</code>
     *
     * @param latitude  the latitude
     * @param longitude the longitude
     * @param radius    the distance from the origin
     * @return a cartesian point from two angles and a radius
     * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
     */
    public static Point toCartesian(Angle latitude, Angle longitude, double radius)
    {
        if (latitude == null || longitude == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        double x = radius * longitude.sin() * latitude.cos();
        double y = radius * latitude.sin();
        double z = radius * longitude.cos() * latitude.cos();
        return new Point(x, y, z);
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final gov.nasa.worldwind.geom.PolarPoint that = (gov.nasa.worldwind.geom.PolarPoint) o;

        if (Double.compare(that.radius, radius) != 0)
            return false;
        if (!latitude.equals(that.latitude))
            return false;
        //noinspection RedundantIfStatement
        if (!longitude.equals(that.longitude))
            return false;

        return true;
    }

    @Override
    public int hashCode()
    {
        int result;
        long temp;
        result = latitude.hashCode();
        result = 29 * result + longitude.hashCode();
        temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
        result = 29 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }

    @Override
    public String toString()
    {
        return "(lat: " + this.latitude.toString() + ", lon: " + this.longitude.toString() + ", r: " + this.radius
            + ")";
    }
}
