--------------------------------------------------------------------------------
-- |
-- Module : Database.EventStore.Internal.Settings
-- Copyright : (C) 2017 Yorick Laupa
-- License : (see the file LICENSE)
--
-- Maintainer : Yorick Laupa <yo.eight@gmail.com>
-- Stability : provisional
-- Portability : non-portable
--
--------------------------------------------------------------------------------
module Database.EventStore.Internal.Settings where

--------------------------------------------------------------------------------
import Network.Connection (TLSSettings)

--------------------------------------------------------------------------------
import Database.EventStore.Internal.Logger
import Database.EventStore.Internal.Prelude

--------------------------------------------------------------------------------
-- Flag
--------------------------------------------------------------------------------
-- | Indicates either a 'Package' contains 'Credentials' data or not.
data Flag
    = None
    | Authenticated
    deriving Show

--------------------------------------------------------------------------------
-- | Maps a 'Flag' into a 'Word8' understandable by the server.
flagWord8 :: Flag -> Word8
flagWord8 None          = 0x00
flagWord8 Authenticated = 0x01

--------------------------------------------------------------------------------
-- Credentials
--------------------------------------------------------------------------------
-- | Holds login and password information.
data Credentials
    = Credentials
      { credLogin    :: !ByteString
      , credPassword :: !ByteString
      }
    deriving (Eq, Show)

--------------------------------------------------------------------------------
-- | Creates a 'Credentials' given a login and a password.
credentials :: ByteString -- ^ Login
            -> ByteString -- ^ Password
            -> Credentials
credentials = Credentials

--------------------------------------------------------------------------------
-- | Represents reconnection strategy.
data Retry
    = AtMost Int
    | KeepRetrying

--------------------------------------------------------------------------------
-- | Indicates how many times we should try to reconnect to the server. A value
--   less than or equal to 0 means no retry.
atMost :: Int -> Retry
atMost = AtMost

--------------------------------------------------------------------------------
-- | Indicates we should try to reconnect to the server until the end of the
--   Universe.
keepRetrying :: Retry
keepRetrying = KeepRetrying

--------------------------------------------------------------------------------
-- | Global 'Connection' settings
data Settings
    = Settings
      { s_heartbeatInterval :: NominalDiffTime
        -- ^ Maximum delay of inactivity before the client sends a heartbeat
        --   request.
      , s_heartbeatTimeout :: NominalDiffTime
        -- ^ Maximum delay the server has to issue a heartbeat response.
      , s_requireMaster :: Bool
        -- ^ On a cluster settings. Requires the master node when performing a
        --   write operation.
      , s_retry :: Retry
        -- ^ Retry strategy when failing to connect.
      , s_reconnect_delay :: NominalDiffTime
        -- ^ Delay before issuing a new connection request.
      , s_ssl :: Maybe TLSSettings
        -- ^ SSL settings.
      , s_loggerType :: LogType
        -- ^ Type of logging to use.
      , s_loggerFilter :: LoggerFilter
        -- ^ Restriction of what would be logged.
      , s_loggerDetailed :: Bool
        -- ^ Detailed logging output. Currently, it also indicates the location
        --   where the log occurred.
      , s_operationTimeout :: NominalDiffTime
        -- ^ Delay in which an operation will be retried if no response arrived.
      , s_operationRetry :: Retry
        -- ^ Retry strategy when an operation timeout.
      , s_monitoring :: MonitoringBackend
        -- ^ Monitoring backend abstraction. You could implement one targetting
        --   `ekg-core` for example. We will expose an `ekg-core` implementation
        --   as soon as `ekg-core` supports GHC 8.8.*.
      , s_defaultConnectionName :: Maybe Text
        -- ^ Default connection name.
      , s_defaultUserCredentials :: Maybe Credentials
        -- ^ 'Credentials' to use for operations where other 'Credentials' are
        --   not explicitly supplied.
      }

