content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  
  module Network.HTTP3.Recv (
      Source,
      newSource,
      readSource,
      readSource',
      recvHeader,
      recvBody,
  ) where
  
  import qualified Data.ByteString as BS
  import Data.IORef
  import Network.QUIC
  
  import Imports
  import Network.HTTP3.Context
  import Network.HTTP3.Error
  import Network.HTTP3.Frame
  
  data Source = Source
      { sourceRead :: IO ByteString
      , sourcePending :: IORef (Maybe ByteString)
      }
  
  newSource :: Stream -> IO Source
  newSource strm = Source (recvStream strm 1024) <$> newIORef Nothing
  
  readSource :: Source -> IO ByteString
  readSource Source{..} = do
      mx <- readIORef sourcePending
      case mx of
          Nothing -> sourceRead
          Just x -> do
              writeIORef sourcePending Nothing
              return x
  
  readSource' :: Source -> IO (ByteString, Bool)
  readSource' src = do
      x <- readSource src
      return $ if x == "" then (x, True) else (x, False)
  
  pushbackSource :: Source -> ByteString -> IO ()
  pushbackSource _ "" = return ()
  pushbackSource Source{..} bs = writeIORef sourcePending $ Just bs
  
  recvHeader :: Context -> StreamId -> Source -> IO (Maybe TokenHeaderTable)
  recvHeader ctx sid src = loop IInit
    where
      loop st = do
          bs <- readSource src
          if bs == ""
              then return Nothing
              else case parseH3Frame st bs of
                  IDone typ payload leftover
                      | typ == H3FrameHeaders -> do
                          pushbackSource src leftover
                          Just <$> qpackDecode ctx sid payload
                      | typ == H3FrameData -> do
                          abort ctx H3FrameUnexpected
                          loop IInit -- dummy
                      | permittedInRequestStream typ -> do
                          pushbackSource src leftover
                          loop IInit
                      | otherwise -> do
                          abort ctx H3FrameUnexpected
                          loop IInit -- dummy
                  st' -> loop st'
  
  recvBody
      :: Context
      -> StreamId
      -> Source
      -> IORef IFrame
      -> IORef (Maybe TokenHeaderTable)
      -> IO (ByteString, Bool)
  recvBody ctx sid src refI refH = do
      st <- readIORef refI
      loop st
    where
      loop st = do
          bs <- readSource src
          if bs == ""
              then return ("", True)
              else case parseH3Frame st bs of
                  IPay H3FrameData siz received bss -> do
                      let st' = IPay H3FrameData siz received []
                      if null bss
                          then loop st'
                          else do
                              writeIORef refI st'
                              let ret = BS.concat $ reverse bss
                              return (ret, False)
                  IDone typ payload leftover
                      | typ == H3FrameHeaders -> do
                          writeIORef refI IInit
                          -- pushbackSource src leftover -- fixme
                          hdr <- qpackDecode ctx sid payload
                          writeIORef refH $ Just hdr
                          return ("", True)
                      | typ == H3FrameData -> do
                          writeIORef refI IInit
                          pushbackSource src leftover
                          return (payload, False)
                      | permittedInRequestStream typ -> do
                          pushbackSource src leftover
                          loop IInit
                      | otherwise -> do
                          abort ctx H3FrameUnexpected
                          return (payload, False) -- dummy
                  st' -> loop st'
  