module Geometry.Plane.General
    ( Plane (..)
    , Plane2, Plane3
    , Plane2D, Plane3D
    , MakePlane (..)
    , unsafeMakePlane
    , flipPlane
    , collinear
 
    , PlanesRelation (..), Incidence (..), Orientation (..)
    , planesRelation
    , isParallel
    ) where
import Protolude hiding (zipWith, zero)
import Data.Maybe (fromJust)
import qualified Data.List as List
import Linear
import Linear.Affine (Point, (.-.))
import qualified Linear.Affine as Point
import Data.EqZero
data Plane v n = Plane
   { planeVector :: !(v n)
   , planeLast   :: !n
   } deriving (Eq, Ord, Show)
type Plane2 = Plane V2
type Plane3 = Plane V3
type Plane2D = Plane V2 Double
type Plane3D = Plane V3 Double
instance (NFData (v n), NFData n) => NFData (Plane v n) where
    rnf (Plane vs l) = rnf vs `seq` rnf l
flipPlane :: (Functor v, Num n) => Plane v n -> Plane v n
flipPlane (Plane v n) = Plane (fmap negate v) (negate n)
class MakePlane v n where
    
    
    makePlane :: v (Point v n) -> Maybe (Plane v n)
instance (Num n, Eq n) => MakePlane V3 n where
    makePlane (V3 p1 p2 p3)
        | n == zero = Nothing
        | otherwise = Just $ Plane n d
        where
        n = cross (p2 .-. p1) (p3 .-. p1)
        d = negate $ dot n $ unPoint p1
unsafeMakePlane :: MakePlane v n => v (Point v n) -> Plane v n
unsafeMakePlane = fromJust . makePlane
unPoint :: Point v n -> v n
unPoint (Point.P x) = x
collinear :: (Foldable v, Num n, EqZero n) => v n -> v n -> Bool
collinear v w = all f $ combinations 2 $ zipWith (,) v w
    where
    f [(a, b), (c, d)] = eqZero $ a*d  b*c
    f _                = False 
combinations :: Int -> [a] -> [[a]]
combinations k is
    | k <= 0    = [ [] ]
    | otherwise = [ x:r | x:xs <- tails is, r <- combinations (k1) xs ]
zipWith :: Foldable f => (a -> b -> c) -> f a -> f b -> [c]
zipWith f a b = List.zipWith f (toList a) (toList b)
coincidence :: (Foldable v, Num n, EqZero n) => Plane v n -> Plane v n -> Bool
coincidence (Plane v1 d1) (Plane v2 d2) = all f $ zipWith (,) v1 v2
    where
    f (x1, x2) = eqZero $ x1*d2  x2*d1
coorientation :: (Foldable v, Num n, Ord n, EqZero n)
    => Plane v n -> Plane v n -> Bool
coorientation (Plane v1 d1) (Plane v2 d2)
    = all geqZero $ d1*d2 : zipWith (*) v1 v2
data PlanesRelation = Parallel Incidence Orientation | Crossing deriving Show
data Incidence      = CoIncident |  NonIncident                 deriving Show
data Orientation    = CoOriented | AntiOriented                 deriving Show
planesRelation :: (Foldable v, Num n, Ord n, EqZero n)
    => Plane v n -> Plane v n -> PlanesRelation
planesRelation p1@(Plane v1 _) p2@(Plane v2 _)
    | collinear v1 v2 = Parallel incidence orientation
    | otherwise       = Crossing
    where
    incidence   = bool  NonIncident CoIncident $ coincidence   p1 p2
    orientation = bool AntiOriented CoOriented $ coorientation p1 p2
isParallel :: (Foldable v, Num n, Ord n, EqZero n)
    => Plane v n -> Plane v n -> Bool
isParallel a b = case planesRelation a b of
    Parallel _ _ -> True
    Crossing     -> False