{-# LANGUAGE FlexibleInstances, TypeOperators, UndecidableInstances
           , TypeSynonymInstances
  #-}
{-# OPTIONS_GHC -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.Cross
-- Copyright   :  (c) Conal Elliott 2008
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Cross products and normals
----------------------------------------------------------------------

module Data.Cross
  (
    HasNormal(..), normal
  , One, Two, Three
  , HasCross2(..), HasCross3(..)
  ) where

import Data.VectorSpace
import Data.LinearMap
import Data.Derivative

-- | Thing with a normal vector (not necessarily normalized).
class HasNormal v where normalVec :: v -> v

-- | Normalized normal vector.  See also 'cross'.
normal :: (HasNormal v, InnerSpace v s, Floating s) => v -> v
normal = normalized . normalVec


-- | Singleton
type One   s = s

-- | Homogeneous pair
type Two   s = (s,s)

-- | Homogeneous triple
type Three s = (s,s,s)

-- | Cross product of various forms of 2D vectors
class HasCross2 v where cross2 :: v -> v

instance Num s => HasCross2 (s,s) where
  cross2 (x,y) = (-y,x)  -- or @(y,-x)@?

-- TODO: Eliminate the 'Num' constraint by using negateV.

-- "Variable occurs more often in a constraint than in the instance
-- head".  Hence UndecidableInstances.

instance (LMapDom a s, VectorSpace v s, HasCross2 v) => HasCross2 (a:>v) where
  -- 2d cross-product is linear
  cross2 = fmapD cross2

instance (Num s, LMapDom s s) => HasNormal (One s :> Two s) where
  normalVec v = cross2 (derivativeAt v 1)

-- Does this problem come from the choice of 'VectorSpace' instance?

instance (Num s, LMapDom s s, VectorSpace s s)
    => HasNormal (Two (One s :> s)) where
  normalVec = unpairD . normalVec . pairD


-- | Cross product of various forms of 3D vectors
class HasCross3 v where cross3 :: v -> v -> v

instance Num s => HasCross3 (s,s,s) where
  (ax,ay,az) `cross3` (bx,by,bz) = ( ay * bz - az * by
                                   , az * bx - ax * bz
                                   , ax * by - ay * bx )

-- TODO: Eliminate the 'Num' constraint by using 'VectorSpace' operations.

instance (LMapDom a s, VectorSpace v s, HasCross3 v) => HasCross3 (a:>v) where
  -- 3D cross-product is bilinear (curried linear)
  cross3 = distrib cross3

instance (Num s, LMapDom s s) => HasNormal (Two s :> Three s) where
  normalVec v = d (1,0) `cross3` d (0,1)
   where
     d = derivativeAt v

instance (Num s, VectorSpace s s, LMapDom s s) => HasNormal (Three (Two s :> s)) where
  normalVec = untripleD . normalVec . tripleD

---- Could go elsewhere

pairD :: (LMapDom a s, VectorSpace b s, VectorSpace c s) =>
         (a:>b,a:>c) -> a:>(b,c)
pairD (u,v) = liftD2 (,) u v

tripleD :: (LMapDom a s, VectorSpace b s, VectorSpace c s, VectorSpace d s) =>
           (a:>b,a:>c,a:>d) -> a:>(b,c,d)
tripleD (u,v,w) = liftD3 (,,) u v w

unpairD :: (LMapDom a s, VectorSpace a s, VectorSpace b s, VectorSpace c s) =>
           (a :> (b,c)) -> (a:>b, a:>c)
unpairD d = (fst <$>> d, snd <$>> d)

untripleD :: ( LMapDom a s , VectorSpace a s, VectorSpace b s
             , VectorSpace c s, VectorSpace d s) =>
             (a :> (b,c,d)) -> (a:>b, a:>c, a:>d)
untripleD d =
  ((\ (a,_,_) -> a) <$>> d, (\ (_,b,_) -> b) <$>> d, (\ (_,_,c) -> c) <$>> d)