{-
%------------------------------------------------------------------------------
% Copyright (C) 1997, 1998, 2008 Joern Dinkla, www.dinkla.net
%------------------------------------------------------------------------------
%
% see
%     Joern Dinkla, Geometrische Algorithmen in Haskell, Diploma Thesis,
%     University of Bonn, Germany, 1998. 
%
-}

-- |The type class 'Point' defines all operations on points. This type class is instanciated by the
-- different data types of points 'Point1', 'Point2', 'Point3', 'Point4' and 'PointN'.

module GeomAlg.Point (
     -- * The 'Point' class 
     Point (..), 
     toList,
	 xcoord, ycoord, zcoord, 
	 -- * Ordering relations on points.
	 lexic, 
	 lessIth, leqIth, equalIth, geqIth, greaterIth,
	 compareIthBy, compareIth, 
	 -- * Containedness in intervals
	 inIntervalIth, inInterval, 
	 -- * Norm, length and distance
	 norm, distance, sqrDistance
) where

import GeomAlg.External.Utilities (Rel, OrderRel)

infixl 7 <.>
infixl 6 <*>
infixl 5 <+>, <->
infix  4 <==>, </=>

-- |This type class defines all operations on points. 
class Functor p => Point p where

    -- |The origin.
    origin                    :: Num a => p a

    -- |The dimension.
    dimension                 :: Num a => p a -> Int

    -- |The ith component, aka x-, y- and z-coordinates.
    ith                       :: Num a => Int -> p a -> a

    -- |Equality.
    (<==>)                    :: Num a => p a -> p a -> Bool

    -- |Inequality.
    (</=>)                    :: Num a => p a -> p a -> Bool
    x </=> y                  = not (x <==> y)

    -- |Addition.
    (<+>)                     :: Num a => p a -> p a -> p a
    
    -- |Subtraction.
    (<->)                     :: Num a => p a -> p a -> p a
    v <-> w                   = v <+> negateP w

    -- |Unary negation
    negateP                   :: Num a => p a -> p a
    negateP                   = fmap (0-)

    -- |Dot product
    (<.>)                     :: Num a => p a -> p a -> a
    
    -- |Scalar multiplication from the left 
    (<*>)                     :: Num a => a -> p a -> p a
    k <*> p                   = fmap (k*) p

-- |Puts all coordinates into a list.
toList                        :: (Point p, Num a) => p a -> [a]
toList p                      = [ ith i p | i <- [1..dimension p]]

-- |The first coordinate, traditionally called x coordinate.
xcoord :: (Num a, Point p) => p a -> a
xcoord                        = ith 1

-- |The second coordinate, traditionally called y coordinate.
ycoord :: (Num a, Point p) => p a -> a
ycoord                        = ith 2

-- |The third coordinate, traditionally called z coordinate.
zcoord :: (Num a, Point p) => p a -> a
zcoord                        = ith 3

-- |Lexicographic ordering 
lexic                         :: (Point p, Num a, Ord a) => OrderRel (p a)
p `lexic` q                   = case is of
                                  [] -> EQ
                                  (i:_) -> compareIth i p q
  where is                    = dropWhile (\ i -> equalIth i p q) [1..dimension p]


-- |Auxiliary function that lifts a relation into the ith coordinate of the points x and y.
cmpAux                        :: (Point p, Num a) => Rel a -> Int -> Rel (p a)
cmpAux rel i x y                = ith i x `rel` ith i y

-- |Less-than relation for the ith dimension.
lessIth :: (Point p, Ord a, Num a) => Int -> Rel (p a)
lessIth	= cmpAux (<)

-- |Less-than-or-equal relation for the ith dimension.
leqIth :: (Point p, Ord a, Num a) => Int -> Rel (p a)
leqIth = cmpAux (<=)

-- |Equality relation for the ith dimension.
equalIth :: (Point p, Ord a, Num a) => Int -> Rel (p a)
equalIth = cmpAux (==)

-- |Greater-than-or-equal relation for the ith dimension.
geqIth :: (Point p, Ord a, Num a) => Int -> Rel (p a)
geqIth = cmpAux (>=)

-- |Greater-than relation for the ith dimension.
greaterIth :: (Point p, Ord a, Num a) => Int -> Rel (p a)
greaterIth = cmpAux (>)

-- |Lift a 'OrderRel' to the ith coordinate of the points x and y.
compareIthBy                  :: (Num a, Point p) => OrderRel a -> Int -> OrderRel (p a)
compareIthBy cmp i x y        = cmp (ith i x) (ith i y)

-- |Compares the ith coordinate.
compareIth                    :: (Ord a, Num a, Point p) => Int -> OrderRel (p a)
compareIth       		      = compareIthBy compare

-- |Test containedness in an (closed) interval in one dimension.
inIntervalIth                 :: (Point p, Num a, Ord a) => Int -> p a -> (p a,p a) -> Bool
inIntervalIth i x (p,q)       = ith i p <= y && y <= ith i q
  where y                     = ith i x

-- |Test containedness in an (closed) interval in all dimensions.
inInterval                    :: (Point p, Num a, Ord a) => p a -> (p a,p a) -> Bool
x `inInterval` (p,q)          = all (\ i -> inIntervalIth i x (p,q)) [1..dimension x]

-- |The norm
norm                          :: (Point p, Floating a) => p a -> a
norm x                        = sqrt (x <.> x)

-- |The distance between p and q.
distance                      :: (Point p, Floating a) => p a -> p a -> a
distance p q                  = norm (p <-> q)

-- |The square of the distance.
sqrDistance                   :: (Point p, Num a) => p a -> p a -> a
sqrDistance p q               = r <.> r where r = p <-> q