{-# LANGUAGE BangPatterns #-}

module Database.VCache.VGetInit
    ( vgetInit
    ) where

import Data.Bits
import Foreign.Ptr
import Database.VCache.Types
import Database.VCache.VGetAux

-- | For VGet from the database, we start with just a pointer and a
-- size. To process the VGet data, we also need to read addresses 
-- from a dedicated region. This is encoded from the end, as follows:
--
--     (normal data) addressN offset offset offset offset ... bytes
--                                                           
-- Here 'bytes' is basically a varNat encoded backwards for the
-- number of bytes (not counting 'bytes') back to the start of the
-- first address. This address is then encoded as a varNat, and any
-- offset is encoded as a varInt with the idea of reducing overhead
-- for encoding addresses near to each other in memory.
--
-- Addresses are encoded such that the first address to parse is last
-- in the sequence (thereby avoiding a list reverse operation).
--
-- To read addresses, we simply read the number of bytes from the end,
-- step back that far, then read the initial address and offsets until
-- we get back to the end. This must be performed before we apply the
-- normal read operation for the VGet state. It must be applied exactly
-- once for a given input.
--
vgetInit :: VGet ()
vgetInit =
    readAddrBytes >>= \ nAddrBytes ->
    if (0 == nAddrBytes) then return () else
    VGet $ \ s -> 
        let bUnderflow = nAddrBytes > (vget_limit s `minusPtr` vget_target s) in 
        if bUnderflow then return eBadAddressRegion else 
        let pAddrs = vget_limit s `plusPtr` negate nAddrBytes in
        let sAddrs = s { vget_target = pAddrs } in
        _vget readAddrs sAddrs >>= \ mbAddrs ->
        case mbAddrs of
            VGetR addrs _ ->
                let s' = s { vget_children = addrs, vget_limit = pAddrs } in
                return (VGetR () s')
            VGetE eMsg -> return (VGetE eMsg)
{-# INLINABLE vgetInit #-}

eBadAddressRegion :: VGetR a
eBadAddressRegion = VGetE "VGet: failed to read address region"

readAddrBytes :: VGet Int
readAddrBytes = readAddrBytes' 0
{-# INLINE readAddrBytes #-}

readAddrBytes' :: Int -> VGet Int
readAddrBytes' !nAccum = 
    getWord8FromEnd >>= \ w8 ->
    let nAccum' = (nAccum `shiftL` 7) .|. (fromIntegral (0x7f .&. w8)) in
    if (w8 < 0x80) then return $! nAccum' else
    readAddrBytes' nAccum'

-- read a variable list of at least one address
readAddrs :: VGet [Address]
readAddrs = 
    getVarNat >>= \ nFirst ->
    let addr0 = fromIntegral nFirst in
    addr0 `seq` readAddrs' [addr0] nFirst

-- read address offsets until end of input
readAddrs' :: [Address] -> Integer -> VGet [Address]
readAddrs' addrs !nLast =
    isEmpty >>= \ bEmpty ->
    if bEmpty then return addrs else
    getVarInt >>= \ nOff ->
    let nCurr = nLast + nOff in
    let addr = fromIntegral nCurr in
    addr `seq` readAddrs' (addr:addrs) nCurr