{-# Language FlexibleInstances,
             UndecidableInstances,
             OverlappingInstances
  #-}
module Data.Geometry.Geometry(
                              -- ** Point based geometries
                               IsPoint2Functor(..)
                             , IsTransformable(..)
                             , HasPoints(..)
                             , Vec3(..)
                             , Matrix3(..)
                             , identityMatrix3
                             , matrix3FromLists
                             , matrix3FromList
                             , matrix3ToList
                             , matrix3ToLists
                             ) where


import Data.Geometry.Point

---------------------------------------------------------------------
-- | Point based geometries

-- | A class that defines a point2 functor. This defines that every operation that
-- we can do on a point we can also do on instances of this class. i.e. by
-- applying the operation on the underlying points.

class IsPoint2Functor g where
    p2fmap :: (Point2' a -> Point2' b) -> g a -> g b

class HasPoints g where
    points :: g a -> [Point2' a]

instance HasPoints Point2' where
    points p = [p]

---------------------------------------------------------------------
-- | Basic linear algebra to support affine transformations in 2D

-- | Type to represent a matrix, form is:
-- [ [ a11, a12, a13 ]
--   [ a21, a22, a23 ]
--   [ a31, a32, a33 ] ]

newtype Vec3    a = Vec3 (a,a,a)
                   deriving (Show,Eq)
newtype Matrix3 a = Matrix3 (Vec3 (Vec3 a))
                   deriving (Show,Eq)

instance Functor Vec3 where
    fmap f (Vec3 (a,b,c)) = Vec3 (f a, f b, f c)

instance Functor Matrix3 where
    fmap f (Matrix3 (Vec3 (a,b,c))) = Matrix3 $ Vec3 (fmap f a, fmap f b, fmap f c)

v3FromList         :: [a] -> Vec3 a
v3FromList [a,b,c] = Vec3 (a,b,c)
v3FromList _       = error "v3FromList needs exactly 3 elements."


-- | given a single list of 9 elements, construct a Matrix3
matrix3FromList :: [a] -> Matrix3 a
matrix3FromList = matrix3FromLists . rtake 3
                  where
                    rtake k ss = let (xs,ys) = splitAt k ss in
                                 xs : rtake k ys

-- | Given a 3x3 matrix as a list of lists, convert it to a Matrix3
matrix3FromLists :: [[a]] -> Matrix3 a
matrix3FromLists = Matrix3 . v3FromList . map v3FromList

v3ToList                :: Vec3 a -> [a]
v3ToList (Vec3 (a,b,c)) = [a,b,c]


-- | Gather the elements of the matrix in one long list (in row by row order)
matrix3ToList :: Matrix3 a -> [a]
matrix3ToList = concat . matrix3ToLists

matrix3ToLists                          :: Matrix3 a -> [[a]]
matrix3ToLists (Matrix3 (Vec3 (a,b,c))) = map v3ToList [a,b,c]


identityMatrix3 :: Num a => Matrix3 a
identityMatrix3 = matrix3FromLists [ [ 1, 0, 0 ]
                                   , [ 0, 1, 0 ]
                                   , [ 0, 0, 1 ] ]

multiply3 :: Num a => Matrix3 a -> Vec3 a -> Vec3 a
multiply3 (Matrix3 (Vec3 ( Vec3 (a,b,c)
                         , Vec3 (d,e,f)
                         , Vec3 (g,h,i)))) (Vec3 (x,y,z)) =
    Vec3 ( a*x + b*y + c*z,
           d*x + e*y + f*z,
           g*x + h*y + i*z)





-- | Class that indicates that something can be transformable using
-- an affine transformation

class IsTransformable g where
    transformWith :: Num a => Matrix3 a -> g a -> g a

-- | Points are transformable
instance IsTransformable Point2' where
    transformWith = transformPoint

transformPoint                  :: Num a => Matrix3 a -> Point2' a -> Point2' a
transformPoint m (Point2 (x,y)) =  Point2 (x',y')
    where
      v              = Vec3 (x,y,1)
      Vec3 (x',y',_) = multiply3 m v

-- | Everything that is built from points is transformable
tranformPointBased   :: (Num a, IsPoint2Functor g) => Matrix3 a -> g a -> g a
tranformPointBased m = p2fmap (transformWith m)

instance IsPoint2Functor g => IsTransformable g where
    transformWith = tranformPointBased
