{-# LANGUAGE OverloadedStrings #-}

-- | HTTP\/2 client library.
--
--  Example:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- > module Main where
-- >
-- > import qualified Control.Exception as E
-- > import Control.Concurrent (forkIO, threadDelay)
-- > import qualified Data.ByteString.Char8 as C8
-- > import Network.HTTP.Types
-- > import Network.Run.TCP (runTCPClient) -- network-run
-- >
-- > import Network.HTTP2.Client
-- >
-- > serverName :: String
-- > serverName = "127.0.0.1"
-- >
-- > main :: IO ()
-- > main = runTCPClient serverName "80" runHTTP2Client
-- >   where
-- >     cliconf = ClientConfig "http" (C8.pack serverName) 20
-- >     runHTTP2Client s = E.bracket (allocSimpleConfig s 4096)
-- >                                  freeSimpleConfig
-- >                                  (\conf -> run cliconf conf client)
-- >     client sendRequest = do
-- >         let req = requestNoBody methodGet "/" []
-- >         _ <- forkIO $ sendRequest req $ \rsp -> do
-- >             print rsp
-- >             getResponseBodyChunk rsp >>= C8.putStrLn
-- >         sendRequest req $ \rsp -> do
-- >             threadDelay 100000
-- >             print rsp
-- >             getResponseBodyChunk rsp >>= C8.putStrLn

module Network.HTTP2.Client (
  -- * Runner
    run
  , Scheme
  , Authority
  -- * Runner arguments
  , ClientConfig(..)
  , Config(..)
  , allocSimpleConfig
  , freeSimpleConfig
  -- * HTTP\/2 client
  , Client
  -- * Request
  , Request
  -- * Creating request
  , requestNoBody
  , requestFile
  , requestStreaming
  , requestBuilder
  -- ** Trailers maker
  , TrailersMaker
  , NextTrailersMaker(..)
  , defaultTrailersMaker
  , setRequestTrailersMaker
  -- * Response
  , Response
  -- ** Accessing response
  , responseStatus
  , responseHeaders
  , responseBodySize
  , getResponseBodyChunk
  , getResponseTrailers
  -- * Types
  , Method
  , Path
  , FileSpec(..)
  , FileOffset
  , ByteCount
  -- * RecvN
  , defaultReadN
  -- * Position read for files
  , PositionReadMaker
  , PositionRead
  , Sentinel(..)
  , defaultPositionReadMaker
  ) where

import Data.ByteString (ByteString)
import Data.ByteString.Builder (Builder)
import Data.IORef (readIORef)
import Network.HTTP.Types

import Network.HPACK
import Network.HTTP2.Arch
import Network.HTTP2.Client.Types
import Network.HTTP2.Client.Run

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

-- | Creating request without body.
requestNoBody :: Method -> Path -> RequestHeaders -> Request
requestNoBody :: Method -> Method -> RequestHeaders -> Request
requestNoBody Method
m Method
p RequestHeaders
hdr = OutObj -> Request
Request (OutObj -> Request) -> OutObj -> Request
forall a b. (a -> b) -> a -> b
$ RequestHeaders -> OutBody -> TrailersMaker -> OutObj
OutObj RequestHeaders
hdr' OutBody
OutBodyNone TrailersMaker
defaultTrailersMaker
  where
    hdr' :: RequestHeaders
hdr' = Method -> Method -> RequestHeaders -> RequestHeaders
addHeaders Method
m Method
p RequestHeaders
hdr

-- | Creating request with file.
requestFile :: Method -> Path -> RequestHeaders -> FileSpec -> Request
requestFile :: Method -> Method -> RequestHeaders -> FileSpec -> Request
requestFile Method
m Method
p RequestHeaders
hdr FileSpec
fileSpec = OutObj -> Request
Request (OutObj -> Request) -> OutObj -> Request
forall a b. (a -> b) -> a -> b
$ RequestHeaders -> OutBody -> TrailersMaker -> OutObj
OutObj RequestHeaders
hdr' (FileSpec -> OutBody
OutBodyFile FileSpec
fileSpec) TrailersMaker
defaultTrailersMaker
  where
    hdr' :: RequestHeaders
hdr' = Method -> Method -> RequestHeaders -> RequestHeaders
addHeaders Method
m Method
p RequestHeaders
hdr

