{-# Language
    MultiParamTypeClasses
  , FlexibleInstances
  , UndecidableInstances
  #-}
module Data.Geometry.SetOperations( AreIntersectable(..)
                                  ) where

import Data.List
import Data.Geometry.Point
import Data.Geometry.Line
import Data.Geometry.Circle

import Debug.Trace

--------------------------------------------------------------------------------

-- | A class to represent that a pair of geometry objects (both parameterized
-- over a) can be intersected.
class AreIntersectable g h a where
    intersectionPoints :: g a -> h a -> [Point2' a]

-- | Intersection is symetrical
-- instance AreIntersectable g h a => AreIntersectable h g a where
--     intersectionPoints h g = intersectionPoints g h


-- instance AreIntersectable LineSegment2' LineSegment2' a where
--     intersectionPoints _ _ = []

instance (Ord a, Floating a) => AreIntersectable Circle2' LineSegment2' a where
    -- | The intersection points, ordered along the line segment
    intersectionPoints c l = filter (`onLineSegment` l) $ circleAndLine c l

instance (Ord a, Floating a) => AreIntersectable Circle2' Polyline2' a where
    -- | The intersection points, ordered along the polyline
    intersectionPoints c (Polyline2 ls) = concatMap (intersectionPoints c) ls

instance (Ord a, Floating a) => AreIntersectable Circle2' Line2' a where
    intersectionPoints c (Line2 l) = circleAndLine c l


-- | represents the intersection of a circle and an infinite line (as LineSegment )
circleAndLine :: (Ord a, Floating a) => Circle2' a -> LineSegment2' a -> [Point2' a]
circleAndLine (Circle2 p r) (LineSegment2 s t) = map (|+| p) $ pts discr
        where
          s'@(Point2 (sx,sy))  = s  |-| p   -- translate so the circle is centred at (0,0)
          t'                   = t  |-| p
          d@(Point2 (dx,dy))   = t' |-| s'  -- vector from s' to t'
          ----
          -- any point on the line segment is given as: q = s' + \lambda * d
          q lamb               = s' |+| (lamb |*| d)
          -- solving the equation (q_x)^2 + (q_y)^2 = r^2 then yields the equation
          -- L^2(dx^2 + dy^2) + L2(sx*dx + sy*dy) + sx^2 + sy^2 = 0
          -- where L = \lambda
          a                    = dx^2 + dy^2
          b                    = 2*(sx*dx + sy*dy)
          c                    = sx^2 + sy^2 - r^2
          discr                = b^2 - 4*a*c
          discr'               = sqrt discr
          lambda (|+-|)        = (-b |+-| discr') / (2*a)
          pts dscr
              | dscr < 0       = []                -- no intersections
              | dscr == 0      = [q $ lambda (+)]  -- line tangent
              | otherwise      = let lambdas = sort [lambda (-), lambda (+)] in
                                 map q lambdas     -- intersection