{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

-- | This modules extends the 'Prim' from "Data.Primitive" class to cases
-- where we don't know the primitive information (like the size) at compile
-- time.  Instead, we must pass in a 'PseudoPrimInfo' object that will get
-- evaluated on every function call and that contains the needed information.
--
-- For 'PseudoPrim' instances that are also 'Prim' instances, this involves 
-- no run time overhead.  For 'PseudoPrim' instances that cannot be made
-- 'Prim' instances, this involves a mild memory and speed bookkeeping 
-- overhead.
module Data.Params.PseudoPrim
    where

import GHC.Base (Int (..))
import GHC.Int
import GHC.Prim
import Data.Word

import Control.Monad.Primitive
import Data.Primitive

-------------------------------------------------------------------------------
-- PseudoPrim class

class PseudoPrim a where
    data family PseudoPrimInfo a
    pp_sizeOf#          :: PseudoPrimInfo a -> Int#
    pp_alignment#       :: PseudoPrimInfo a -> Int#
    pp_indexByteArray#  :: PseudoPrimInfo a -> ByteArray# -> Int# -> a
    pp_readByteArray#   :: PseudoPrimInfo a -> MutableByteArray# s -> Int# -> State# s -> (# State# s, a #)
    pp_writeByteArray#  :: PseudoPrimInfo a -> MutableByteArray# s -> Int# -> a -> State# s -> State# s

    -- | Do we need to evaluate the info in order to call these functions?
    seqInfo :: a -> Bool

    -- | If 'seqInfo' returns 'True', then this function is undefined.
    -- Otherwise, it containes an empty 'PseudoPrimInfo' whose type is
    -- sufficient to determine all the needed information.
    emptyInfo :: PseudoPrimInfo a

#define mkPseudoPrim(t,ppi) \
instance PseudoPrim t where\
    newtype PseudoPrimInfo t = ppi ()               ;\
    pp_sizeOf# a = sizeOf# (undefined :: t)         ;\
    pp_alignment# a = alignment# (undefined :: t)   ;\
    pp_indexByteArray# a = indexByteArray#          ;\
    pp_readByteArray# _ = readByteArray#            ;\
    pp_writeByteArray# _ = writeByteArray#          ;\
    seqInfo _ = False                               ;\
    emptyInfo = ppi ()               

mkPseudoPrim(Double,PseudoPrimInfo_Double)
mkPseudoPrim(Float,PseudoPrimInfo_Float)
mkPseudoPrim(Int,PseudoPrimInfo_Int)
mkPseudoPrim(Char,PseudoPrimInfo_Char)
mkPseudoPrim(Word8,PseudoPrimInfo_Word8)
mkPseudoPrim(Word16,PseudoPrimInfo_Word16)
mkPseudoPrim(Word32,PseudoPrimInfo_Word32)
mkPseudoPrim(Word64,PseudoPrimInfo_Word64)

-------------------------------------------------------------------------------
-- helper functions

{-# INLINE pp_sizeOf #-}
pp_sizeOf :: PseudoPrim a => PseudoPrimInfo a -> Int
pp_sizeOf x = I# (pp_sizeOf# x)

{-# INLINE pp_alignment #-}
pp_alignment :: PseudoPrim a => PseudoPrimInfo a -> Int
pp_alignment x = I# (pp_alignment# x)

{-# INLINE pp_readByteArray #-}
pp_readByteArray
  :: (PseudoPrim a, PrimMonad m) => PseudoPrimInfo a -> MutableByteArray (PrimState m) -> Int -> m a
pp_readByteArray ppi (MutableByteArray arr#) (I# i#) = primitive (pp_readByteArray# ppi arr# i#)

{-# INLINE pp_writeByteArray #-}
pp_writeByteArray
  :: (PseudoPrim a, PrimMonad m) => PseudoPrimInfo a -> MutableByteArray (PrimState m) -> Int -> a -> m ()
pp_writeByteArray ppi (MutableByteArray arr#) (I# i#) x = primitive_ (pp_writeByteArray# ppi arr# i# x)

{-# INLINE pp_indexByteArray #-}
pp_indexByteArray :: PseudoPrim a => PseudoPrimInfo a -> ByteArray -> Int -> a
pp_indexByteArray ppi (ByteArray arr#) (I# i#) = pp_indexByteArray# ppi arr# i#