module Yam.Logger(
    LogRank(..)
  , LoggerConfig(..)
  , LoggerMonad(..)
  , defaultLoggerConfig
  , stdoutLoggerConfig
  , toMonadLogger
  , addVaultToLoggerConfig
  , logger
  , logL
  , logLn
  , traceLn
  , debugLn
  , infoLn
  , warnLn
  , errorLn
  , toLogStr
  , (<>)
  ) where

import           Yam.Config.Vault

import           Control.Monad          (when)
import           Control.Monad.IO.Class (MonadIO, liftIO)
import           Control.Monad.Logger
import           Data.Aeson
import           Data.Monoid
import           Data.Text              (Text)
import           Data.Vault.Lazy
import           System.Log.FastLogger

data LogRank
  = TRACE
  | DEBUG
  | INFO
  | WARN
  | ERROR
  deriving (Show, Eq, Ord)

instance FromJSON LogRank where
  parseJSON v = go <$> parseJSON v
    where go :: Text -> LogRank
          go "trace" = TRACE
          go "debug" = DEBUG
          go "info"  = INFO
          go "warn"  = WARN
          go "error" = ERROR
          go  _      = INFO

type Logger = LogStr -> IO ()
type Clock  = IO FormattedTime

data LoggerConfig = LoggerConfig
  { func     :: Logger
  , clock    :: Clock
  , logKey   :: Key Text
  , traceKey :: Key Text
  , rank     :: LogRank
  , logVault :: Vault
  }

defaultLoggerConfig :: IO LoggerConfig
defaultLoggerConfig = do
  c <- newTimeCache "%F %X"
  k <- newKey
  t <- newKey
  return $ LoggerConfig (\_ -> return ()) c k t INFO empty

stdoutLoggerConfig :: IO LoggerConfig
stdoutLoggerConfig = do
  lc <- defaultLoggerConfig
  ls <- newStdoutLoggerSet 4096
  return lc { func = pushLogStr ls }

toRank :: LogLevel -> LogRank
toRank LevelDebug = DEBUG
toRank LevelInfo  = INFO
toRank LevelWarn  = WARN
toRank LevelError = ERROR
toRank _          = INFO

type LogFunc = Loc -> LogSource -> LogLevel -> LogStr -> IO ()

toMonadLogger :: LoggerConfig -> LogFunc
toMonadLogger lc@LoggerConfig{..} _ ls level = logger lc { logVault = addFirstVault ls "." logKey logVault} (toRank level)

addVaultToLoggerConfig :: Vault -> LoggerConfig -> LoggerConfig
addVaultToLoggerConfig vault lc = lc {logVault = union vault $ logVault lc}

logger :: LoggerConfig -> LogRank -> Logger
logger LoggerConfig{..} r str = when (r >= rank) $ do
  now <- clock
  let nm :: Text
      nm = extracBoxOrDefault "" $ newBox logKey logVault
      ti :: Text
      ti = extracBoxOrDefault "" (newBox traceKey logVault)
  func $ toLogStr now <> " [" <> toLogStr (show r) <> "] - [" <> toLogStr ti <> "] " <> toLogStr nm <> " - " <> str

class MonadIO m => LoggerMonad m where
  loggerConfig :: m LoggerConfig

logL :: (ToLogStr msg, LoggerMonad m) => LogRank -> msg -> m ()
logL rank msg = do
  lc <- loggerConfig
  liftIO $ logger lc rank $ toLogStr msg

logLn :: (ToLogStr msg, LoggerMonad m) => LogRank -> msg -> m ()
logLn rank msg = do
  lc <- loggerConfig
  liftIO $ logger lc rank $ toLogStr msg <> "\n"

traceLn :: (ToLogStr msg, LoggerMonad m) => msg -> m ()
traceLn = logLn TRACE
{-# INLINE traceLn #-}
debugLn :: (ToLogStr msg, LoggerMonad m) => msg -> m ()
debugLn = logLn DEBUG
{-# INLINE debugLn #-}
infoLn  :: (ToLogStr msg, LoggerMonad m) => msg -> m ()
infoLn  = logLn INFO
{-# INLINE infoLn #-}
warnLn  :: (ToLogStr msg, LoggerMonad m) => msg -> m ()
warnLn  = logLn WARN
{-# INLINE warnLn #-}
errorLn :: (ToLogStr msg, LoggerMonad m) => msg -> m ()
errorLn = logLn ERROR
{-# INLINE errorLn #-}