{-# LANGUAGE OverloadedStrings, TypeSynonymInstances, FlexibleInstances, ExistentialQuantification, TypeFamilies, GeneralizedNewtypeDeriving, StandaloneDeriving, MultiParamTypeClasses, UndecidableInstances, AllowAmbiguousTypes, ScopedTypeVariables, FunctionalDependencies, FlexibleContexts, ConstraintKinds #-}

-- | This module contains types and functions for log message severities.
module System.Log.Heavy.Level
  ( -- * Data types
   Level (..),
   -- * Utility functions
   levelToLogLevel, logLevelToLevel,
   parseLevel,
   -- * Standard severity levels
   trace_level, debug_level, info_level, warn_level, error_level, fatal_level,
   disable_logging
  ) where

import Control.Monad.Logger (LogLevel (..))
import qualified Data.Text as T
import qualified System.Posix.Syslog as Syslog

-- | Logging message severity level data type
data Level = Level {
    levelName :: T.Text            -- ^ Level name, like @"debug"@ or @"info"@
  , levelInt :: Int                -- ^ Integer level identifier. Comparation is based on these identifiers.
  , levelToPriority :: Syslog.Priority -- ^ Syslog equivalent of this level.
  } deriving (Eq)

instance Show Level where
  show l = T.unpack (levelName l)

instance Ord Level where
  compare l1 l2 = compare (levelInt l1) (levelInt l2)

-- | TRACE level is supposed to be used for development-stage debugging.
-- Has integer value of 600.
trace_level :: Level
trace_level = Level "TRACE" 600 Syslog.Debug

-- | DEBUG level is supposed to be used for debug logging that can be
-- enabled on production.
-- Has integer value of 500.
debug_level :: Level
debug_level = Level "DEBUG" 500 Syslog.Debug

-- | INFO level: some event occured.
-- Has integer value of 400.
info_level :: Level
info_level = Level "INFO" 400 Syslog.Info

-- | WARN level: something went wrong, but for now it will not affect
-- system's stability.
-- Has integer value of 300.
warn_level :: Level
warn_level = Level "WARN" 300 Syslog.Warning

-- | ERROR level: something went wrong.
-- Has integer value of 200.
error_level :: Level
error_level = Level "ERROR" 200 Syslog.Error

-- | FATAL level: something went terribly wrong, application is to be stopped.
-- Has integer value of 100.
fatal_level :: Level
fatal_level = Level "FATAL" 100 Syslog.Emergency

-- | DISABLED level. This has integer identifier of 0, which is supposed to
-- be less than any other level. This value can be used to disable logging at
-- all.
disable_logging :: Level
disable_logging = Level "DISABLED" 0 Syslog.Emergency

-- | Conversion function
levelToLogLevel :: Level -> LogLevel
levelToLogLevel l =
  case levelName l of
    "DEBUG" -> LevelDebug
    "INFO"  -> LevelInfo
    "WARN"  -> LevelWarn
    "ERROR" -> LevelError
    name -> LevelOther name

-- | Convertion function. Note that @LevelOther@ is
-- translated to integer level 210 and Syslog's Alert priority,
-- since in @monad-logger@ semantics any LevelOther is more severe
-- than LevelError.
logLevelToLevel :: LogLevel -> Level
logLevelToLevel LevelDebug = debug_level
logLevelToLevel LevelInfo  = info_level
logLevelToLevel LevelWarn  = warn_level
logLevelToLevel LevelError = error_level
logLevelToLevel (LevelOther name) = Level name 210 Syslog.Alert

-- | Detect @Level@ from it's name. This function
-- is not case-sensitive.
parseLevel :: [Level] -- ^ List of recognized levels
           -> T.Text  -- ^ Level name to find
           -> Maybe Level -- ^ Nothing if no match found
parseLevel knownLevels str = go knownLevels
  where
    go [] = Nothing
    go (l:ls)
      | T.toCaseFold (levelName l) == needle = Just l
      | otherwise = go ls

    needle = T.toCaseFold str