content: {-# LANGUAGE BinaryLiterals #-}
  
  module Network.QUIC.Types.Integer (
      encodeInt,
      encodeInt8,
      encodeInt',
      encodeInt'2,
      encodeInt'4,
      decodeInt,
      decodeInt',
  ) where
  
  import Data.ByteString.Internal (unsafeCreate)
  import Foreign.Ptr
  import Foreign.Storable
  import System.IO.Unsafe (unsafeDupablePerformIO)
  
  import Network.QUIC.Imports
  
  -- $setup
  -- >>> :set -XOverloadedStrings
  -- >>> import Network.QUIC.Utils
  
  ----------------------------------------------------------------
  
  -- |
  -- >>> enc16 $ encodeInt 151288809941952652
  -- "c2197c5eff14e88c"
  -- >>> enc16 $ encodeInt 494878333
  -- "9d7f3e7d"
  -- >>> enc16 $ encodeInt 15293
  -- "7bbd"
  -- >>> enc16 $ encodeInt 37
  -- "25"
  encodeInt :: Int64 -> ByteString
  encodeInt i = unsafeCreate n $ go tag n i'
    where
      (tag, n, i') = tagLen i
  {-# NOINLINE encodeInt #-}
  
  encodeInt8 :: Int64 -> ByteString
  encodeInt8 i = unsafeCreate n $ go tag n i
    where
      n = 8
      tag = 0b11000000
  {-# NOINLINE encodeInt8 #-}
  
  encodeInt' :: WriteBuffer -> Int64 -> IO ()
  encodeInt' wbuf i = go' tag n i' wbuf
    where
      (tag, n, i') = tagLen i
  
  encodeInt'2 :: WriteBuffer -> Int64 -> IO ()
  encodeInt'2 wbuf i = go' tag n i' wbuf
    where
      tag = 0b01000000
      n = 2
      i' = i !<<. 48
  
  encodeInt'4 :: WriteBuffer -> Int64 -> IO ()
  encodeInt'4 wbuf i = go' tag n i' wbuf
    where
      tag = 0b10000000
      n = 4
      i' = i !<<. 32
  
  tagLen :: Int64 -> (Word8, Int, Int64)
  tagLen i
      | i <= 63 = (0b00000000, 1, i !<<. 56)
      | i <= 16383 = (0b01000000, 2, i !<<. 48)
      | i <= 1073741823 = (0b10000000, 4, i !<<. 32)
      | otherwise = (0b11000000, 8, i)
  {-# INLINE tagLen #-}
  
  msb8 :: Int64 -> Word8
  msb8 i = fromIntegral (i !>>. 56)
  {-# INLINE msb8 #-}
  
  go :: Word8 -> Int -> Int64 -> Ptr Word8 -> IO ()
  go tag n0 i0 p0 = do
      poke p0 (tag .|. msb8 i0)
      let n' = n0 - 1
          i' = i0 !<<. 8
          p' = p0 `plusPtr` 1
      loop n' i' p'
    where
      loop 0 _ _ = return ()
      loop n i p = do
          poke p $ msb8 i
          let n' = n - 1
              i' = i !<<. 8
              p' = p `plusPtr` 1
          loop n' i' p'
  {-# INLINE go #-}
  
  go' :: Word8 -> Int -> Int64 -> WriteBuffer -> IO ()
  go' tag n0 i0 wbuf = do
      write8 wbuf (tag .|. msb8 i0)
      let n' = n0 - 1
          i' = i0 !<<. 8
      loop n' i'
    where
      loop 0 _ = return ()
      loop n i = do
          write8 wbuf $ msb8 i
          let n' = n - 1
              i' = i !<<. 8
          loop n' i'
  {-# INLINE go' #-}
  
  ----------------------------------------------------------------
  
  -- |
  -- >>> decodeInt (dec16 "c2197c5eff14e88c")
  -- 151288809941952652
  -- >>> decodeInt (dec16 "9d7f3e7d")
  -- 494878333
  -- >>> decodeInt (dec16 "7bbd")
  -- 15293
  -- >>> decodeInt (dec16 "25")
  -- 37
  decodeInt :: ByteString -> Int64
  decodeInt bs = unsafeDupablePerformIO $ withReadBuffer bs decodeInt'
  {-# NOINLINE decodeInt #-}
  
  decodeInt' :: ReadBuffer -> IO Int64
  decodeInt' rbuf = do
      b0 <- read8 rbuf
      let flag = b0 !>>. 6
          b1 = fromIntegral (b0 .&. 0b00111111)
      case flag of
          0 -> return b1
          1 -> loop b1 1
          2 -> loop b1 3
          _ -> loop b1 7
    where
      loop :: Int64 -> Int -> IO Int64
      loop r 0 = return r
      loop r n = do
          b <- fromIntegral <$> read8 rbuf
          loop (r * 256 + b) (n - 1)
  