--------------------------------------------------------------------------------
-- | Default global settings.
--
--   * 's_heartbeatInterval'      = 750 ms
--   * 's_heartbeatTimeout'       = 1500 ms
--   * 's_requireMaster'          = 'True'
--   * 's_retry'                  = 'atMost' 3
--   * 's_reconnect_delay'        = 3 seconds
--   * 's_ssl'                    = 'Nothing'
--   * 's_loggerType'             = 'LogNone'
--   * 's_loggerFilter'           = 'LoggerLevel' 'LevelInfo'
--   * 's_loggerDetailed'         = 'False'
--   * 's_operationTimeout'       = 10 seconds
--   * 's_operationRetry'         = 'atMost' 3
--   * 's_monitoring'             = 'noopMonitoringBackend'
--   * 's_defaultConnectionName'  = 'Nothing'
--   * 's_defaultUserCredentials' = 'Nothing'
defaultSettings :: Settings
defaultSettings  = Settings
                   { s_heartbeatInterval      = msDiffTime 750  -- 750ms
                   , s_heartbeatTimeout       = msDiffTime 1500 -- 1500ms
                   , s_requireMaster          = True
                   , s_retry                  = atMost 3
                   , s_reconnect_delay        = 3
                   , s_ssl                    = Nothing
                   , s_loggerType             = LogNone
                   , s_loggerFilter           = LoggerLevel LevelInfo
                   , s_loggerDetailed         = False
                   , s_operationTimeout       = 10 -- secs
                   , s_operationRetry         = atMost 3
                   , s_monitoring             = noopMonitoringBackend
                   , s_defaultConnectionName  = Nothing
                   , s_defaultUserCredentials = Nothing
                   }

--------------------------------------------------------------------------------
-- | Default SSL settings based on 'defaultSettings'.
defaultSSLSettings :: TLSSettings -> Settings
defaultSSLSettings tls = defaultSettings { s_ssl = Just tls }

--------------------------------------------------------------------------------
-- | Millisecond timespan
msDiffTime :: Float -> NominalDiffTime
msDiffTime n = fromRational $ toRational (n / 1000)

--------------------------------------------------------------------------------
-- | Monitoring backend abstraction. Gathers all the metrics currently tracked
--   by the client. Used only by the TCP interface. Be careful as
--   'MonitoringBackend' is used in a very tight loop. Each
--   function must not throw any exception or the client will end in a broken
--   state.
data MonitoringBackend =
    MonitoringBackend
    { monitoringBackendIncrPkgCount :: IO ()
      -- ^ Called every time a TCP package is sent. We mean high-level TCP
      --   package, used in EventStore TCP protocol.
    , monitoringBackendIncrConnectionDrop :: IO ()
      -- ^ Called every time the client has lost the connection.
    , monitoringBackendAddDataTransmitted :: Int -> IO ()
      -- ^ When the client sends a TCP package, it calls that function by
      --   passing the size of the payload. The goal is to have a distrubtion
      --   of the amount of data exchanged with the server.
    , monitoringBackendIncrForceReconnect :: IO ()
      -- ^ Called every time the client is asked by a node to connect to
      --   another node. It happens only in cluster connection setting.
    , monitoringBackendIncrHeartbeatTimeouts :: IO ()
      -- ^ Called every time the client detects a heartbeat timeout from the
      --   server.
    }

--------------------------------------------------------------------------------
-- | A 'MonitoringBackend' that does nothing.
noopMonitoringBackend :: MonitoringBackend
noopMonitoringBackend =
    MonitoringBackend
    { monitoringBackendIncrPkgCount = pure ()
    , monitoringBackendIncrConnectionDrop = pure ()
    , monitoringBackendAddDataTransmitted = const (pure ())
    , monitoringBackendIncrForceReconnect = pure ()
    , monitoringBackendIncrHeartbeatTimeouts = pure ()
    }