-- | Creating request with builder.
requestBuilder :: Method -> Path -> RequestHeaders -> Builder -> Request
requestBuilder :: Method -> Method -> RequestHeaders -> Builder -> Request
requestBuilder Method
m Method
p RequestHeaders
hdr Builder
builder = OutObj -> Request
Request (OutObj -> Request) -> OutObj -> Request
forall a b. (a -> b) -> a -> b
$ RequestHeaders -> OutBody -> TrailersMaker -> OutObj
OutObj RequestHeaders
hdr' (Builder -> OutBody
OutBodyBuilder Builder
builder) TrailersMaker
defaultTrailersMaker
  where
    hdr' :: RequestHeaders
hdr' = Method -> Method -> RequestHeaders -> RequestHeaders
addHeaders Method
m Method
p RequestHeaders
hdr

-- | Creating request with streaming.
requestStreaming :: Method -> Path -> RequestHeaders
                 -> ((Builder -> IO ()) -> IO () -> IO ())
                 -> Request
requestStreaming :: Method
-> Method
-> RequestHeaders
-> ((Builder -> IO ()) -> IO () -> IO ())
-> Request
requestStreaming Method
m Method
p RequestHeaders
hdr (Builder -> IO ()) -> IO () -> IO ()
strmbdy = OutObj -> Request
Request (OutObj -> Request) -> OutObj -> Request
forall a b. (a -> b) -> a -> b
$ RequestHeaders -> OutBody -> TrailersMaker -> OutObj
OutObj RequestHeaders
hdr' (((Builder -> IO ()) -> IO () -> IO ()) -> OutBody
OutBodyStreaming (Builder -> IO ()) -> IO () -> IO ()
strmbdy) TrailersMaker
defaultTrailersMaker
  where
    hdr' :: RequestHeaders
hdr' = Method -> Method -> RequestHeaders -> RequestHeaders
addHeaders Method
m Method
p RequestHeaders
hdr


addHeaders :: Method -> Path -> RequestHeaders -> RequestHeaders
addHeaders :: Method -> Method -> RequestHeaders -> RequestHeaders
addHeaders Method
m Method
p RequestHeaders
hdr = (HeaderName
":method", Method
m) (HeaderName, Method) -> RequestHeaders -> RequestHeaders
forall a. a -> [a] -> [a]
: (HeaderName
":path", Method
p) (HeaderName, Method) -> RequestHeaders -> RequestHeaders
forall a. a -> [a] -> [a]
: RequestHeaders
hdr

-- | Setting 'TrailersMaker' to 'Response'.
setRequestTrailersMaker :: Request -> TrailersMaker -> Request
setRequestTrailersMaker :: Request -> TrailersMaker -> Request
setRequestTrailersMaker (Request OutObj
req) TrailersMaker
tm = OutObj -> Request
Request OutObj
req { outObjTrailers :: TrailersMaker
outObjTrailers = TrailersMaker
tm }

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

-- | Getting the status of a response.
responseStatus :: Response -> Maybe Status
responseStatus :: Response -> Maybe Status
responseStatus (Response InpObj
rsp) = HeaderTable -> Maybe Status
getStatus (HeaderTable -> Maybe Status) -> HeaderTable -> Maybe Status
forall a b. (a -> b) -> a -> b
$ InpObj -> HeaderTable
inpObjHeaders InpObj
rsp

-- | Getting the headers from a response.
responseHeaders :: Response -> HeaderTable
responseHeaders :: Response -> HeaderTable
responseHeaders (Response InpObj
rsp) = InpObj -> HeaderTable
inpObjHeaders InpObj
rsp

-- | Getting the body size from a response.
responseBodySize :: Response -> Maybe Int
responseBodySize :: Response -> Maybe Int
responseBodySize (Response InpObj
rsp) = InpObj -> Maybe Int
inpObjBodySize InpObj
rsp

-- | Reading a chunk of the response body.
--   An empty 'ByteString' returned when finished.
getResponseBodyChunk :: Response -> IO ByteString
getResponseBodyChunk :: Response -> IO Method
getResponseBodyChunk (Response InpObj
rsp) = InpObj -> IO Method
inpObjBody InpObj
rsp

-- | Reading response trailers.
--   This function must be called after 'getResponseBodyChunk'
--   returns an empty.
getResponseTrailers :: Response -> IO (Maybe HeaderTable)
getResponseTrailers :: Response -> IO (Maybe HeaderTable)
getResponseTrailers (Response InpObj
rsp) = IORef (Maybe HeaderTable) -> IO (Maybe HeaderTable)
forall a. IORef a -> IO a
readIORef (InpObj -> IORef (Maybe HeaderTable)
inpObjTrailers InpObj
rsp)