{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}

-- | A client library for HTTP/3.
module Network.HTTP3.Client (
    -- * Runner
    run,

    -- * Runner arguments
    ClientConfig (..),
    Config (..),
    allocSimpleConfig,
    freeSimpleConfig,
    Hooks (..),
    defaultHooks,
    Scheme,
    Authority,

    -- * HTTP\/3 client
    Client,
    SendRequest,
    Aux,

    -- * Request
    Request,

    -- * Creating request
    H2.requestNoBody,
    H2.requestFile,
    H2.requestStreaming,
    H2.requestBuilder,

    -- ** Trailers maker
    H2.TrailersMaker,
    H2.NextTrailersMaker (..),
    H2.defaultTrailersMaker,
    H2.setRequestTrailersMaker,

    -- * Response
    Response,

    -- ** Accessing response
    H2.responseStatus,
    H2.responseHeaders,
    H2.responseBodySize,
    H2.getResponseBodyChunk,
    H2.getResponseTrailers,

    -- * Types
    H2.Method,
    H2.Path,
    H2.FileSpec (..),
    H2.FileOffset,
    H2.ByteCount,

    -- * RecvN
    H2.defaultReadN,

    -- * Position read for files
    H2.PositionReadMaker,
    H2.PositionRead,
    H2.Sentinel (..),
    H2.defaultPositionReadMaker,
) where

import Control.Concurrent
import qualified Data.ByteString.Char8 as C8
import Data.IORef
import Network.HTTP2.Client (Authority, Client, Scheme, SendRequest)
import qualified Network.HTTP2.Client as H2
import Network.HTTP2.Client.Internal (Aux (..), Request (..), Response (..))
import Network.HTTP2.Internal (InpObj (..))
import qualified Network.HTTP2.Internal as H2
import Network.QUIC (Connection)
import qualified Network.QUIC as QUIC
import Network.QUIC.Internal (possibleMyStreams)
import qualified UnliftIO.Exception as E

import Network.HTTP3.Config
import Network.HTTP3.Context
import Network.HTTP3.Control
import Network.HTTP3.Error
import Network.HTTP3.Frame
import Network.HTTP3.Recv
import Network.HTTP3.Send

-- | Configuration for HTTP\/3 or HQ client. For HQ, 'authority' is
--   not used and an server's IP address is used in 'Request'.
data ClientConfig = ClientConfig
    { scheme :: Scheme
    , authority :: Authority
    }

-- | Running an HTTP\/3 client.
run :: Connection -> ClientConfig -> Config -> H2.Client a -> IO a
run conn ClientConfig{..} conf client = E.bracket open close $ \ctx -> do
    tid0 <- forkIO $ setupUnidirectional conn conf
    addThreadId ctx tid0
    tid1 <- forkIO $ readerClient ctx
    addThreadId ctx tid1
    client (sendRequest ctx scheme authority) aux
  where
    open = do
        ref <- newIORef IInit
        newContext conn conf (controlStream conn ref)
    close = clearContext
    aux =
        Aux
            { auxPossibleClientStreams = possibleMyStreams conn
            }

readerClient :: Context -> IO ()
readerClient ctx = loop
  where
    loop = do
        accept ctx >>= process
        loop
    process strm
        | QUIC.isClientInitiatedUnidirectional sid = return () -- error
        | QUIC.isClientInitiatedBidirectional sid = return ()
        | QUIC.isServerInitiatedUnidirectional sid = do
            tid <- forkIO $ unidirectional ctx strm
            addThreadId ctx tid
        | otherwise = return () -- push?
      where
        sid = QUIC.streamId strm

sendRequest
    :: Context -> Scheme -> Authority -> Request -> (Response -> IO a) -> IO a
sendRequest ctx scm auth (Request outobj) processResponse = do
    th <- registerThread ctx
    let hdr = H2.outObjHeaders outobj
        hdr' =
            (":scheme", scm)
                : (":authority", C8.pack auth)
                : hdr
    E.bracket (newStream ctx) closeStream $ \strm -> do
        sendHeader ctx strm th hdr'
        tid <- forkIO $ do
            sendBody ctx strm th outobj
            QUIC.shutdownStream strm
        addThreadId ctx tid
        src <- newSource strm
        mvt <- recvHeader ctx src
        case mvt of
            Nothing -> do
                QUIC.resetStream strm H3MessageError
                threadDelay 100000
                -- just for type inference
                E.throwIO $ QUIC.ApplicationProtocolErrorIsSent H3MessageError ""
            Just vt -> do
                refI <- newIORef IInit
                refH <- newIORef Nothing
                let readB = recvBody ctx src refI refH
                    rsp = Response $ InpObj vt Nothing readB refH
                processResponse rsp
