```{-# 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*z + 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
```