-- | The network stream header design is inspired by the variable length
-- integers used in Googles Protocol Buffers (protobuf): Each stream block is
-- preceded by a binary header of variable length. There are currently 3 types
-- of headers:
--
--  * The beginning of a conduit block is encoded as @[0, MSB]@
--  * The end of a conduit block is encoded as @[1, MSB]@
--  * Length definition of the current block, encoded as variable length integer
--
-- Each header byte consists of 7 bits + the "most significant bit". For
-- example, the number 256 in its binary representation is:
--
-- > 1 0000 0000
--
-- First, split the number into 7-bit packages (add 0s to the front if
-- necessary):
--
-- > 0000 010   0000 000
--
-- These 7-bit packages are then reversed in order and encoded as 8-bit bytes
-- where the last bit marks the end of our header if set, for example:
--
-- > 0000 000   0000 010   --  reverse order
-- > 0000 0000  0000 0101  --  set 8th bit
-- >         _          X  --  (unset = _, set = X):
--
-- Another example would be the representation of the number 65536, or
--
-- > 1 0000 0000 0000 0000
--
-- as
--
-- > 0000 100   0000 000   0000 000   --  7 bit packages
-- > 0000 000   0000 000   0000 100   --  reverse order
-- > 0000 0000  0000 0000  0000 1001  --  set 8th bit
-- >         _          _          X
--
-- Distinction between sepcial headers (such as the start or end of conduit
-- blocks) and regular variable length integers is done by checking the most
-- significant (i.e. last) byte. In a variable length integer, the first 7 bits
-- of the MSB are always bigger than 0, while special headers are ended by the
-- byte "00000001":
--
-- > 0000 0000  0000 0001  --  special header: conduit block start
-- > 0000 0010  0000 0001  --  special header: conduit block end
-- > 0000 0011             --  block of length 1
-- > 0000 0101             --  block of length 2
-- > 0000 1001             --  block of length 4
-- > 1111 1111             --  block of length 127
-- > 0000 0000  0000 0011  --  block of length 128 (note the 7 bit shift)
-- > 0000 0010  0000 0011  --  block of length 129

module Data.Conduit.Network.Stream.Header where

import Data.Bits
--import Data.Enum
import Data.Word
import Data.Conduit
-- import Data.Conduit.Network.Stream.Exceptions

import qualified Data.Conduit.Binary  as CB
import qualified Data.ByteString      as BS
--import qualified Data.ByteString.Lazy as BL

data Header
  = ConduitSTART
  | ConduitEND
  | VarInt Int
  | InvalidHeader [Word8]
  | EndOfInput
  deriving (Show)

-- | Make the current \"7-bit byte\" the most significant byte in the header
mkMSB :: Word8 -> Word8
mkMSB = setBit `flip` 7

specialHeaderMSB :: Word8
specialHeaderMSB = mkMSB 0

-- | Test wether or not a byte is the most significant byte of a special header
-- (i.e. 8th bit = 1, rest = 0)
isSpecialHeaderMSB :: Word8 -> Bool
isSpecialHeaderMSB = (specialHeaderMSB ==)

-- | Test wether or not a byte is the most significant byte (i.e. the last byte
-- of a header block)
isMSB :: Word8 -> Bool
isMSB = testBit `flip` 7

varint :: (Show int, Integral int) => int -> [Word8]
varint int = go (fromIntegral int :: Integer) []
 where
  go i l =
    let w8 = fromIntegral $ 127 .&. i -- take the first 7 bits
        r  = shiftR i 7               -- shift the rest of the bits to the right
     in if r == 0
           then l ++ [mkMSB w8]
           else go r (l ++ [w8])

fromVarint :: (Show int, Integral int, Bits int) => [Word8] -> int
fromVarint []    = 0
fromVarint [x]   = fromIntegral $ x `clearBit` 7
fromVarint (w:r) = fromIntegral w + shiftL (fromVarint r) 7

condStart, condEnd :: [Word8]
condStart = [0, mkMSB 0]
condEnd   = [1, mkMSB 0]

-- | A decode 'ByteString' sink which returns the current header
decodeHeader :: Monad m => Consumer BS.ByteString m Header
decodeHeader = go []
 where
  go w8s = do
    h <- CB.head
    case h of
         Nothing -> return EndOfInput
         Just w8 | isSpecialHeaderMSB w8 -> spec w8s
                 | isMSB w8              -> var  (w8s ++ [w8])
                 | otherwise             -> go   (w8s ++ [w8])

  -- special header decoding
  spec [0] = return ConduitSTART
  spec [1] = return ConduitEND
  spec w8s = return $ InvalidHeader w8s

  -- var int decoding
  var  vi  = return $ VarInt (fromVarint vi)

encodeHeader :: Header -> Maybe BS.ByteString
encodeHeader ConduitSTART = Just $ BS.pack condStart
encodeHeader ConduitEND   = Just $ BS.pack condEnd
encodeHeader (VarInt vi)  = Just $ BS.pack (varint vi)
encodeHeader _            = Nothing