{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances,
 FunctionalDependencies, KindSignatures,
  MultiParamTypeClasses, NoImplicitPrelude,
  TypeOperators, UndecidableInstances  #-}
{-# OPTIONS -Wall #-}
-- | A tensor algebra library. Main ingredients are :
--
-- 'Vec' and ':~' are data constructors for rank-1 tensor.
-- This is essentially a touple of objects of the same type.
--
-- 'Vector' is a class for rank-1 tensor.
--
-- 'Axis' is an object for accessing the tensor components.

module Data.Tensor.TypeLevel
    (
     (:~)(..), Vec(..), Axis(..), (!),
     Vector(..), VectorRing(..),
     contract,
     Vec0, Vec1, Vec2, Vec3, Vec4,
     Vec5, Vec6, Vec7, Vec8, Vec9, Vec10,
     vec0, vec1, vec2, vec3, vec4,
     vec5, vec6, vec7, vec8, vec9, vec10
    ) where

import qualified Algebra.Additive as Additive
import qualified Algebra.Ring as Ring
import           System.IO.Unsafe
import           Text.Read
import qualified Text.ParserCombinators.ReadP as P

import           Control.Applicative
import           Control.Monad hiding
    (mapM_, sequence_, forM_, msum, mapM, sequence, forM)
import           Data.Foldable
import           Data.List (intercalate)
import           Data.Traversable
import           NumericPrelude hiding
    (Monad, Functor, (*>),
     (>>=), (>>), return, fail, fmap, mapM, mapM_, sequence, sequence_,
     (=<<), foldl, foldl1, foldr, foldr1, and, or, any, all, sum, product,
     concat, concatMap, maximum, minimum, elem, notElem)
import qualified Prelude as P98
import qualified Test.QuickCheck.Arbitrary as QC

infixl 9 !
-- | a component operator.
(!) :: Vector v => v a -> Axis v -> a
v ! i  = component i v

-- | data constructor for 0-dimensional tensor.
data Vec a
  = Vec
  deriving (Eq, Ord)

-- | data constructor for constructing n+1-dimensional tensor
-- from n-dimensional tensor.
data (n :: * -> * ) :~ a
  = (n a) :~ a
  deriving (Eq)
infixl 3 :~

instance Show (Vec a) where
  show = const "()"

instance (Show a, Traversable ((:~) n)) => Show (n :~ a) where
  show = ("("++) . (++")") .
         intercalate "," . map show . toList

instance Read (Vec a) where
  readsPrec _ = P.readP_to_S $ do
    _ <- P.string "()"
    return Vec

instance (Read a, Vector ((:~) n)) => Read (n :~ a) where
  readsPrec _ = P.readP_to_S $ do
    _ <- P.char '('
    ret <- sequence $ compose (\(Axis i) -> do
                when (i>0) $ (P.char ',' >> return ())
                P.readS_to_P (readsPrec 0)
            )
    _ <- P.char ')'
    return ret

instance QC.Arbitrary (Vec a) where
  arbitrary = return Vec
  shrink = const []

instance (QC.Arbitrary a, QC.Arbitrary (n a), Traversable n) => QC.Arbitrary (n :~ a) where
  arbitrary = (:~) <$> QC.arbitrary <*> QC.arbitrary
  shrink = sequence . fmap QC.shrink



-- | the last component contributes the most to the ordering
instance (Ord (n a), Ord a) => Ord (n :~ a) where
  compare (xs :~ x) (ys :~ y) = compare (x, xs) (y, ys)

instance Foldable Vec where
  foldMap = foldMapDefault
instance Functor Vec where
  fmap = fmapDefault
instance Traversable Vec where
  traverse _ Vec = pure Vec
instance Applicative Vec where
  pure _  = Vec
  _ <*> _ = Vec

instance (Traversable n) => Foldable ((:~) n) where
  foldMap = foldMapDefault
instance (Traversable n) => Functor ((:~) n) where
  fmap = fmapDefault
instance (Traversable n) => Traversable ((:~) n) where
  traverse f (x :~ y) = (:~) <$> traverse f x <*> f y
instance (Applicative n, Traversable n) => Applicative ((:~) n) where
  pure x = pure x :~ x
  (vf :~ f) <*> (vx :~ x) = (vf <*> vx) :~ (f x)



-- | An coordinate 'Axis' , labeled by an integer.
-- Axis also carries v, the container type for its corresponding
-- vector. Therefore, An axis of one type can access only vectors
-- of a fixed dimension, but of arbitrary type.
newtype Axis (v :: * -> *) = Axis {axisIndex::Int} deriving (Eq,Ord,Show,Read)

-- | An object that allows component-wise access.
class (Traversable v) => Vector v where
  -- | Get a component within f, a context which allows failure.
  componentF :: (Alternative f) =>
                Axis v -- ^the axis of the component you want
                -> v a -- ^the target vector
                -> f a -- ^the component, obtained within a failure

  -- | Get a component. This computation may result in a runtime error,
  -- though, as long as the 'Axis' is generated from library functions
  -- such as 'compose', there will be no error.
  component :: Axis v -> v a -> a
  component axis vec = case componentF axis vec of
    Just x  -> x
    Nothing -> error $ "axis out of bound: " ++ show axis
  -- | The dimension of the vector.
  dimension :: v a -> Int
  -- | Create a 'Vector' from a function that maps
  -- axis to components.
  compose :: (Axis v -> a) -> v a

instance Vector Vec where
  componentF axis Vec
    = empty
  dimension _ = 0
  compose _ = Vec

instance (Vector v) => Vector ((:~) v) where
  componentF (Axis i) vx@(v :~ x)
    | i==dimension vx - 1 = pure x
    | True                = componentF (Axis i) v
  dimension (v :~ _) = 1 + dimension v
  compose f = let
    xs = compose (\(Axis i)->f (Axis i)) in xs :~ f (Axis (dimension xs))

-- | Vector whose components are additive is also additive.
instance (Additive.C a) => Additive.C (Vec a) where
  zero = compose $ const Additive.zero
  x+y  = compose (\i ->  x!i +  y!i)
  x-y  = compose (\i ->  x!i -  y!i)
  negate x = compose (\i -> negate $ x!i)

instance (Vector v, Additive.C a) => Additive.C ((:~) v a) where
  zero = compose $ const Additive.zero
  x+y  = compose (\i -> x!i + y!i)
  x-y  = compose (\i -> x!i - y!i)
  negate x = compose (\i -> negate $ x!i)

-- | Tensor contraction. Create a 'Vector' from a function that maps
-- axis to component, then sums over the axis and returns @a@.
contract :: (Vector v, Additive.C a) => (Axis v -> a) -> a
contract f = foldl (+) Additive.zero (compose f)



-- | 'VectorRing' is a 'Vector' whose components belongs to 'Ring.C',
-- thus providing unit vectors.
class  (Vector v, Ring.C a) => VectorRing v a where
  -- | A vector where 'Axis'th component is unity but others are zero.
  unitVectorF :: (Alternative f) => Axis v -> f (v a)
  -- | pure but unsafe version means of obtaining a 'unitVector'
  unitVector :: Axis v -> v a
  unitVector axis = case unitVectorF axis of
    Just x  -> x
    Nothing -> error $ "axis out of bound: " ++ show axis

instance (Ring.C a) => VectorRing Vec a where
  unitVectorF axis = empty

instance (Ring.C a, VectorRing v a, Additive.C (v a))
    => VectorRing ((:~) v) a where
  unitVectorF axis@(Axis i) = ret
    where
      z = Additive.zero
      d = dimension z
      ret
        | i < 0 || i >= d   = empty
        | i == d-1          = pure $ Additive.zero :~ Ring.one
        | 0 <= i && i < d-1 = fmap (:~ Additive.zero) $ unitVectorF (Axis i)
        | True              = pure z
        -- this last guard never matches, but needed to infer the type of z.

instance (Vector v, P98.Num a) => P98.Num ((:~) v a) where
  x+y  = compose (\i -> x!i P98.+ y!i)
  x-y  = compose (\i -> x!i P98.- y!i)
  negate x = compose (\i -> P98.negate $ x!i)
  (*) = error "P98 legacy instance"
  fromInteger x = compose $ const (P98.fromInteger x)
  abs = error "P98 legacy instance"
  signum = error "P98 legacy instance"

-- | Type synonyms
type Vec0 = Vec
type Vec1 = (:~) Vec0
type Vec2 = (:~) Vec1
type Vec3 = (:~) Vec2
type Vec4 = (:~) Vec3
type Vec5 = (:~) Vec4
type Vec6 = (:~) Vec5
type Vec7 = (:~) Vec6
type Vec8 = (:~) Vec7
type Vec9 = (:~) Vec8
type Vec10 = (:~) Vec9

-- | Utility functions
vec0 :: Vec0 a
vec0  = Vec
vec1 :: a -> Vec1 a
vec1 x0 = Vec :~ x0
vec2 :: a -> a -> Vec2 a
vec2 x0 x1 = Vec :~ x0 :~ x1
vec3 :: a -> a -> a -> Vec3 a
vec3 x0 x1 x2 = Vec :~ x0 :~ x1 :~ x2
vec4 :: a -> a -> a -> a -> Vec4 a
vec4 x0 x1 x2 x3 = Vec :~ x0 :~ x1 :~ x2 :~ x3
vec5 :: a -> a -> a -> a -> a -> Vec5 a
vec5 x0 x1 x2 x3 x4 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4
vec6 :: a -> a -> a -> a -> a -> a -> Vec6 a
vec6 x0 x1 x2 x3 x4 x5 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4 :~ x5
vec7 :: a -> a -> a -> a -> a -> a -> a -> Vec7 a
vec7 x0 x1 x2 x3 x4 x5 x6 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4 :~ x5 :~ x6
vec8 :: a -> a -> a -> a -> a -> a -> a -> a -> Vec8 a
vec8 x0 x1 x2 x3 x4 x5 x6 x7 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4 :~ x5 :~ x6 :~ x7
vec9 :: a -> a -> a -> a -> a -> a -> a -> a -> a -> Vec9 a
vec9 x0 x1 x2 x3 x4 x5 x6 x7 x8 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4 :~ x5 :~ x6 :~ x7 :~ x8
vec10 :: a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> Vec10 a
vec10 x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 = Vec :~ x0 :~ x1 :~ x2 :~ x3 :~ x4 :~ x5 :~ x6 :~ x7 :~ x8 :~ x9
