{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances, DeriveTraversable, FunctionalDependencies #-}

-- | Basic 2 dimensional geometry functions.
module Geom2D (
  module Data.VectorSpace,
  module Data.Cross,
  module Geom2D ) where
import qualified Data.Vector.Generic.Mutable as M
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Unboxed as V
import Data.VectorSpace
import Data.Cross
import Control.Monad
import Numeric.FastMath

infixr 5 $*

data Point a = Point {
  pointX :: !a,
  pointY :: !a
  } deriving (Eq, Ord, Functor, Foldable, Traversable)

type DPoint = Point Double

instance Show a => Show (Point a) where
  show (Point x y) =
    "Point " ++ show x ++ " " ++ show y

-- | A transformation (x, y) -> (ax + by + c, dx + ey + d)
data Transform a = Transform {
  xformA :: !a,
  xformB :: !a,
  xformC :: !a,
  xformD :: !a,
  xformE :: !a,
  xformF :: !a }
  deriving (Eq, Show, Functor, Foldable, Traversable)

data Line a = Line (Point a) (Point a)
            deriving (Show, Eq, Functor, Foldable, Traversable)

data Polygon a = Polygon [Point a]
               deriving (Show, Eq, Functor, Foldable, Traversable)

class AffineTransform a b | a -> b where
  transform :: Transform b -> a -> a

instance Num a => AffineTransform (Transform a) a where
  {-# INLINE transform #-}
  transform (Transform a' b' c' d' e' f') (Transform a b c d e f) =
    Transform (a*a'+b'*d) (a'*b + b'*e) (a'*c+b'*f +c')
    (d'*a+e'*d) (d'*b+e'*e) (d'*c+e'*f+f')

instance Num a => AffineTransform (Point a) a where
  {-# INLINE transform #-}
  transform (Transform a b c d e f) (Point x y) =
    Point (a*x + b*y + c) (d*x + e*y + f)

instance Num a => AffineTransform (Polygon a) a where
  {-# INLINE transform #-}
  transform t (Polygon p) = Polygon $ map (transform t) p

newtype instance V.MVector s (Point a) = MV_Point (V.MVector s (a, a))
newtype instance V.Vector    (Point a) = V_Point  (V.Vector    (a, a))

instance V.Unbox a => V.Unbox (Point a)
instance V.Unbox a => M.MVector V.MVector (Point a) where
  {-# INLINE basicLength #-}
  {-# INLINE basicUnsafeSlice #-}
  {-# INLINE basicOverlaps #-}
  {-# INLINE basicUnsafeNew #-}
  {-# INLINE basicUnsafeReplicate #-}
  {-# INLINE basicUnsafeRead #-}
  {-# INLINE basicUnsafeWrite #-}
  {-# INLINE basicClear #-}
  {-# INLINE basicSet #-}
  {-# INLINE basicUnsafeCopy #-}
  {-# INLINE basicUnsafeGrow #-}
  basicInitialize (MV_Point v) = M.basicInitialize v
  basicLength (MV_Point v) = M.basicLength v
  basicUnsafeSlice i n (MV_Point v) = MV_Point $ M.basicUnsafeSlice i n v
  basicOverlaps (MV_Point v1) (MV_Point v2) = M.basicOverlaps v1 v2
  basicUnsafeNew n = MV_Point `liftM` M.basicUnsafeNew n
  basicUnsafeReplicate n (Point x y) = MV_Point `liftM` M.basicUnsafeReplicate n (x,y)
  basicUnsafeRead (MV_Point v) i = uncurry Point `liftM` M.basicUnsafeRead v i
  basicUnsafeWrite (MV_Point v) i (Point x y) = M.basicUnsafeWrite v i (x,y)
  basicClear (MV_Point v) = M.basicClear v
  basicSet (MV_Point v) (Point x y) = M.basicSet v (x,y)
  basicUnsafeCopy (MV_Point v1) (MV_Point v2) = M.basicUnsafeCopy v1 v2
  basicUnsafeGrow (MV_Point v) n = MV_Point `liftM` M.basicUnsafeGrow v n

instance V.Unbox a => G.Vector V.Vector (Point a) where
  {-# INLINE basicUnsafeFreeze #-}
  {-# INLINE basicUnsafeThaw #-}
  {-# INLINE basicLength #-}
  {-# INLINE basicUnsafeSlice #-}
  {-# INLINE basicUnsafeIndexM #-}
  {-# INLINE elemseq #-}
  basicUnsafeFreeze (MV_Point v) = V_Point `liftM` G.basicUnsafeFreeze v
  basicUnsafeThaw (V_Point v) = MV_Point `liftM` G.basicUnsafeThaw v
  basicLength (V_Point v) = G.basicLength v
  basicUnsafeSlice i n (V_Point v) = V_Point $ G.basicUnsafeSlice i n v
  basicUnsafeIndexM (V_Point v) i
                = uncurry Point `liftM` G.basicUnsafeIndexM v i
  basicUnsafeCopy (MV_Point mv) (V_Point v)
                = G.basicUnsafeCopy mv v
  elemseq _ (Point x y) z = G.elemseq (undefined :: V.Vector a) x
                       $ G.elemseq (undefined :: V.Vector a) y z

-- | Operator for applying a transformation.
($*) :: AffineTransform a b => Transform b -> a -> a
t $* p = transform t p
{-# INLINE ($*) #-}

-- | Calculate the inverse of a transformation.
inverse :: (Eq a, Fractional a) => Transform a -> Maybe (Transform a)
inverse (Transform a b c d e f) = case a*e - b*d of
  0 -> Nothing
  det -> Just $! Transform (a/det) (d/det) (-(a*c + d*f)/det) (b/det) (e/det)
         (-(b*c + e*f)/det)
{-# SPECIALIZE inverse :: Transform Double -> Maybe (Transform Double) #-}

-- | Return the parameters (a, b, c) for the normalised equation
-- of the line: @a*x + b*y + c = 0@.
lineEquation :: Floating t => Line t -> ( t, t, t )
lineEquation (Line (Point x1 y1) (Point x2 y2)) =
  a `seq` b `seq` c `seq` (a, b, c)
  where a = a' / d
        b = b' / d
        c = -(y1*b' + x1*a') / d
        a' = y1 - y2
        b' = x2 - x1
        d = sqrt(a'*a' + b'*b')
{-# SPECIALIZE lineEquation :: Line Double -> (Double, Double, Double) #-}

-- | Return the signed distance from a point to the line.  If the
-- distance is negative, the point lies to the right of the line
lineDistance :: Floating a => Line a -> Point a -> a
lineDistance (Line (Point x1 y1) (Point x2 y2)) =
  let dy = y1 - y2
      dx = x2 - x1
      d = sqrt(dx*dx + dy*dy)
  in dy `seq` dx `seq` d `seq`
     \(Point x y) -> (x-x1)*dy/d + (y-y1)*dx/d
{-# INLINE lineDistance #-}

-- | Return the point on the line closest to the given point.
closestPoint :: Fractional a => Line a -> Point a -> Point a
closestPoint (Line p1 p2) p3 = Point px py
  where
    (Point dx dy) = p2 ^-^ p1
    u = dy*pointY p3 + dx*pointX p3
    v = pointX p1*pointY p2 - pointX p2*pointY p1
    m = dx*dx + dy*dy
    px = (dx*u + dy*v) / m
    py = (dy*u - dx*v) / m
{-# specialize closestPoint :: Line Double -> Point Double -> Point Double #-}

-- | Calculate the intersection of two lines.  If the determinant is
-- less than tolerance (parallel or coincident lines), return Nothing.
lineIntersect :: (Ord a, Floating a) => Line a -> Line a -> a -> Maybe (Point a)
lineIntersect (Line p1 p2) (Line p3 p4) eps
  | abs det <= eps = Nothing
  | otherwise = Just $ (a*^d2 ^-^ b*^d1) ^/ det
  where
    d1 = p1 ^-^ p2
    d2 = p3 ^-^ p4
    det = vectorCross d1 d2
    a = vectorCross p1 p2
    b = vectorCross p3 p4
{-# SPECIALIZE lineIntersect :: Line Double -> Line Double -> Double -> Maybe DPoint #-}

-- | The lenght of the vector.
vectorMag :: Floating a => Point a -> a
vectorMag (Point x y) = sqrt(x*x + y*y)
{-# INLINE vectorMag #-}

-- | The lenght of the vector.
vectorMagSquare :: Floating a => Point a -> a
vectorMagSquare (Point x y) = x*x + y*y
{-# INLINE vectorMagSquare #-}


-- | The angle of the vector, in the range @(-'pi', 'pi']@.
vectorAngle :: RealFloat a => Point a -> a
vectorAngle (Point 0.0 0.0) = 0.0
vectorAngle (Point x y) = atan2 y x
{-# INLINE vectorAngle #-}

-- | The unitvector with the given angle.
dirVector :: Floating a => a -> Point a
dirVector angle = Point (cos angle) (sin angle)
{-# INLINE dirVector #-}

-- | The unit vector with the same direction.
normVector :: Floating a => Point a -> Point a
normVector p@(Point x y) = Point (x/l) (y/l)
  where l = vectorMag p
{-# INLINE normVector #-}

instance Num e => AdditiveGroup (Point e) where
  zeroV = Point 0 0
  {-# INLINE (^+^) #-}
  (Point x1 y1) ^+^ (Point x2 y2) = Point (x1+x2) (y1+y2)
  {-# INLINE negateV #-}
  negateV (Point a b) = Point (-a) (-b)
  {-# INLINE (^-^) #-}
  (Point x1 y1) ^-^ (Point x2 y2) = Point (x1-x2) (y1-y2)

instance (Num e) => VectorSpace (Point e) where
  type Scalar (Point e) = e
  s *^ (Point x y) = Point (s*x) (s*y)

instance (AdditiveGroup e, Num e) => InnerSpace (Point e) where
  (<.>) = (^.^)

instance (Floating e) => HasNormal (Point e) where
  normalVec = normVector

-- | Dot product of two vectors.
(^.^) :: Num a => Point a -> Point a -> a
(Point x1 y1) ^.^ (Point x2 y2) = x1*x2 + y1*y2
{-# INLINE (^.^) #-}

-- | Cross product of two vectors.
vectorCross :: Num a => Point a -> Point a -> a
vectorCross (Point x1 y1) (Point x2 y2) = x1*y2 - y1*x2
{-# INLINE vectorCross #-}

-- | Distance between two vectors.
vectorDistance :: Floating a => Point a -> Point a -> a
vectorDistance p q = vectorMag (p^-^q)
{-# INLINE vectorDistance #-}

-- | Interpolate between two vectors.
interpolateVector :: (Num a) => Point a -> Point a -> a -> Point a
interpolateVector a b t = t*^b ^+^ (1-t)*^a
{-# INLINE interpolateVector #-}

-- | Create a transform that rotates by the angle of the given vector
-- and multiplies with the magnitude of the vector.
rotateScaleVec :: Num a => Point a -> Transform a
rotateScaleVec (Point x y) = Transform x (-y) 0 y x 0
{-# INLINE rotateScaleVec #-}

-- | reflect the vector over the X-axis.
flipVector :: (Num a) => Point a -> Point a
flipVector (Point x y) = Point x (-y)
{-# INLINE flipVector #-}

turnAround :: (Num a) => Point a -> Point a
turnAround = negateV
{-# INLINE turnAround #-}

-- | Create a transform that rotates by the angle of the given vector
-- with the x-axis
rotateVec :: Floating a => Point a -> Transform a
rotateVec v = Transform x (-y) 0 y x 0
  where Point x y = normVector v
{-# INLINE rotateVec #-}

-- | Create a transform that rotates by the given angle (radians).
rotate :: Floating s => s -> Transform s
rotate a = Transform (cos a) (negate $ sin a) 0
           (sin a) (cos a) 0
{-# INLINE rotate #-}

-- | Rotate vector 90 degrees left.
rotate90L :: Floating s => Transform s
rotate90L = Transform 0 (-1) 0 1 0 0
{-# INLINE rotate90L #-}

-- | Rotate vector 90 degrees right.
rotate90R :: Floating s => Transform s
rotate90R = Transform 0 1 0 (-1) 0 0
{-# INLINE rotate90R #-}

-- | Create a transform that translates by the given vector.
translate :: Num a => Point a -> Transform a
translate (Point x y) = Transform 1 0 x 0 1 y
{-# INLINE translate #-}

-- | The identity transformation.
idTrans :: Num a => Transform a
idTrans = Transform 1 0 0 0 1 0
{-# INLINE idTrans #-}