-- | Module to write stuff to buffers. As opposed to similar functions
-- exposed in "Raaz.Core.Write.Unsafe", the writes exposed here are
-- safe as necessary range checks are done on the buffer before
-- writing stuff to it.

{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE TypeSynonymInstances       #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE MultiParamTypeClasses      #-}

module Raaz.Core.Write
       ( Write, bytesToWrite, unsafeWrite
       , write, writeStorable, writeVector, writeStorableVector
       , writeBytes, writeByteString, skipWrite
       ) where

import           Data.ByteString           (ByteString)
import           Data.String
import           Data.ByteString.Internal  (unsafeCreate)
import           Data.Monoid
import qualified Data.Vector.Generic       as G
import           Data.Word                 (Word8)
import           Foreign.Ptr               (castPtr)
import           Foreign.Storable

import           Raaz.Core.MonoidalAction
import           Raaz.Core.Types.Endian
import           Raaz.Core.Types.Pointer
import           Raaz.Core.Util.ByteString as BU
import           Raaz.Core.Encode

-- | The monoid for write.
newtype WriteM = WriteM { unWriteM :: IO () }

instance Monoid WriteM where
  mempty        = WriteM $ return ()
  {-# INLINE mempty #-}

  mappend wa wb = WriteM $ unWriteM wa >> unWriteM wb
  {-# INLINE mappend #-}

  mconcat = WriteM . mapM_ unWriteM
  {-# INLINE mconcat #-}

-- | A write action is nothing but an IO action that returns () on
-- input a pointer.
type WriteAction = Pointer -> WriteM

type BytesMonoid = Sum (BYTES Int)

instance LAction BytesMonoid WriteAction where
  m <.> action = action . (m<.>)
  {-# INLINE (<.>) #-}

instance Distributive BytesMonoid WriteAction

-- | A write is an action which when executed using `runWrite` writes
-- bytes to the input buffer. It is similar to the `WU.Write` type
-- exposed from the "Raaz.Write.Unsafe" module except that it keeps
-- track of the total bytes that would be written to the buffer if the
-- action is run. The `runWrite` action will raise an error if the
-- buffer it is provided with is of size smaller. `Write`s are monoid
-- and hence can be concatnated using the `<>` operator.
type Write = SemiR WriteAction BytesMonoid

-- | Create a write action.
makeWrite :: LengthUnit u => u -> (Pointer -> IO ()) -> Write
{-# INLINE makeWrite #-}
makeWrite sz action = SemiR (WriteM . action) $ Sum (inBytes sz)

-- | Returns the bytes that will be written when the write action is performed.
bytesToWrite :: Write -> BYTES Int
bytesToWrite = getSum . semiRMonoid

-- | Perform the write action without any checks.
unsafeWrite :: Write -> Pointer -> IO ()
unsafeWrite wr =  unWriteM . semiRSpace wr

{-
-- | The function tries to write the given `Write` action on the
-- buffer and returns `True` if successful.
tryWriting :: Write         -- ^ The write action.
           -> CryptoBuffer  -- ^ The buffer to which the bytes are to
                            -- be written.
           -> IO Bool
tryWriting wr cbuf = withCryptoBuffer cbuf $ \ sz cptr ->
  if sz < bytesToWrite wr then return False
  else do unsafeWrite wr cptr; return True

-}

-- | The expression @`writeStorable` a@ gives a write action that
-- stores a value @a@ in machine endian. The type of the value @a@ has
-- to be an instance of `Storable`. This should be used when we want
-- to talk with C functions and not when talking to the outside world
-- (otherwise this could lead to endian confusion). To take care of
-- endianness use the `write` combinator.
writeStorable :: Storable a => a -> Write
writeStorable a = makeWrite (byteSize a) pokeIt
  where pokeIt = flip poke a . castPtr
-- | The expression @`write` a@ gives a write action that stores a
-- value @a@. One needs the type of the value @a@ to be an instance of
-- `EndianStore`. Proper endian conversion is done irrespective of
-- what the machine endianness is. The man use of this write is to
-- serialize data for the consumption of the outside world.
write :: EndianStore a => a -> Write
write a = makeWrite (byteSize a) $ flip store a

-- | The vector version of `writeStorable`.
writeStorableVector :: (Storable a, G.Vector v a) => v a -> Write
{-# INLINE writeStorableVector #-}
writeStorableVector = G.foldl' foldFunc mempty
  where foldFunc w a =  w <> writeStorable a

-- | The vector version of `write`.
writeVector :: (EndianStore a, G.Vector v a) => v a -> Write
{-# INLINE writeVector #-}
writeVector = G.foldl' foldFunc mempty
  where foldFunc w a =  w <> write a

-- | The combinator @writeBytes n b@ writes @b@ as the next @n@
-- consecutive bytes.
writeBytes :: LengthUnit n => Word8 -> n -> Write
writeBytes w8 n = makeWrite n memsetIt
  where memsetIt cptr = memset cptr w8 n

-- | Writes a strict bytestring.
writeByteString :: ByteString -> Write
writeByteString bs = makeWrite (BU.length bs) $  BU.unsafeCopyToPointer bs

-- | A write action that just skips over the given bytes.
skipWrite :: LengthUnit u => u -> Write
skipWrite = flip makeWrite $ const $ return ()

instance IsString Write where
  fromString = writeByteString . fromString

instance Encodable Write where
  {-# INLINE toByteString #-}
  toByteString w  = unsafeCreate n $ unsafeWrite w . castPtr
    where BYTES n = bytesToWrite w

  {-# INLINE unsafeFromByteString #-}
  unsafeFromByteString = writeByteString

  {-# INLINE fromByteString #-}
  fromByteString       = Just . writeByteString