module Data.Repa.Array.Material.Strided
        ( S      (..)
        , Name   (..)
        , Array  (..)

        -- * Conversions
        , unsafeCast
        , fromForeignPtr,       toForeignPtr)
where
import Data.Repa.Array.Meta.Window
import Data.Repa.Array.Internals.Bulk
import Data.Repa.Array.Internals.Layout
import Data.Repa.Fusion.Unpack
import Data.Word
import qualified Foreign.Storable               as S
import qualified Foreign.ForeignPtr             as F
import qualified Data.ByteString.Internal       as BS
#include "repa-array.h"


-- | Layout for Foreign Strided arrays.
--
--   UNSAFE: indexing into foreign strided arrays is not bounds checked.
--   You may want to wrap this with a Checked layout as well.
--
data S  = Strided 
        { stridedLength :: !Int }
        deriving (Show, Eq)


-------------------------------------------------------------------------------
instance Layout S where
  data Name S           = S
  type Index S          = Int

  name                  = S
  create S len          = Strided len
  extent (Strided len)  = len
  toIndex   _ ix        = ix
  fromIndex _ ix        = ix
  {-# INLINE_ARRAY name      #-}
  {-# INLINE_ARRAY create    #-}
  {-# INLINE_ARRAY extent    #-}
  {-# INLINE_ARRAY toIndex   #-}
  {-# INLINE_ARRAY fromIndex #-}

deriving instance Eq   (Name S)
deriving instance Show (Name S)


-------------------------------------------------------------------------------
instance S.Storable a => Bulk S a where
  data Array S a         
        = SArray
        { sArrayStartBytes   :: !Int
        , sArrayStrideBytes  :: !Int
        , sArrayLenElems     :: !Int 
        , sArrayPtr          :: !(F.ForeignPtr a) }

  layout (SArray _ _ len _)  
   = Strided len

  index  (SArray start stride len fptr) ix
   = BS.inlinePerformIO
         $ F.withForeignPtr fptr
         $ \ptr -> S.peekByteOff ptr 
                      (start + (toIndex (Strided len) ix) * stride)
  {-# INLINE_ARRAY layout #-}
  {-# INLINE_ARRAY index  #-}
  {-# SPECIALIZE instance Bulk S Char    #-}
  {-# SPECIALIZE instance Bulk S Int     #-}
  {-# SPECIALIZE instance Bulk S Float   #-}
  {-# SPECIALIZE instance Bulk S Double  #-}
  {-# SPECIALIZE instance Bulk S Word8   #-}
  {-# SPECIALIZE instance Bulk S Word16  #-}
  {-# SPECIALIZE instance Bulk S Word32  #-}
  {-# SPECIALIZE instance Bulk S Word64  #-}


deriving instance (S.Storable a, Show a) => Show (Array S a)


instance Unpack (Array S a) (Int, Int, Int, F.ForeignPtr a) where
  unpack   (SArray start stride len fptr)  = (start, stride, len, fptr)
  repack _ (start, stride, len, fptr)      = (SArray start stride len fptr)
  {-# INLINE unpack #-}
  {-# INLINE repack #-}


-------------------------------------------------------------------------------
instance S.Storable a => Windowable S a where
  window startElems' lenElems' 
         (SArray startBytes strideBytes _lenElems fptr)
   = let lenElem = S.sizeOf (undefined :: a)
     in  SArray (startBytes + (lenElem * startElems'))
                strideBytes lenElems' fptr
  {-# INLINE_ARRAY window #-}
  {-# SPECIALIZE instance Windowable S Char    #-}
  {-# SPECIALIZE instance Windowable S Int     #-}
  {-# SPECIALIZE instance Windowable S Float   #-}
  {-# SPECIALIZE instance Windowable S Double  #-}
  {-# SPECIALIZE instance Windowable S Word8   #-}
  {-# SPECIALIZE instance Windowable S Word16  #-}
  {-# SPECIALIZE instance Windowable S Word32  #-}
  {-# SPECIALIZE instance Windowable S Word64  #-}


-------------------------------------------------------------------------------
-- | O(1). Cast a foreign array from one element type to another.
unsafeCast
        :: (S.Storable a, S.Storable b)
        => Array S a -> Array S b
unsafeCast (SArray startBytes strideBytes lenElems fptr)
        =  (SArray startBytes strideBytes lenElems $ F.castForeignPtr fptr)


-- | O(1). Wrap a `ForeignPtr` as a strided array.
fromForeignPtr 
        :: Int            -- ^ Starting position in bytes.
        -> Int            -- ^ Stride to get to next element, in bytes.
        -> Int            -- ^ Length of array in elements.
        -> F.ForeignPtr a -- ^ `ForeignPtr` holding the data.
        -> Array S a

fromForeignPtr startBytes strideBytes lenElems fptr
      = SArray startBytes strideBytes lenElems fptr
{-# INLINE_ARRAY fromForeignPtr #-}


-- | O(1). Unwrap a `ForeignPtr` from a strided array.
toForeignPtr
        :: Array S a
        -> (Int, Int, Int, F.ForeignPtr a)
toForeignPtr (SArray startBytes strideBytes lenElems fptr)
        = (startBytes, strideBytes, lenElems, fptr)
{-# INLINE_ARRAY toForeignPtr #-}