content: {-# LANGUAGE BinaryLiterals #-}
  
  module Network.QPACK.HeaderBlock.Prefix (
      -- * Prefix
      encodePrefix,
      decodePrefix,
      encodeRequiredInsertCount,
      decodeRequiredInsertCount,
      encodeBase,
      decodeBase,
  ) where
  
  import qualified Control.Exception as E
  import Network.ByteOrder
  import Network.HPACK.Internal (decodeI, encodeI)
  
  import Imports
  import Network.QPACK.Error
  import Network.QPACK.Table
  import Network.QPACK.Types
  
  ----------------------------------------------------------------
  
  -- |
  -- >>> encodeRequiredInsertCount 3 9
  -- 4
  -- >>> encodeRequiredInsertCount 128 1000
  -- 233
  encodeRequiredInsertCount :: Int -> RequiredInsertCount -> Int
  encodeRequiredInsertCount _ 0 = 0
  encodeRequiredInsertCount maxEntries (RequiredInsertCount reqInsertCount) =
      (reqInsertCount `mod` (2 * maxEntries)) + 1
  
  -- | for decoder
  --
  -- >>> decodeRequiredInsertCount 3 10 4
  -- RequiredInsertCount 9
  -- >>> decodeRequiredInsertCount 128 990 233
  -- RequiredInsertCount 1000
  decodeRequiredInsertCount
      :: Int -> InsertionPoint -> Int -> RequiredInsertCount
  decodeRequiredInsertCount _ _ 0 = 0
  decodeRequiredInsertCount 0 _ n = RequiredInsertCount (n - 1)
  decodeRequiredInsertCount maxEntries (InsertionPoint totalNumberOfInserts) encodedInsertCount
      | encodedInsertCount > fullRange = E.throw IllegalInsertCount
      | reqInsertCount > maxValue && reqInsertCount <= fullRange =
          E.throw IllegalInsertCount
      | reqInsertCount > maxValue = RequiredInsertCount (reqInsertCount - fullRange)
      | otherwise = RequiredInsertCount reqInsertCount
    where
      fullRange = 2 * maxEntries
      maxValue = totalNumberOfInserts + maxEntries
      maxWrapped = (maxValue `div` fullRange) * fullRange
      reqInsertCount = maxWrapped + encodedInsertCount - 1
  
  ----------------------------------------------------------------
  
  -- |
  -- >>> encodeBase 6 9
  -- (False,3)
  -- >>> encodeBase 9 6
  -- (True,2)
  encodeBase :: RequiredInsertCount -> BasePoint -> (Bool, Int)
  encodeBase (RequiredInsertCount reqInsCnt) (BasePoint base)
      | diff >= 0 = (False, diff) -- base - reqInsCnt
      | otherwise = (True, negate diff - 1) -- reqInsCnt - base - 1
    where
      diff = base - reqInsCnt
  
  -- |
  -- >>> decodeBase 6 False 3
  -- BasePoint 9
  -- >>> decodeBase 9 True 2
  -- BasePoint 6
  decodeBase :: RequiredInsertCount -> Bool -> Int -> BasePoint
  decodeBase (RequiredInsertCount reqInsCnt) False deltaBase = BasePoint (reqInsCnt + deltaBase)
  decodeBase (RequiredInsertCount reqInsCnt) True deltaBase = BasePoint (reqInsCnt - deltaBase - 1)
  
  ----------------------------------------------------------------
  
  -- | Encoding the prefix part of header block.
  --   This should be used after 'encodeTokenHeader'.
  encodePrefix :: WriteBuffer -> DynamicTable -> IO ()
  encodePrefix wbuf dyntbl = do
      clearWriteBuffer wbuf
      maxEntries <- getMaxNumOfEntries dyntbl
      baseIndex <- getBasePoint dyntbl
      reqInsCnt <- getRequiredInsertCount dyntbl
      qpackDebug dyntbl $ print reqInsCnt
      -- Required Insert Count
      let ric = encodeRequiredInsertCount maxEntries reqInsCnt
      encodeI wbuf set0 8 ric
      -- Sign bit + Delta Base (7+)
      let (s, base) = encodeBase reqInsCnt baseIndex
          set
              | s = set1
              | otherwise = set0
      encodeI wbuf set 7 base
  
  -- | Decoding the prefix part of header block.
  decodePrefix
      :: ReadBuffer -> DynamicTable -> IO (RequiredInsertCount, BasePoint, Bool)
  decodePrefix rbuf dyntbl = do
      maxEntries <- getMaxNumOfEntries dyntbl
      totalNumberOfInserts <- getInsertionPoint dyntbl
      w8 <- read8 rbuf
      ric <- decodeI 8 w8 rbuf
      let reqInsCnt = decodeRequiredInsertCount maxEntries totalNumberOfInserts ric
      w8' <- read8 rbuf
      let s = w8' `testBit` 7
          w8'' = w8' .&. 0b01111111
      delta <- decodeI 7 w8'' rbuf
      let baseIndex = decodeBase reqInsCnt s delta
      qpackDebug dyntbl $
          putStrLn $
              show reqInsCnt
                  ++ ", "
                  ++ show baseIndex
                  ++ ", "
                  ++ show totalNumberOfInserts
                  ++ ", maxN "
                  ++ show maxEntries
      let needAck = reqInsCnt /= 0
      return (reqInsCnt, baseIndex, needAck)
  