-- | Here is an example GET request that streams the response body to standard
--   output:
--
-- > import Pipes
-- > import Pipes.HTTP
-- > import qualified Pipes.ByteString as PB  -- from `pipes-bytestring`
-- >
-- > main = do
-- >     req <- parseUrl "https://www.example.com"
-- >     withManager tlsManagerSettings $ \m ->
-- >         withHTTP req m $ \resp ->
-- >             runEffect $ responseBody resp >-> PB.stdout
--
--   Here is an example POST request that also streams the request body from
--   standard input:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import Pipes
-- > import Pipes.HTTP
-- > import qualified Pipes.ByteString as PB
-- >
-- > main = do
-- >     req <- parseUrl "https://www.example.com"
-- >     let req' = req
-- >             { method = "POST"
-- >             , requestBody = stream PB.stdin
-- >             }
-- >     withManager tlsManagerSettings $ \m ->
-- >         withHTTP req' m $ \resp ->
-- >             runEffect $ responseBody resp >-> PB.stdout
--
-- For non-streaming request bodies, study the 'RequestBody' type, which also
-- accepts strict \/ lazy bytestrings or builders.


module Pipes.HTTP (
    -- * http-client
    -- $httpclient
      module Network.HTTP.Client
    , module Network.HTTP.Client.TLS

    -- * Pipes Interface
    , withHTTP
    , streamN
    , stream

    ) where

import Control.Monad (unless)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Int (Int64)
import Data.IORef (newIORef, readIORef, writeIORef)
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import Pipes

{- $httpclient
    This module is a thin @pipes@ wrapper around the @http-client@ and
    @http-client-tls@ libraries.

    Read the documentation in the "Network.HTTP.Client" module of the
    @http-client@ library to learn about how to:

    * manage connections using connection pooling,

    * use more advanced request\/response features,

    * handle exceptions, and:

    * manage cookies.

    @http-client-tls@ provides support for TLS connections (i.e. HTTPS).
-}

-- | Send an HTTP 'Request' and wait for an HTTP 'Response'
withHTTP
    :: Request
    -- ^
    -> Manager
    -- ^
    -> (Response (Producer ByteString IO ()) -> IO a)
    -- ^ Handler for response
    -> IO a
withHTTP r m k = withResponse r m k'
  where
    k' resp = do
        let p = (from . brRead . responseBody) resp
        k (resp { responseBody = p})
{-# INLINABLE withHTTP #-}

-- | Create a 'RequestBody' from a content length and 'Producer'
streamN :: Int64 -> Producer ByteString IO () -> RequestBody
streamN n p = RequestBodyStream n (to p)
{-# INLINABLE streamN #-}

{-| Create a 'RequestBody' from a 'Producer'

    'stream' is more flexible than 'streamN', but requires the server to support
    chunked transfer encoding.
-}
stream :: Producer ByteString IO () -> RequestBody
stream p = RequestBodyStreamChunked (to p)
{-# INLINABLE stream #-}

to :: Producer ByteString IO () -> (IO ByteString -> IO ()) -> IO ()
to p0 k = do
    ioref <- newIORef p0
    let readAction :: IO ByteString
        readAction = do
            p <- readIORef ioref
            x <- next p
            case x of
                Left   ()      -> do
                    writeIORef ioref (return ())
                    return B.empty
                Right (bs, p') -> do
                    writeIORef ioref p'
                    return bs
    k readAction 

from :: IO ByteString -> Producer ByteString IO ()
from io = go
  where
    go = do
        bs <- lift io
        unless (B.null bs) $ do
            yield bs
            go