require 'sugar-high/arguments'
require 'geo_calc/calc'
require 'geo_calc/extensions'
require 'geo_calc/geo_point/shared'

 #  Sample usage:                                                                                 
 #    p1 = GeoPoint.new(51.5136, -0.0983)                                                      
 #    p2 = GeoPoint.new(51.4778, -0.0015)                                                      
 #    dist = p1.distance_to(p2)          # in km                                             
 #    brng = p1.bearing_to(p2)           # in degrees clockwise from north                   
 #    ... etc                                                                                     
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  

class GeoPoint
  include GeoCalc::Calc::All

  autoload :Shared,         'geo_calc/geo_point/shared'
  autoload :ClassMethods,   'geo_calc/geo_point/class_methods'
  autoload :CoreExtension,  'geo_calc/geo_point/core_extension'

  attr_reader   :lat, :lon  

  # Creates a point on the earth's surface at the supplied latitude / longitude
  # 
  # - Numeric latitude in numeric degrees
  # - Numeric longitude in numeric degrees  

  # Optional options
  # - :radius - earth radius in km
  # - :mode - coordinates mode, either :lng_lat or :lat_lng, otherwise uses global setting as per GeoPoint.coord_mode
  def initialize *args
    options = args.is_a?(GeoPoint) ? {} : args.last_option
    earth_radius_km = options[:radius]
    coord_mode = options[:mode]
    
    case args.size
    when 1
      create_from_one args
    when 2
      create_from_two *args
    else
      raise "GeoPoint must be initialized with either one or to arguments defining the (latitude, longitude) coordinate on the map"
    end
  end

  extend ClassMethods
  include Shared

  def coord_mode
    @coord_mode ||= GeoPoint.coord_mode
  end

  def earth_radius_km  
    @earth_radius_km ||= GeoPoint.earth_radius_km  # default
  end

  def lat= value 
    @lat = value.to_lat
  end

  def lon= value
    @lon = value.to_lng
  end

  (Symbol.lng_symbols - [:lon]).each do |sym|
    class_eval %{
      alias_method :#{sym}, :lon
      alias_method :#{sym}=, :lon=
    }
  end    
  alias_method :to_lng, :lng

  (Symbol.lat_symbols - [:lat]).each do |sym|
    class_eval %{
      alias_method :#{sym}, :lat
      alias_method :#{sym}=, :lat=
    }
  end
  alias_method :to_lat, :lat

  def [] key
    case key
    when Fixnum   
      raise ArgumentError, "Index must be 0 or 1" if !(0..1).cover?(key)
      to_a[key] 
    when String, Symbol
      send(key) if respond_to? key
    else
      raise ArgumentError, "Key must be a Fixnum (index) or a method name"  
    end    
  end

  alias_method :to_dms, :to_s

  def reverse_point!
    self.lat = lat * -1
    self.lng = lng * -1
    self    
  end

  def reverse_point
    self.dup.reverse_point!
  end

  def to_lat_lng
    [lat, lng]
  end

  def to_lng_lat
    [lng, lat]
  end

  def to_a
    send(:"to_#{coord_mode}")
  end
  
  protected

  include ::GeoCalc::NumericCheckExt

  def to_coords points
    points.send(:"to_#{coord_mode}")
  end
  
  def create_from_one args
    args = args.first
    create_from_two *to_coords(args)
  end
  
  def create_from_two lat, lon
    @lat    = lat.to_lat
    @lon    = lon.to_lng
  end  
end