-- | Conversions for "Data.ByteString" things.
module Data.Repa.Convert.Format.Bytes
        (VarBytes (..))
where
import Data.Repa.Convert.Internal.Format
import Data.Repa.Convert.Internal.Packable
import Data.Word
import GHC.Exts
import Prelude                                  hiding (fail)
import Data.ByteString                          (ByteString)
import qualified Data.ByteString.Char8          as BS
import qualified Data.ByteString.Internal       as BS
import qualified Foreign.Marshal.Alloc          as F
import qualified Foreign.ForeignPtr             as F
import qualified Foreign.Storable               as F
import qualified Foreign.Ptr                    as F


-- | Variable length sequence of bytes, represented as a `Data.ByteString`.
--
data VarBytes                   = VarBytes      deriving (Eq, Show)
instance Format VarBytes        where
 type Value VarBytes            = ByteString
 fieldCount _                   = 1
 minSize    _                   = 0
 fixedSize  VarBytes            = Nothing
 packedSize VarBytes bs         = Just $ BS.length bs
 {-# INLINE fieldCount #-}
 {-# INLINE minSize #-}
 {-# INLINE fixedSize #-}
 {-# INLINE packedSize #-}


instance Packable VarBytes where

 packer VarBytes (BS.PS fptr start len) dst _fails k
  = F.withForeignPtr fptr
  $ \ptr_
  -> let
        -- Pointer to active bytes.
        !ptr = F.plusPtr ptr_ start

        -- Copy bytes from the bytestring to the destination buffer.
        packer_VarBytes !ix
         | ix >= len
         = let  !(Ptr dst') = F.plusPtr (Ptr dst) ix
            in  k dst'

         | otherwise
         = do   !(x :: Word8) <- F.peekByteOff ptr ix
                F.pokeByteOff (Ptr dst) ix x
                packer_VarBytes (ix + 1)
        {-# INLINE packer_VarBytes #-}

     in packer_VarBytes 0


instance Unpackable VarBytes where

 unpacker VarBytes start end stop _fail eat
  = checkLen 0
  where
        -- Length of the input buffer.
        !lenBuf = F.minusPtr (pw8 end) (pw8 start)

        -- Scan through the input to see how long the result will be.
        checkLen !ix
         | ix >= lenBuf
         = copy lenBuf

         | otherwise
         = do   !(x :: Word8) <- F.peekByteOff (pw8 start) ix
                if stop x
                 then copy      ix
                 else checkLen (ix + 1)
        {-# INLINE checkLen #-}

        -- Copy the desired bytes into a new buffer.
        copy !len
         =  F.mallocBytes len >>= \ptr
         -> let
                unpacker_VarBytes !ix
                 | ix >= len
                 = do   fptr       <- F.newForeignPtr F.finalizerFree ptr
                        let bs  =  BS.PS fptr 0 len
                        let !(Ptr start') = F.plusPtr (pw8 start) len
                        eat start' bs

                 | otherwise
                 = do   x :: Word8 <- F.peekByteOff (pw8 start) ix
                        F.pokeByteOff ptr ix x
                        unpacker_VarBytes (ix + 1)
            in  unpacker_VarBytes 0
        {-# INLINE copy #-}
 {-# INLINE unpacker #-}


pw8 :: Addr# -> Ptr Word8
pw8 addr = Ptr addr
{-# INLINE pw8 #-}