{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeApplications     #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Gargantext.System.Logging (
    module Gargantext.System.Logging.Types
  , logM
  , logLocM
  , logLoc
  , withLogger
  , withLoggerIO
  ) where

import Gargantext.System.Logging.Types
import Gargantext.System.Logging.Loggers
import Control.Exception.Safe (MonadMask, bracket)
import Gargantext.Core.Config (LogConfig(..))
import Control.Monad.IO.Class
import Control.Monad.Trans.Control
import Data.Text qualified as T
import Language.Haskell.TH hiding (Type)
import Language.Haskell.TH.Syntax qualified as TH
import Prelude


-- | A variant of 'logTxt' that doesn't require passing an explicit 'Logger'.
logM :: (Monad m, MonadLogger m) => LogLevel -> T.Text -> m ()
logM level msg = do
  logger <- getLogger
  logTxt logger level msg

-- | Like 'logM', but it automatically adds the file and line number to
-- the output log.
logLocM :: ExpQ
logLocM = [| \level msg ->
  let loc = $(getLocTH)
  in logM level (formatWithLoc loc msg)
  |]

logLoc :: ExpQ
logLoc = [| \logger level msg ->
  let loc = $(getLocTH)
  in logTxt logger level (formatWithLoc loc msg)
  |]

formatWithLoc :: Loc -> T.Text -> T.Text
formatWithLoc loc msg = "[" <> locationToText <> "] " <> msg
  where
    locationToText :: T.Text
    locationToText = T.pack $ (loc_filename loc) ++ ':' : (line loc) ++ ':' : (char loc)
      where
        line = show . fst . loc_start
        char = show . snd . loc_start

getLocTH :: ExpQ
getLocTH = [| $(location >>= liftLoc) |]

liftLoc :: Loc -> Q Exp
liftLoc (Loc a b c (d1, d2) (e1, e2)) = [|Loc
    $(TH.lift a)
    $(TH.lift b)
    $(TH.lift c)
    ($(TH.lift d1), $(TH.lift d2))
    ($(TH.lift e1), $(TH.lift e2))
    |]

-- | exception-safe combinator that creates and destroys a logger.
-- Think about it like a 'bracket' function from 'Control.Exception'.
withLogger :: (MonadBaseControl IO m, MonadIO m, HasLogger m, MonadMask m)
           => LogInitParams m
           -> (Logger m -> m a)
           -> m a
withLogger params = bracket (initLogger params) destroyLogger

-- | Like 'withLogger', but it allows creating a 'Logger' that can run in
-- a different monad from within an 'IO' action.
withLoggerIO :: (MonadBaseControl IO m, HasLogger m)
             => LogInitParams m
             -> (Logger m -> IO a)
             -> IO a
withLoggerIO params act = bracket (initLogger params) destroyLogger act

-- | A plain logger in the IO monad, waiting for more serious logging solutions like
-- the one described in https://gitlab.iscpif.fr/gargantext/haskell-gargantext/issues/229
instance HasLogger IO where
  data instance Logger IO        = IOLogger { _IOLogger :: IOStdLogger }
  type instance LogInitParams IO = LogConfig
  type instance LogPayload IO    = String
  initLogger cfg = fmap IOLogger $ (liftIO $ ioStdLogger cfg)
  destroyLogger = liftIO . _iosl_destroy . _IOLogger
  logMsg (IOLogger ioLogger) = _iosl_log_msg ioLogger
  logTxt (IOLogger ioLogger) lvl msg = liftIO $ _iosl_log_txt ioLogger lvl msg
