{-# LANGUAGE BangPatterns #-}
-- This module mostly exists to avoid cyclic dependencies
module Database.VCache.VGetAux 
    ( getWord8FromEnd
    , getWord8
    , isEmpty, vgetStateEmpty
    , getVarNat
    , getVarInt
    , consuming
    , peekByte
    ) where

import Control.Applicative
import Data.Word
import Data.Bits
import qualified Data.List as L
import Foreign.Ptr
import Foreign.Storable
import Database.VCache.Types

-- | Read one byte of data, or fail if not enough data.
getWord8 :: VGet Word8 
getWord8 = consuming 1 $ VGet $ \ s -> do
    let p = vget_target s
    r <- peekByte p
    let s' = s { vget_target = p `plusPtr` 1 }
    return (VGetR r s')
{-# INLINE getWord8 #-}

getWord8FromEnd :: VGet Word8
getWord8FromEnd = consuming 1 $ VGet $ \ s -> do
    let p = vget_limit s `plusPtr` (-1)
    r <- peekByte p
    let s' = s { vget_limit = p }
    return (VGetR r s')
{-# INLINE getWord8FromEnd #-}

-- to simplify type inference
peekByte :: Ptr Word8 -> IO Word8
peekByte = peek
{-# INLINE peekByte #-}

-- | isEmpty will return True iff there is no available input (neither
-- references nor values).
isEmpty :: VGet Bool
isEmpty = VGet $ \ s ->
    let bEOF = vgetStateEmpty s in
    bEOF `seq` return (VGetR bEOF s)
{-# INLINE isEmpty #-}

vgetStateEmpty :: VGetS -> Bool
vgetStateEmpty s = (vget_target s == vget_limit s)
                && (L.null (vget_children s))
{-# INLINE vgetStateEmpty #-}

-- | Get an integer represented in the Google protocol buffers zigzag
-- 'varint' encoding, e.g. as produced by 'putVarInt'. 
getVarInt :: VGet Integer
getVarInt = unZigZag <$> getVarNat
{-# INLINE getVarInt #-}

-- undo protocol buffers zigzag encoding
unZigZag :: Integer -> Integer
unZigZag !n =
    let (q,r) = n `divMod` 2 in
    if (1 == r) then negate q - 1
                else q
{-# INLINE unZigZag #-}

-- | Get a non-negative number represented in the Google protocol
-- buffers 'varint' encoding, e.g. as produced by 'putVarNat'.
getVarNat :: VGet Integer
getVarNat = getVarNat' 0
{-# INLINE getVarNat #-}

-- getVarNat' uses accumulator
getVarNat' :: Integer -> VGet Integer
getVarNat' !n =
    getWord8 >>= \ w ->
    let n' = (128 * n) + fromIntegral (w .&. 0x7f) in
    if (w < 128) then return $! n'
                 else getVarNat' n'


-- consuming a number of bytes (for unsafe VGet operations)
--  does not perform a full isolation
consuming :: Int -> VGet a -> VGet a
consuming n op = VGet $ \ s ->
    let pConsuming = vget_target s `plusPtr` n in
    if (pConsuming > vget_limit s) then return (VGetE "not enough data") else 
    _vget op s 
{-# RULES
"consuming.consuming"   forall n1 n2 op . consuming n1 (consuming n2 op) = consuming (max n1 n2) op
"consuming>>consuming"  forall n1 n2 f g . consuming n1 f >> consuming n2 g = consuming (n1+n2) (f>>g)
"consuming>>=consuming" forall n1 n2 f g . consuming n1 f >>= consuming n2 . g = consuming (n1+n2) (f>>=g)
 #-}
{-# INLINABLE consuming #-}