/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.lucene.spatial.geometry;


/**
 * Abstract base lat-lng class which can manipulate fixed point or floating
 * point based coordinates. Instances are immutable.
 * 
 * @see FloatLatLng
 *
 * <p><font color="red"><b>NOTE:</b> This API is still in
 * flux and might change in incompatible ways in the next
 * release.</font>
 */
@Deprecated
public abstract class LatLng {

  public abstract boolean isNormalized();

  public abstract boolean isFixedPoint();

  public abstract LatLng normalize();

  public abstract int getFixedLat();

  public abstract int getFixedLng();

  public abstract double getLat();

  public abstract double getLng();

  public abstract LatLng copy();

  public abstract FixedLatLng toFixed();

  public abstract FloatLatLng toFloat();
  
  /**
   * Convert the lat/lng into the cartesian coordinate plane such that all
   * world coordinates are represented in the first quadrant.
   * The x dimension corresponds to latitude and y corresponds to longitude.
   * The translation starts with the normalized latlng and adds 180 to the latitude and 
   * 90 to the longitude (subject to fixed point scaling).
   */
  public CartesianPoint toCartesian() {
    LatLng ll=normalize();
    
    int lat=ll.getFixedLat();
    int lng=ll.getFixedLng();
    
    return new CartesianPoint(
        lng+180*FixedLatLng.SCALE_FACTOR_INT,
        lat+90*FixedLatLng.SCALE_FACTOR_INT
    );
  }
  
  /**
   * The inverse of toCartesian().  Always returns a FixedLatLng.
   * @param pt
   */
  public static LatLng fromCartesian(CartesianPoint pt) {
    int lat=pt.getY() - 90 * FixedLatLng.SCALE_FACTOR_INT;
    int lng=pt.getX() - 180 * FixedLatLng.SCALE_FACTOR_INT;
    
    return new FixedLatLng(lat, lng);
  }
  
  /**
   * Calculates the distance between two lat/lng's in miles.
   * Imported from mq java client.
   * 
   * @param ll2
   *            Second lat,lng position to calculate distance to.
   * 
   * @return Returns the distance in miles.
   */
  public double arcDistance(LatLng ll2) {
    return arcDistance(ll2, DistanceUnits.MILES);
  }

  /**
   * Calculates the distance between two lat/lng's in miles or meters.
   * Imported from mq java client.  Variable references changed to match.
   * 
   * @param ll2
   *            Second lat,lng position to calculate distance to.
   * @param lUnits
   *            Units to calculate distance, defaults to miles
   * 
   * @return Returns the distance in meters or miles.
   */
  public double arcDistance(LatLng ll2, DistanceUnits lUnits) {
    LatLng ll1 = normalize();
    ll2 = ll2.normalize();

    double lat1 = ll1.getLat(), lng1 = ll1.getLng();
    double lat2 = ll2.getLat(), lng2 = ll2.getLng();

    // Check for same position
    if (lat1 == lat2 && lng1 == lng2)
      return 0.0;

    // Get the m_dLongitude difference. Don't need to worry about
    // crossing 180 since cos(x) = cos(-x)
    double dLon = lng2 - lng1;

    double a = radians(90.0 - lat1);
    double c = radians(90.0 - lat2);
    double cosB = (Math.cos(a) * Math.cos(c))
        + (Math.sin(a) * Math.sin(c) * Math.cos(radians(dLon)));

    double radius = (lUnits == DistanceUnits.MILES) ? 3963.205/* MILERADIUSOFEARTH */
    : 6378.160187/* KMRADIUSOFEARTH */;

    // Find angle subtended (with some bounds checking) in radians and
    // multiply by earth radius to find the arc distance
    if (cosB < -1.0)
      return 3.14159265358979323846/* PI */* radius;
    else if (cosB >= 1.0)
      return 0;
    else
      return Math.acos(cosB) * radius;
  }

  private double radians(double a) {
    return a * 0.01745329251994;
  }

  @Override
  public String toString() {
    return "[" + getLat() + "," + getLng() + "]";
  }

  /**
   * Calculate the midpoint between this point an another.  Respects fixed vs floating point
   * @param other
   */
  public abstract LatLng calculateMidpoint(LatLng other);
  
  @Override
  public abstract int hashCode();

  @Override
  public abstract boolean equals(Object obj);
}
