{-# LANGUAGE RecordWildCards #-}

-- | Basic abstraction
module Network.AMQP.Connection
    ( Connection
    , SSL.SSLContext
    , ConnectionParams(..)
    , connectionClose
    , connectTo
    , connectionGet
    , connectionPut
    ) where

import Network.Socket (PortNumber)
import qualified OpenSSL.Session as SSL
import qualified OpenSSL as SSL
import qualified System.IO.Streams as Streams
import qualified System.IO.Streams.SSL as Streams hiding (connect)
import System.IO.Streams (InputStream, OutputStream)
import qualified Network.Socket as N

import Network.AMQP.Prelude

data Connection = Connection
    { connClose :: IO ()
    , connOS    :: OutputStream ByteString
    , connIS    :: InputStream ByteString
    }

data ConnectionParams = ConnectionParams
    { connectionHostname   :: String
    , connectionPort       :: PortNumber
    , connectionSecure     :: Maybe SSL.SSLContext
    }

connectionClose :: Connection -> IO ()
connectionClose = connClose

connectionPut :: Connection -> ByteString -> IO ()
connectionPut Connection{..} bs = do
  Streams.write (Just bs) connOS
  Streams.write (Just mempty) connOS

connectionGet :: Connection -> Int -> IO ByteString
connectionGet Connection{..} n = Streams.readExactly n connIS

connectTo :: ConnectionParams -> IO Connection
connectTo ConnectionParams{..} = do
      ais <- N.getAddrInfo (Just N.defaultHints { N.addrFlags = [N.AI_ADDRCONFIG, N.AI_NUMERICSERV], N.addrSocketType = N.Stream })
                           (Just connectionHostname) (Just $ show connectionPort)
      let addr = head ais
      s <- N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
      N.connect s (N.addrAddress addr)

      case connectionSecure of
        Nothing -> do
          (connIS,connOS) <- Streams.socketToStreams s
          let connClose = N.close s
          pure Connection {..}
        Just ctx -> SSL.withOpenSSL $ do
          ssl <- SSL.connection ctx s
          SSL.setTlsextHostName ssl connectionHostname
          SSL.connect ssl
          (connIS,connOS) <- Streams.sslToStreams ssl
          let connClose = do
                SSL.shutdown ssl SSL.Unidirectional
                N.close s
          pure Connection {..}