/*
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 on the two-dimensional surface of a globe. Latitude is the degrees North and ranges between [-90,
 * 90], while longitude refers to degrees East, and ranges between (-180, 180].
 * <p/>
 * Instances of <code>LatLon</code> are immutable.
 *
 * @author Tom Gaskins
 * @version $Id: LatLon.java 1749 2007-05-06 19:48:14Z tgaskins $
 */
public class LatLon
{
    private final Angle latitude;
    private final Angle longitude;

    /**
     * Factor method for obtaining a new <code>LatLon</code> from two angles expressed in radians.
     *
     * @param latitude  in radians
     * @param longitude in radians
     * @return a new <code>LatLon</code> from the given angles, which are expressed as radians
     */
    public static LatLon fromRadians(double latitude, double longitude)
    {
        return new LatLon(Math.toDegrees(latitude), Math.toDegrees(longitude));
    }

    /**
     * Factory method for obtaining a new <code>LatLon</code> from two angles expressed in degrees.
     *
     * @param latitude  in degrees
     * @param longitude in degrees
     * @return a new <code>LatLon</code> from the given angles, which are expressed as degrees
     */
    public static LatLon fromDegrees(double latitude, double longitude)
    {
        return new LatLon(latitude, longitude);
    }

    private LatLon(double latitude, double longitude)
    {
        this.latitude = Angle.fromDegrees(latitude);
        this.longitude = Angle.fromDegrees(longitude);
    }

    /**
     * Contructs a new  <code>LatLon</code> from two angles. Neither angle may be null.
     *
     * @param latitude
     * @param longitude
     * @throws IllegalArgumentException if <code>latitude</code> or <code>longitude</code> is null
     */
    public LatLon(Angle latitude, Angle longitude)
    {
        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;
    }

    /**
     * Obtains the latitude of this <code>LatLon</code>.
     *
     * @return this <code>LatLon</code>'s latitude
     */
    public final Angle getLatitude()
    {
        return this.latitude;
    }

    /**
     * Obtains the longitude of this <code>LatLon</code>.
     *
     * @return this <code>LatLon</code>'s longitude
     */
    public final Angle getLongitude()
    {
        return this.longitude;
    }

    public static LatLon interpolate(double t, LatLon begin, LatLon end)
    {
        if (begin == null || end == null)
        {
            String message = WorldWind.retrieveErrMsg("nullValue.LatitudeOrLongitudeIsNull");
            WorldWind.logger().log(java.util.logging.Level.FINE, message);
            throw new IllegalArgumentException(message);
        }

        if (t < 0)
            return begin;
        else if (t > 1)
            return end;
        Quaternion beginQuat = Quaternion.EulerToQuaternion(begin.getLongitude().getRadians(),
            begin.getLatitude().getRadians(), 0);
        Quaternion endQuat = Quaternion.EulerToQuaternion(end.getLongitude().getRadians(),
            end.getLatitude().getRadians(), 0);
        Quaternion q = Quaternion.Slerp(beginQuat, endQuat, t);
        Point v = Quaternion.QuaternionToEuler(q);
        if (Double.isNaN(v.x()) || Double.isNaN(v.y()))
            return null;
        return LatLon.fromRadians(v.y(), v.x());
    }

    @Override
    public String toString()
    {
        return "(" + this.latitude.toString() + ", " + this.longitude.toString() + ")";
    }

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

        final gov.nasa.worldwind.geom.LatLon latLon = (gov.nasa.worldwind.geom.LatLon) o;

        if (!latitude.equals(latLon.latitude))
            return false;
        //noinspection RedundantIfStatement
        if (!longitude.equals(latLon.longitude))
            return false;

        return true;
    }

    @Override
    public int hashCode()
    {
        int result;
        result = latitude.hashCode();
        result = 29 * result + longitude.hashCode();
        return result;
    }
}
