{-# LANGUAGE UnicodeSyntax, ForeignFunctionInterface #-}
{-# CFILES Numeric/Geometric/Predicates/ESSA/ESSAPrimitives.c #-}

{- | Hardware based, exact computation using the ESSA algorithm in double precision (1) 

     * We're using Float inputs on Double precision ESSA at the moment. Hopefully later we can add support for Double inputs on Quadruple ESSA

     * Line intersection is based on the algorithm presented in (4)

     * ccw and incircle based on (5)

     * See (2) and (3) for more information on the splitDouble operation

     * We assume realToFrac is broken and that CFloat == Float and CDouble == Double

     (1) Helmut Ratschek, Jon Rokne. \"Exact computation of the sign of a finite sum\". Applied Mathematics and Computation, Volume 99, Issue 2-3, Pages: 99-127, ISSN:0096-3003, 1999.   

     (2) Siegfried M. Rump. \"High precision evaluation of nonlinear functions\" 

     (3) T.J. Dekker. \"A Floating-Point Technique for Extending the Available Precision\". Numerische Mathematik, 18:224-242, 1971.

     (4) Marina Gavrilova, Jon Rokne. \"Reliable line segment intersection testing\"

     (5) Marina Gavrilova, Helmut Ratschek and Jon Rokne. \"Exact computation of Voronoi diagram and Delaunay triangulation\" 
-}

module Numeric.Geometric.Predicates.ESSA (cinttESSA, intersectESSA_SS2D, ccwESSA, incircleESSA, essa, splitDouble) where
import Numeric.Geometric.Primitives

import Data.Foldable (toList,Foldable)
import Control.Applicative
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Storable
import Foreign.Marshal.Array
import Foreign.C.Types
import System.IO.Unsafe


foreign import ccall unsafe ccw_essa  Float  Float  Float  Float  Float  Float  IO CInt 
foreign import ccall unsafe incircle_essa  Float  Float  Float  Float  Float  Float  Float  Float  IO CInt 
foreign import ccall unsafe intersect2D_essa  Float  Float  Float  Float  Float  Float  Float  Float  Ptr CInt  Ptr CInt  IO CInt 
foreign import ccall unsafe cintt_essa  Float  Float  Float  IO CInt 

foreign import ccall unsafe split_double  Double  Ptr Double  Ptr Double  IO () 
foreign import ccall unsafe essa_double  Ptr Double  CSize  IO CInt 

-- | Test if p3 is within the closed interval specified by [p1,p2]

cinttESSA  Float  Float  Float  Bool
cinttESSA lo hi p = (unsafePerformIO $ cintt_essa (cast lo) (cast hi) (cast p)) /= 0

-- | Intersect two line segments

intersectESSA_SS2D  LineSegment (Vector2 Float)  LineSegment (Vector2 Float)  LineIntersection 
intersectESSA_SS2D (a,b) (c,d) = unsafePerformIO $ 
         alloca (\ip1p  
         alloca (\ip2p  do
                         x  intersect2D_essa xi yi xj yj xk yk xl yl ip1p ip2p
                         case x of
                           0  return NINP
                           1  return Coincident
                           2  return Parallel
                           3  do
                               ip1  ip <$> peek ip1p
                               ip2  ip <$> peek ip2p
                               return (Intersecting (ip1,ip2))

                           _  error "intersectESSA_SS2D: unexpected result from FFI"
                ))


    where 
      ip 0 = Endpoint0
      ip 1 = Endpoint1
      ip 2 = Between
      ip _ = error "intersectESSA_SS2D: unexpected intersection result from FFI"

      (xi,yi) = castVector a 
      (xj,yj) = castVector b
      (xk,yk) = castVector c
      (xl,yl) = castVector d      



{- | Counter-clockwise orientation test. Classifies p3 in relation to the line formed by p1 and p2. 

     Result: LT=Right, GT=Left, EQ=Coincident 
-}

ccwESSA  Vector2 Float  Vector2 Float  Vector2 Float  Ordering
ccwESSA p1 p2 p3 = compare (unsafePerformIO $ ccw_essa x1 y1 x2 y2 x3 y3) 0
    where
      (x1,y1) = castVector p1  
      (x2,y2) = castVector p2
      (x3,y3) = castVector p3



{- | Test the relation of a point to the circle formed by (p1..p3). (p1..p3) must be in counterclockwise order. 

     Result: GT=inside, EQ=border, LT=outside.

     Note: this is the sum of 192 multiplications.
-}

incircleESSA  (Vector2 Float, Vector2 Float, Vector2 Float)  Vector2 Float  Ordering
incircleESSA (a,b,c) d = compare (unsafePerformIO (incircle_essa xi yi xj yj xk yk xl yl)) 0

    where 
      (xi,yi) = castVector a 
      (xj,yj) = castVector b
      (xk,yk) = castVector c
      (xl,yl) = castVector d


{- | Compute the exact sign of the sum of the input sequence.   
     It is the caller's responsibility to ensure that the inputs have not suffered a loss of precision.
-}

essa  (Functor t, Foldable t) => t Double  Ordering
essa = doubleESSA . fmap realToFrac -- maybe support 128bit quad later



{- | Split a 53 bit double into two 26 bit halves so that: @ let (lo,hi) = splitDouble x in x == lo + hi @

     The trick is that the sign is used as the additional bit.

     Note that the multiplication of two 26-bit floating point numbers is exact in double precision.

     If you're new to this function you may want to read paper (5), as using this function properly may be trickier than it seems.
-}

splitDouble  Double  (Double,Double)
splitDouble a = unsafePerformIO (alloca (\xp  
                                 alloca (\yp  do
                                               split_double a xp yp
                                               x  peek xp
                                               y  peek yp
                                               return (x,y))))




{--

foreign import ccall unsafe slope_essa ∷ Double → Double → Double → Double → IO CInt 

slopeESSA ∷ LineSegment (Vector2 Float) → Slope
slopeESSA (a,b) = case unsafePerformIO $ slope_essa x1 y1 x2 y2 of
                                0 → ZeroSlope
                                1 → UndefinedSlope
                                2 → NegativeSlope
                                3 → PositiveSlope

    where
      (x1,y1) = castVector a
      (x2,y2) = castVector b

--}

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

-- At the time realToFrac couldn't be trusted to do the right thing 
cast  Float  Float
cast = id

castVector  Vector2 Float  Vector2 Float
castVector = id

doubleESSA  (Functor a, Foldable a) => a Double  Ordering
doubleESSA xs = compare v 0

    where
      v = unsafePerformIO $ withArrayLen (toList xs) f
      f = (\i p  essa_double p (fromIntegral i))