{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# OPTIONS_GHC -Wno-star-is-type #-}
module Thrift.Channel
  ( ClientChannel(..)
  , SendCallback, RecvCallback
  , Handle, mkCallbacks, wait
  , Request(..), RpcOptions(..)
  , Priority(..), Header
  , defaultRpcOptions, simpleRequest
  , Response(..), ChannelException(..)
  ) where

import Control.Exception hiding (handle)
import Control.Concurrent
import Control.Monad
import Data.ByteString (ByteString)
import Data.Text (Text)

import Thrift.Monad

-- Channels --------------------------------------------------------------------

class ClientChannel (c :: * -> *) where
  sendRequest       :: c t -> Request -> SendCallback -> RecvCallback -> IO ()
  sendOnewayRequest :: c t -> Request -> SendCallback -> IO ()

type SendCallback = Maybe ChannelException -> IO ()
type RecvCallback = Either ChannelException Response -> IO ()

-- Default Callback Implementation with an MVar --------------------------------

type Handle a = MVar (Either SomeException a)

mkCallbacks
  :: (Response -> Either SomeException a)
  -> IO (Handle a, SendCallback, RecvCallback)
mkCallbacks deserialize = do
  handle <- newEmptyMVar
  let
    sendCob Nothing = return ()
    -- If there is a send error, then recv will never get called
    sendCob (Just err) = putMVar handle (Left $ SomeException err)

    recvCob (Left err) = putMVar handle (Left $ SomeException err)
    recvCob (Right r)  = putMVar handle $ deserialize r
  return (handle, sendCob, recvCob)

wait :: Handle a -> IO a
wait = takeMVar >=> either throw return

-- Requests --------------------------------------------------------------------

data Request = Request
  { reqMsg     :: !ByteString
  , reqOptions :: !RpcOptions
  }

simpleRequest :: ByteString -> Request
simpleRequest bs = Request { reqMsg     = bs
                           , reqOptions = defaultRpcOptions
                           }

-- Responses -------------------------------------------------------------------

data Response = Response
  { respMsg    :: ByteString
  , respHeader :: Header
  }

type Header = [(ByteString, ByteString)]

newtype ChannelException = ChannelException Text
                         deriving (Show, Eq)
instance Exception ChannelException
