module Network.Mail.Postie.Connection
  ( Connection,
    connIsSecure,
    connSetSecure,
    connRecv,
    connSend,
    connClose,
    mkSocketConnection,
    toProducer,
  )
where

import Control.Exception (finally)
import Control.Monad (unless)
import Control.Monad.IO.Class
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import Data.ByteString.Lazy.Internal (defaultChunkSize)
import Data.IORef
import Network.Socket
import Network.Socket.ByteString hiding (sendAll)
import Network.Socket.ByteString.Lazy (sendAll)
import Network.TLS
import qualified Pipes as P

data ConnectionBackend
  = ConnPlain Socket
  | ConnSecure Context

newtype Connection = Connection (IORef ConnectionBackend)

connSetSecure :: Connection -> ServerParams -> IO ()
connSetSecure (Connection cbe) params = do
  backend <- readIORef cbe
  securedBackend <- upgrade backend
  writeIORef cbe securedBackend
  where
    upgrade (ConnPlain be) = do
      context <- contextNew be params
      handshake context
      return (ConnSecure context)
    upgrade (ConnSecure _) = error "already on secure connection"

connIsSecure :: Connection -> IO Bool
connIsSecure (Connection cbe) = do
  backend <- readIORef cbe
  return $ case backend of
    (ConnSecure _) -> True
    _ -> False

mkSocketConnection :: Socket -> IO Connection
mkSocketConnection s = do
  conn <- newIORef (ConnPlain s)
  return (Connection conn)

connBackendRecv :: ConnectionBackend -> IO BS.ByteString
connBackendRecv (ConnPlain s) = recv s defaultChunkSize
connBackendRecv (ConnSecure ctx) = recvData ctx

connBackendSend :: ConnectionBackend -> LBS.ByteString -> IO ()
connBackendSend (ConnPlain s) = sendAll s
connBackendSend (ConnSecure ctx) = sendData ctx

connRecv :: Connection -> IO BS.ByteString
connRecv (Connection cbe) = readIORef cbe >>= connBackendRecv

connSend :: Connection -> LBS.ByteString -> IO ()
connSend (Connection cbe) lbs = do
  backend <- readIORef cbe
  connBackendSend backend lbs

connClose :: Connection -> IO ()
connClose (Connection cbe) = closeBackend =<< readIORef cbe
  where
    closeBackend (ConnPlain s) = close s
    closeBackend (ConnSecure context) = bye context `finally` contextClose context

toProducer :: (MonadIO m) => Connection -> P.Producer' BS.ByteString m ()
toProducer conn = go
  where
    go = do
      bs <- liftIO $ connRecv conn
      unless (BS.null bs) $
        P.yield bs >> go
