{-# LANGUAGE CPP #-}

module Network.HPACK.HeaderBlock.Encode (
    toByteString
  , toBuilder
  ) where

import Data.Bits (setBit)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString.Builder (Builder)
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Builder.Prim as P
import qualified Data.ByteString.Lazy as BL
import Data.List (foldl')
#if __GLASGOW_HASKELL__ < 709
import Data.Monoid (mempty)
#endif
import Data.Monoid ((<>))
import Data.Word (Word8)
import Network.HPACK.HeaderBlock.HeaderField
import qualified Network.HPACK.HeaderBlock.Integer as I
import qualified Network.HPACK.HeaderBlock.String as S

----------------------------------------------------------------

-- | Converting 'HeaderBlock' to the low level format.
toByteString :: Bool -> HeaderBlock -> ByteString
toByteString huff hbs = BL.toStrict $ BB.toLazyByteString $ toBuilder huff hbs

toBuilder :: Bool -> [HeaderField] -> Builder
toBuilder huff hbs = foldl' op mempty hbs
  where
    b `op` x = b <> toBB x
    toBB = fromHeaderField huff

fromHeaderField :: Bool -> HeaderField -> Builder
fromHeaderField _    (ChangeTableSize siz)        = change siz
fromHeaderField _    (Indexed idx)                = index idx
fromHeaderField huff (Literal Add    (Idx idx) v) = indexedName huff 6 set01 idx v
fromHeaderField huff (Literal Add    (Lit key) v) = newName     huff set01 key v
fromHeaderField huff (Literal NotAdd (Idx idx) v) = indexedName huff 4 set0000 idx v
fromHeaderField huff (Literal NotAdd (Lit key) v) = newName     huff set0000 key v
fromHeaderField huff (Literal Never (Idx idx) v)  = indexedName huff 4 set0001 idx v
fromHeaderField huff (Literal Never (Lit key) v)  = newName     huff set0001 key v

----------------------------------------------------------------

word8s :: [Word8] -> Builder
word8s = P.primMapListFixed P.word8

change :: Int -> Builder
change i = word8s (w':ws)
  where
    (w:ws) = I.encode 5 i
    w' = set001 w

index :: Int -> Builder
index i = word8s (w':ws)
  where
    (w:ws) = I.encode 7 i
    w' = set1 w

-- Using Huffman encoding
indexedName :: Bool -> Int -> Setter -> Int -> HeaderValue -> Builder
indexedName huff n set idx v = pre <> vlen <> val
  where
    (p:ps) = I.encode n idx
    pre = word8s $ set p : ps
    value = S.encode huff v
    valueLen = BS.length value
    vlen
      | huff      = word8s $ setH $ I.encode 7 valueLen
      | otherwise = word8s $ I.encode 7 valueLen
    val = BB.byteString value

-- Using Huffman encoding
newName :: Bool -> Setter -> HeaderName -> HeaderValue -> Builder
newName huff set k v = pre <> klen <> key <> vlen <> val
  where
    pre = BB.word8 $ set 0
    key0 = S.encode huff k
    keyLen = BS.length key0
    value = S.encode huff v
    valueLen = BS.length value
    klen
      | huff      = word8s $ setH $ I.encode 7 keyLen
      | otherwise = word8s $ I.encode 7 keyLen
    vlen
      | huff      = word8s $ setH $ I.encode 7 valueLen
      | otherwise = word8s $ I.encode 7 valueLen
    key = BB.byteString key0
    val = BB.byteString value

----------------------------------------------------------------

type Setter = Word8 -> Word8

-- Assuming MSBs are 0.
set1, set01, set001, set0001, set0000 :: Setter
set1    x = x `setBit` 7
set01   x = x `setBit` 6
set001  x = x `setBit` 5
set0001 x = x `setBit` 4
set0000 = id

setH :: [Word8] -> [Word8]
setH []     = error "setH"
setH (x:xs) = (x `setBit` 7) : xs