module GeoCalc::Calc
  module Intersection
    def intersection brng1, p2, brng2
      GeoCalc::Calc::Intersection.intersection self, brng1, p2, brng2
    end

    # Returns the point of intersection of two paths defined by point and bearing
    # 
    #   see http:#williams.best.vwh.net/avform.htm#Intersection
    # 
    # @param   {LatLon} p1: First point
    # @param   {Number} brng1: Initial bearing from first point
    # @param   {LatLon} p2: Second point
    # @param   {Number} brng2: Initial bearing from second point
    # @returns {LatLon} Destination point (null if no unique intersection defined)

    def self.intersection p1, brng1, p2, brng2
      lat1 = p1.lat.to_rad
      lon1 = p1.lon.to_rad

      lat2 = p2.lat.to_rad
      lon2 = p2.lon.to_rad

      brng13 = brng1.to_rad
      brng23 = brng2.to_rad

      dlat = lat2-lat1
      dlon = lon2-lon1;

      dist12 = 2*Math.asin( Math.sqrt( Math.sin(dlat/2)*Math.sin(dlat/2) + Math.cos(lat1)*Math.cos(lat2)*Math.sin(dlon/2)*Math.sin(dlon/2) ) )
      return nil if dist12 == 0

      # initial/final bearings between points
      brng_a = begin
        Math.acos( ( Math.sin(lat2) - Math.sin(lat1)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat1) ) )
      rescue # protect against rounding
        0
      end

      brng_b = Math.acos( ( Math.sin(lat1) - Math.sin(lat2)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat2) ) )

      brng12, brng21 = if Math.sin(lon2-lon1) > 0
        [brng_a, 2*Math::PI - brng_b] 
      else
        [2*Math::PI - brng_a, brng_b]
      end

      alpha1 = (brng13 - brng12 + Math::PI) % (2*Math::PI) - Math::PI  # angle 2-1-3
      alpha2 = (brng21 - brng23 + Math::PI) % (2*Math::PI) - Math::PI  # angle 1-2-3

      return nil if (Math.sin(alpha1)==0 && Math.sin(alpha2)==0) # infinite intersections
      return nil if (Math.sin(alpha1)*Math.sin(alpha2) < 0)    # ambiguous intersection

      # alpha1 = Math.abs(alpha1);
      # alpha2 = Math.abs(alpha2);
      # ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation?

      alpha3 = Math.acos( -Math.cos(alpha1)*Math.cos(alpha2) + Math.sin(alpha1)*Math.sin(alpha2)*Math.cos(dist12) )

      dist13 = Math.atan2( Math.sin(dist12)*Math.sin(alpha1)*Math.sin(alpha2), Math.cos(alpha2)+Math.cos(alpha1)*Math.cos(alpha3) )

      lat3 = Math.asin( Math.sin(lat1)*Math.cos(dist13) + Math.cos(lat1)*Math.sin(dist13)*Math.cos(brng13) )

      dlon13 = Math.atan2( Math.sin(brng13)*Math.sin(dist13)*Math.cos(lat1), Math.cos(dist13)-Math.sin(lat1)*Math.sin(lat3) )

      lon3 = lon1 + dlon13;
      lon3 = (lon3 + Math::PI) % (2*Math::PI) - Math::PI  # normalise to -180..180ยบ

      GeoPoint.new lat3.to_deg, lon3.to_deg
    end    
  end
end