{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

module Simplex.Messaging.Transport.HTTP2
  ( HTTP2Body (..),
    HTTP2BodyChunk (..),
    defaultHTTP2BufferSize,
    withHTTP2,
    http2TLSParams,
    getHTTP2Body,
    httpALPN,
    httpALPN11,
  ) where

import qualified Control.Exception as E
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Default (def)
import Data.Maybe (fromMaybe)
import Foreign (mallocBytes)
import Network.HPACK (BufferSize)
import Network.HTTP2.Client (Config (..), defaultPositionReadMaker, freeSimpleConfig)
import qualified Network.HTTP2.Client as HC
import qualified Network.HTTP2.Server as HS
import Network.Socket (SockAddr (..))
import qualified Network.TLS as T
import qualified Network.TLS.Extra as TE
import Simplex.Messaging.Transport (ALPN, TLS, Transport (cGet, cPut))
import Simplex.Messaging.Transport.Buffer
import qualified System.TimeManager as TI

defaultHTTP2BufferSize :: BufferSize
defaultHTTP2BufferSize = 32768

withHTTP2 :: BufferSize -> (Config -> IO a) -> IO () -> TLS p -> IO a
withHTTP2 sz run fin c = E.bracket (allocHTTP2Config c sz) (\cfg -> freeSimpleConfig cfg `E.finally` fin) run

allocHTTP2Config :: TLS p -> BufferSize -> IO Config
allocHTTP2Config c sz = do
  buf <- mallocBytes sz
  tm <- TI.initialize $ 30 * 1000000
  pure
    Config
      { confWriteBuffer = buf,
        confBufferSize = sz,
        confSendAll = cPut c,
        confReadN = cGet c,
        confPositionReadMaker = defaultPositionReadMaker,
        confTimeoutManager = tm,
        confMySockAddr = SockAddrInet 0 0,
        confPeerSockAddr = SockAddrInet 0 0
      }

http2TLSParams :: T.Supported
http2TLSParams =
  def
    { T.supportedVersions = [T.TLS13, T.TLS12],
      T.supportedCiphers = TE.ciphersuite_strong_det,
      T.supportedSecureRenegotiation = False
    }

data HTTP2Body = HTTP2Body
  { bodyHead :: ByteString,
    bodySize :: Int,
    bodyPart :: Maybe (Int -> IO ByteString),
    bodyBuffer :: TBuffer
  }

class HTTP2BodyChunk a where
  getBodyChunk :: a -> IO ByteString
  getBodySize :: a -> Maybe Int

instance HTTP2BodyChunk HC.Response where
  getBodyChunk = HC.getResponseBodyChunk
  {-# INLINE getBodyChunk #-}
  getBodySize = HC.responseBodySize
  {-# INLINE getBodySize #-}

instance HTTP2BodyChunk HS.Request where
  getBodyChunk = HS.getRequestBodyChunk
  {-# INLINE getBodyChunk #-}
  getBodySize = HS.requestBodySize
  {-# INLINE getBodySize #-}

getHTTP2Body :: HTTP2BodyChunk a => a -> Int -> IO HTTP2Body
getHTTP2Body r n = do
  bodyBuffer <- newTBuffer
  let getPart n' = getBuffered bodyBuffer n' Nothing $ getBodyChunk r
  bodyHead <- getPart n
  let bodySize = fromMaybe 0 $ getBodySize r
      -- TODO check bodySize once it is set
      bodyPart = if B.length bodyHead == n then Just getPart else Nothing
  pure HTTP2Body {bodyHead, bodySize, bodyPart, bodyBuffer}

httpALPN :: [ALPN]
httpALPN = ["h2", "http/1.1"]

httpALPN11 :: ALPN
httpALPN11 = "http/1.1"
