module Ptr.Poking
where

import Ptr.Prelude
import qualified Ptr.IO as A
import qualified Ptr.Poke as C
import qualified Ptr.PokeAndPeek as D
import qualified Data.ByteString.Internal as B


{-|
Efficiently composable specification of how to populate a pointer.
-}
data Poking =
  {-|
  * Amount of bytes the encoded data will occupy.
  * Exception-free action, which populates the pointer to the encoded data.
  -}
  Poking !Int !(Ptr Word8 -> IO ())

instance Semigroup Poking where
  {-# INLINE (<>) #-}
  (<>) (Poking space1 action1) (Poking space2 action2) =
    Poking (space1 + space2) (\ptr -> action1 ptr *> action2 (plusPtr ptr space1))

instance Monoid Poking where
  {-# INLINE mempty #-}
  mempty =
    Poking 0 (const (pure ()))
  {-# INLINE mappend #-}
  mappend =
    (<>)

{-|
Same as 'mappend' and '<>',
but performs the serialization concurrently.
This comes at the cost of an overhead,
so it is only advised to use this function when the merged serializations are heavy.
-}
prependConc :: Poking -> Poking -> Poking
prependConc (Poking space1 action1) (Poking space2 action2) =
  Poking (space1 + space2) action
  where
    action ptr =
      do
        lock <- newEmptyMVar
        forkIO $ do
          action1 ptr
          putMVar lock ()
        action2 (plusPtr ptr space1)
        takeMVar lock

{-# INLINE word8 #-}
word8 :: Word8 -> Poking
word8 x =
  Poking 1 (flip A.pokeWord8 x)

{-# INLINE beWord32 #-}
beWord32 :: Word32 -> Poking
beWord32 x =
  Poking 4 (flip A.pokeBEWord32 x)

{-# INLINE beWord64 #-}
beWord64 :: Word64 -> Poking
beWord64 x =
  Poking 8 (flip A.pokeBEWord64 x)

{-# INLINE bytes #-}
bytes :: ByteString -> Poking
bytes (B.PS bytesFPtr offset length) =
  Poking length (\ptr -> withForeignPtr bytesFPtr (\bytesPtr -> B.memcpy ptr (plusPtr bytesPtr offset) length))

{-# INLINE poke #-}
poke :: C.Poke input -> input -> Poking
poke (C.Poke space poke) input =
  Poking space (\ptr -> poke ptr input)

{-# INLINE pokeAndPeek #-}
pokeAndPeek :: D.PokeAndPeek input output -> input -> Poking
pokeAndPeek (D.PokeAndPeek space poke _) input =
  Poking space (\ptr -> poke ptr input)