# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
 #  Geodesy representation conversion functions (c) Chris Veness 2002-2010
 #   - www.movable-type.co.uk/scripts/latlong.html
 #
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  

# Parses string representing degrees/minutes/seconds into numeric degrees
# 
# This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
# suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3ยบ 37' 09"W) 
# or fixed-width format without separators (eg 0033709W). Seconds and minutes may be omitted. 
# (Note minimal validation is done).
# 
# @param   [String|Number] Degrees or deg/min/sec in variety of formats
# @returns [Number] Degrees as decimal number
# @throws  ArgumentError

require 'sugar-high/numeric'

module GeoUnits
  autoload :Converter,      'geo_units/converter'
  autoload :DmsConverter,   'geo_units/dms_converter'
  autoload :NumericExt,     'geo_units/numeric_ext'

  def earth_radius_map
    {
    :miles      => 3963.1676, 
    :kilometers => 6378.135, 
    :meters     =>  6378135, 
    :feet       => 20925639.8 
    }
  end

  def earth_major_axis_radius_map 
    { 
    :miles      => 3963.19059, 
    :kilometers => 6378.137,
    :meters     => 6378137,
    :feet       => 20925646.36
    }
  end

  def earth_minor_axis_radius_map 
    { 
    :kilometers => 6356.7523142,
    :miles      => 3949.90276,
    :meters     => 6356752.3142,
    :feet       => 20855486.627
    }
  end
  
  def radians_per_degree
    Math::PI / 180.0
  end

  # Haversine Formula
  # Adapted from Geokit Gem
  # https://github.com/andre/geokit-gem.git
  # By: Andre Lewis
  def pi_div_rad 
    0.0174
  end

  def kms_per_mile 
    1.609
  end
  
  def meters_per_feet 
    3.2808399
  end

  def miles_per_latitude_degree 
    69.1
  end
  
  def kms_per_latitude_degree
    miles_per_latitude_degree * kms_per_mile
  end

  def latitude_degrees 
    earth_radius_map[:miles] / miles_per_latitude_degree
  end  

  def degrees_to_radians(degrees)   
    degrees.to_f * radians_per_degree
  end

  def units_sphere_multiplier(units)
    units = GeoUnits.key units
    earth_radius_map[units]
  end

  def units_per_latitude_degree(units)
    units = GeoUnits.key units
    GeoUnits.radian_multiplier[units]
  end

  def units_per_longitude_degree(lat, units)
    miles_per_longitude_degree = (latitude_degrees * Math.cos(lat * pi_div_rad)).abs 
    units = GeoUnits.key units
    case units
      when :feet
        miles_per_longitude_degree * kms_per_mile * 1000 * meters_per_feet
      when :meters
        miles_per_longitude_degree * kms_per_mile * 1000
      when :kms
        miles_per_longitude_degree * kms_per_mile
      when :miles
        miles_per_longitude_degree
    end
  end

  module ClassMethods
    def key unit = :km
      unit = unit.to_sym
      methods.grep(/_unit$/).each do |meth|
        return meth.to_s.chomp('_unit').to_sym if send(meth).include? unit
      end
      raise ArgumentError, "Unknown unit key: #{unit}"
    end

    def units
      [:feet, :meters, :kms, :miles, :radians]
    end

    def all_units
      [:miles, :mile, :kms, :km, :feet, :foot, :meter, :meters, :radians, :rad]
    end

    [:feet, :meters, :kms, :miles, :radians].each do |unit|
      class_eval %{
        def #{unit}_to unit, number = 0
          return 0 if number <= 0
          unit = key(unit)
          m = number / GeoUnits.meters_map[:#{unit}]
          m * meters_map[unit]
        end
      }
    end

    def earth_radius units
      units = GeoUnits.key units
      GeoUnits.earth_radius_map[units]
    end

    def radians_per_degree
      0.017453293  #  PI/180
    end    
        
    def radians_ratio units
      units = GeoUnits.key units
      radians_per_degree * earth_radius[units]
    end

    def precision
      {
        :feet => 0,
        :meters => 2,
        :kms => 4,
        :miles => 4,
        :radians => 4
      }
    end

    # from mongoid-geo, as suggested by niedhui :)
    def radian_multiplier
      {
        :feet     => 364491.8,
        :meters   => 111170,
        :kms      => 111.17,
        :miles    => 69.407,
        :radians  => 1
      }
    end

    def meters_multiplier
      {
        :feet     => 0.305,
        :meters   => 1,
        :kms      => 6371,
        :miles    => 3959,
        :radians  => 111170 
      }
    end

    def meters_map
      {
       :feet    => 3.2808399,
       :meters  => 1,
       :kms     => 0.001,
       :miles   => 0.00062137,
       :radians => 0.00000899
      }
    end

    protected

    def feet_unit
      [:ft, :feet, :foot]
    end

    def meters_unit
      [:m, :meter, :meters]
    end

    def kms_unit
      [:km, :kms, :kilometer, :kilometers]
    end

    def miles_unit
      [:mil, :mile, :miles]
    end

    def radians_unit
      [:rad, :radians]
    end          
  end
  
  extend ClassMethods
end  

require 'geo_units/core_ext'