{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE OverloadedStrings #-}

module Network.QPACK.Instruction (
    -- * Encoder instructions
    HIndex (..),
    EncoderInstruction (..),
    InsIndex,
    encodeEncoderInstructions,
    decodeEncoderInstructions,
    decodeEncoderInstructions',
    encodeEI,
    decodeEI,

    -- * Decoder instructions
    DecoderInstruction (..),
    encodeDecoderInstructions,
    decodeDecoderInstructions,
    encodeDI,
    decodeDI,
) where

import qualified Data.ByteString.Char8 as BS8
import Data.CaseInsensitive
import Network.ByteOrder
import Network.HPACK.Internal
import Network.HPACK.Token
import qualified UnliftIO.Exception as E

import Imports
import Network.QPACK.Table.Static
import Network.QPACK.Types

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

type InsIndex = Either AbsoluteIndex InsRelativeIndex

data EncoderInstruction
    = SetDynamicTableCapacity Int
    | InsertWithNameReference InsIndex HeaderValue
    | InsertWithoutNameReference Token HeaderValue
    | Duplicate InsRelativeIndex
    deriving (Eq)

instance Show EncoderInstruction where
    show (SetDynamicTableCapacity n) = "SetDynamicTableCapacity " ++ show n
    show (InsertWithNameReference (Left aidx) v) =
        "InsertWithNameReference \""
            ++ BS8.unpack (entryHeaderName (toStaticEntry aidx))
            ++ "\" \""
            ++ BS8.unpack v
            ++ "\""
    show (InsertWithNameReference (Right (InsRelativeIndex idx)) v) =
        "InsertWithNameReference (DynRel " ++ show idx ++ ") \"" ++ BS8.unpack v ++ "\""
    show (InsertWithoutNameReference t v) =
        "InsertWithoutNameReference \""
            ++ BS8.unpack (foldedCase (tokenKey t))
            ++ "\" \""
            ++ BS8.unpack v
            ++ "\""
    show (Duplicate (InsRelativeIndex idx)) = "Duplicate (DynRel " ++ show idx ++ ")"

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

encodeEncoderInstructions :: [EncoderInstruction] -> Bool -> IO ByteString
encodeEncoderInstructions eis huff = withWriteBuffer 4096 $ \wbuf ->
    mapM_ (encodeEI wbuf huff) eis

encodeEI :: WriteBuffer -> Bool -> EncoderInstruction -> IO ()
encodeEI wbuf _ (SetDynamicTableCapacity cap) = encodeI wbuf set001 5 cap
encodeEI wbuf huff (InsertWithNameReference hidx v) = do
    let (set, idx) = case hidx of
            Left (AbsoluteIndex i) -> (set11, i)
            Right (InsRelativeIndex i) -> (set1, i)
    encodeI wbuf set 6 idx
    encodeS wbuf huff id set1 7 v
encodeEI wbuf huff (InsertWithoutNameReference k v) = do
    encodeS wbuf huff set01 set001 5 $ foldedCase $ tokenKey k
    encodeS wbuf huff id set1 7 v
encodeEI wbuf _ (Duplicate (InsRelativeIndex idx)) = encodeI wbuf set000 5 idx

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

decodeEncoderInstructions'
    :: ByteString -> IO ([EncoderInstruction], ByteString)
decodeEncoderInstructions' bs = do
    let bufsiz = 4096
    gcbuf <- mallocPlainForeignPtrBytes 4096
    decodeEncoderInstructions (decodeH gcbuf bufsiz) bs

decodeEncoderInstructions
    :: HuffmanDecoder -> ByteString -> IO ([EncoderInstruction], ByteString)
decodeEncoderInstructions hufdec bs = withReadBuffer bs $ loop id
  where
    loop build rbuf = do
        n <- remainingSize rbuf
        if n == 0
            then do
                let eis = build []
                return (eis, "")
            else do
                save rbuf
                er <- E.try $ decodeEI hufdec rbuf
                case er of
                    Right r -> loop (build . (r :)) rbuf
                    Left BufferOverrun -> do
                        goBack rbuf
                        rn <- remainingSize rbuf
                        left <- extractByteString rbuf rn
                        let eis = build []
                        return (eis, left)

decodeEI :: HuffmanDecoder -> ReadBuffer -> IO EncoderInstruction
decodeEI hufdec rbuf = do
    w8 <- read8 rbuf
    if w8 `testBit` 7
        then decodeInsertWithNameReference rbuf w8 hufdec
        else
            if w8 `testBit` 6
                then decodeInsertWithoutNameReference rbuf hufdec
                else
                    if w8 `testBit` 5
                        then decodeSetDynamicTableCapacity rbuf w8
                        else decodeDuplicate rbuf w8

decodeInsertWithNameReference
    :: ReadBuffer -> Word8 -> HuffmanDecoder -> IO EncoderInstruction
decodeInsertWithNameReference rbuf w8 hufdec = do
    idx <- decodeI 6 (w8 .&. 0b00111111) rbuf
    let hidx
            | w8 `testBit` 6 = Left (AbsoluteIndex idx)
            | otherwise = Right (InsRelativeIndex idx)
    v <- decodeS (.&. 0b01111111) (`testBit` 7) 7 hufdec rbuf
    return $ InsertWithNameReference hidx v

decodeInsertWithoutNameReference
    :: ReadBuffer -> HuffmanDecoder -> IO EncoderInstruction
decodeInsertWithoutNameReference rbuf hufdec = do
    ff rbuf (-1)
    k <- decodeS (.&. 0b00011111) (`testBit` 5) 5 hufdec rbuf
    v <- decodeS (.&. 0b01111111) (`testBit` 7) 7 hufdec rbuf
    return $ InsertWithoutNameReference (toToken k) v

decodeSetDynamicTableCapacity :: ReadBuffer -> Word8 -> IO EncoderInstruction
decodeSetDynamicTableCapacity rbuf w8 =
    SetDynamicTableCapacity <$> decodeI 5 (w8 .&. 0b00011111) rbuf

decodeDuplicate :: ReadBuffer -> Word8 -> IO EncoderInstruction
decodeDuplicate rbuf w8 =
    Duplicate . InsRelativeIndex <$> decodeI 5 (w8 .&. 0b00011111) rbuf

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

data DecoderInstruction
    = SectionAcknowledgement Int
    | StreamCancellation Int
    | InsertCountIncrement Int
    deriving (Eq, Show)

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

encodeDecoderInstructions :: [DecoderInstruction] -> IO ByteString
encodeDecoderInstructions dis = withWriteBuffer 4096 $ \wbuf ->
    mapM_ (encodeDI wbuf) dis

encodeDI :: WriteBuffer -> DecoderInstruction -> IO ()
encodeDI wbuf (SectionAcknowledgement n) = encodeI wbuf set1 7 n
encodeDI wbuf (StreamCancellation n) = encodeI wbuf set01 6 n
encodeDI wbuf (InsertCountIncrement n) = encodeI wbuf id 6 n

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

decodeDecoderInstructions :: ByteString -> IO ([DecoderInstruction], ByteString)
decodeDecoderInstructions bs = withReadBuffer bs $ loop id
  where
    loop build rbuf = do
        n <- remainingSize rbuf
        if n == 0
            then do
                let dis = build []
                return (dis, "")
            else do
                save rbuf
                er <- E.try $ decodeDI rbuf
                case er of
                    Right r -> loop (build . (r :)) rbuf
                    Left BufferOverrun -> do
                        goBack rbuf
                        rn <- remainingSize rbuf
                        left <- extractByteString rbuf rn
                        let dis = build []
                        return (dis, left)

decodeDI :: ReadBuffer -> IO DecoderInstruction
decodeDI rbuf = do
    w8 <- read8 rbuf
    if w8 `testBit` 7
        then SectionAcknowledgement <$> decodeI 7 (w8 .&. 0b01111111) rbuf
        else do
            i <- decodeI 6 (w8 .&. 0b00111111) rbuf
            return $ if w8 `testBit` 6 then StreamCancellation i else InsertCountIncrement i
