{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE EmptyDataDecls #-}
module Knead.Shape where

import qualified Data.Array.Knead.Shape as Shape
import qualified Data.Array.Knead.Expression as Expr

import qualified Data.Array.Comfort.Shape as ComfortShape

import qualified LLVM.Extra.Multi.Value.Marshal as Marshal
import qualified LLVM.Extra.Multi.Value as MultiValue
import qualified LLVM.Extra.Memory as Memory
import qualified LLVM.Extra.Tuple as Tuple
import qualified LLVM.Extra.Iterator as Iter
import qualified LLVM.Extra.Arithmetic as A
import LLVM.Extra.Multi.Value (atom)

import qualified LLVM.Core as LLVM

import qualified Type.Data.Num.Decimal as TypeNum

import Foreign.Storable
         (Storable, sizeOf, alignment, poke, pokeElemOff, peek, peekElemOff)
import Foreign.Ptr (Ptr, castPtr)

import qualified Control.Monad.HT as Monad
import Control.Monad (join)

import Data.Tuple.HT (mapSnd)
import Data.Int (Int64)


{- |
I choose a bit complicated Dim2 definition
to make it distinct from size pairs with width and height swapped.
Alternatives would be Index.Linear or intentionally complicated Shape types like:

type Dim0 = ()
type Dim1 = ((), Size)
type Dim2 = ((), Size, Size)

Problems with Index.Linear is that it is fixed to Word32 dimensions
which causes trouble with negative coordinates
that we encounter on rotations.

The custom shape type requires lots of new definitions
but it is certainly the cleanest solution.
-}
type Size = Int64
type Dim0 = ()
type Dim1 = Shape.ZeroBased Size
type Dim2 = Shape2ZB Size
type Ix2  = Index2 Size

data Vec2 tag i = Vec2 {vertical, horizontal :: i}

data ShapeTag
data IndexTag
data FactorTag

type Shape2 = Vec2 ShapeTag
type Shape2ZB n = Shape2 (Shape.ZeroBased n)
type Index2 = Vec2 IndexTag
type Factor2 = Vec2 FactorTag



squareShape :: n -> Vec2 tag n
squareShape n = Vec2 n n

castToElemPtr :: Ptr (Vec2 tag a) -> Ptr a
castToElemPtr = castPtr

instance (Storable n) => Storable (Vec2 tag n) where
   -- cf. sample-frame:Frame.Stereo
   sizeOf ~(Vec2 n m) =
      sizeOf n + mod (- sizeOf n) (alignment m) + sizeOf m
   alignment ~(Vec2 n _) = alignment n
   poke p (Vec2 n m) =
      let q = castToElemPtr p
      in  poke q n >> pokeElemOff q 1 m
   peek p =
      let q = castToElemPtr p
      in  Monad.lift2 Vec2 (peek q) (peekElemOff q 1)

instance (Tuple.Undefined n) => Tuple.Undefined (Vec2 tag n) where
   undef = Vec2 Tuple.undef Tuple.undef

instance (Tuple.Phi n) => Tuple.Phi (Vec2 tag n) where
   phi bb (Vec2 a0 a1) =
      Monad.lift2 Vec2 (Tuple.phi bb a0) (Tuple.phi bb a1)
   addPhi bb (Vec2 a0 a1) (Vec2 b0 b1) =
      Tuple.addPhi bb a0 b0 >>
      Tuple.addPhi bb a1 b1

instance (Tuple.Value n) => Tuple.Value (Vec2 tag n) where
   type ValueOf (Vec2 tag n) = Vec2 tag (Tuple.ValueOf n)
   valueOf (Vec2 n m) = Vec2 (Tuple.valueOf n) (Tuple.valueOf m)

instance (MultiValue.C n) => MultiValue.C (Vec2 tag n) where
   type Repr (Vec2 tag n) = Vec2 tag (MultiValue.Repr n)
   cons (Vec2 n m) =
      MultiValue.compose $ Vec2 (MultiValue.cons n) (MultiValue.cons m)
   undef = MultiValue.compose $ squareShape MultiValue.undef
   zero = MultiValue.compose $ squareShape MultiValue.zero
   phi bb a =
      case MultiValue.decompose (squareShape atom) a of
         Vec2 a0 a1 ->
            fmap MultiValue.compose $
            Monad.lift2 Vec2 (MultiValue.phi bb a0) (MultiValue.phi bb a1)
   addPhi bb a b =
      case (MultiValue.decompose (squareShape atom) a,
            MultiValue.decompose (squareShape atom) b) of
         (Vec2 a0 a1, Vec2 b0 b1) ->
            MultiValue.addPhi bb a0 b0 >>
            MultiValue.addPhi bb a1 b1

type instance
   MultiValue.Decomposed f (Vec2 tag pat) =
      Vec2 tag (MultiValue.Decomposed f pat)
type instance
   MultiValue.PatternTuple (Vec2 tag pat) =
      Vec2 tag (MultiValue.PatternTuple pat)

instance (MultiValue.Compose n) => MultiValue.Compose (Vec2 tag n) where
   type Composed (Vec2 tag n) = Vec2 tag (MultiValue.Composed n)
   compose (Vec2 n m) =
      case (MultiValue.compose n, MultiValue.compose m) of
         (MultiValue.Cons rn, MultiValue.Cons rm) ->
            MultiValue.Cons (Vec2 rn rm)

instance (MultiValue.Decompose pn) => MultiValue.Decompose (Vec2 tag pn) where
   decompose (Vec2 pn pm) (MultiValue.Cons (Vec2 n m)) =
      Vec2
         (MultiValue.decompose pn (MultiValue.Cons n))
         (MultiValue.decompose pm (MultiValue.Cons m))

instance (Memory.C i) => Memory.C (Vec2 tag i) where
   type Struct (Vec2 tag i) =
            LLVM.Struct (Memory.Struct i, (Memory.Struct i, ()))
   decompose nm =
      Monad.lift2 Vec2
         (Memory.decompose =<< LLVM.extractvalue nm TypeNum.d0)
         (Memory.decompose =<< LLVM.extractvalue nm TypeNum.d1)
   compose (Vec2 n m) = do
      sn <- Memory.compose n
      sm <- Memory.compose m
      rn <- LLVM.insertvalue (LLVM.value LLVM.undef) sn TypeNum.d0
      LLVM.insertvalue rn sm TypeNum.d1

instance (Marshal.C i) => Marshal.C (Vec2 tag i) where
   pack (Vec2 n m) = LLVM.consStruct (Marshal.pack n) (Marshal.pack m)
   unpack =
      LLVM.uncurryStruct $ \n m -> Vec2 (Marshal.unpack n) (Marshal.unpack m)


unzipShape :: MultiValue.T (Vec2 tag n) -> Vec2 tag (MultiValue.T n)
unzipShape = MultiValue.decompose (squareShape atom)

zipShape :: MultiValue.T n -> MultiValue.T n -> MultiValue.T (Vec2 tag n)
zipShape y x = MultiValue.compose $ Vec2 y x

instance (tag ~ ShapeTag, ComfortShape.C i) => ComfortShape.C (Vec2 tag i) where
   size (Vec2 n m) = ComfortShape.size n * ComfortShape.size m

instance
   (tag ~ ShapeTag, ComfortShape.Indexed i) =>
      ComfortShape.Indexed (Vec2 tag i) where
   type Index (Vec2 tag i) = Index2 (Shape.Index i)
   indices (Vec2 n m) = map (uncurry Vec2) $ ComfortShape.indices (n,m)
   unifiedSizeOffset (Vec2 n m) =
      mapSnd (. (\(Vec2 i j) -> (i,j))) $ ComfortShape.unifiedSizeOffset (n,m)
   inBounds (Vec2 n m) (Vec2 i j) = ComfortShape.inBounds (n,m) (i,j)

instance (tag ~ ShapeTag, Shape.C i) => Shape.C (Vec2 tag i) where
   intersectCode a b =
      case (unzipShape a, unzipShape b) of
         (Vec2 an am, Vec2 bn bm) ->
            Monad.lift2 zipShape
               (Shape.intersectCode an bn)
               (Shape.intersectCode am bm)
   size nm =
      case unzipShape nm of
         Vec2 n m ->
            join $ Monad.lift2 A.mul (Shape.size n) (Shape.size m)
   sizeOffset nm =
      case unzipShape nm of
         (Vec2 n m) -> do
            (ns, iOffset) <- Shape.sizeOffset n
            (ms, jOffset) <- Shape.sizeOffset m
            sz <- A.mul ns ms
            return
               (sz,
                \ij ->
                  case unzipShape ij of
                     (Vec2 i j) -> do
                        il <- iOffset i
                        jl <- jOffset j
                        A.add jl =<< A.mul ms il)
   loop code nm =
      case unzipShape nm of
         Vec2 n m ->
            Shape.loop (\i -> Shape.loop (\j -> code (zipShape i j)) m) n
   iterator nm =
      case unzipShape nm of
         Vec2 n m ->
            fmap (uncurry zipShape) $
            Iter.cartesian (Shape.iterator n) (Shape.iterator m)


instance (Expr.Compose n) => Expr.Compose (Vec2 tag n) where
   type Composed (Vec2 tag n) = Vec2 tag (Expr.Composed n)
   compose (Vec2 n m) = Expr.lift2 zipShape (Expr.compose n) (Expr.compose m)

instance (Expr.Decompose p) => Expr.Decompose (Vec2 tag p) where
   decompose (Vec2 pn pm) vec =
      Vec2
         (Expr.decompose pn (verticalVal vec))
         (Expr.decompose pm (horizontalVal vec))

verticalVal, horizontalVal :: (Expr.Value val) => val (Vec2 tag n) -> val n
verticalVal = Expr.lift1 (MultiValue.lift1 vertical)
horizontalVal = Expr.lift1 (MultiValue.lift1 horizontal)

verticalSize, horizontalSize ::
   (Expr.Value val) => val (Shape2 (Shape.ZeroBased n)) -> val n
verticalSize = Shape.zeroBasedSize . verticalVal
horizontalSize = Shape.zeroBasedSize . horizontalVal
