{-# LANGUAGE OverloadedStrings, FlexibleInstances, UndecidableInstances, MultiParamTypeClasses, GeneralizedNewtypeDeriving, ConstraintKinds, FlexibleContexts, CPP, ImplicitParams #-}

module System.Log.Simple.Monad (
        -- | Monad log
        MonadLog(..), LogT(..),
        noLog, withLog, runLog,

        -- | Getters
        askComponent, askScope,

        -- | Log functions
        log, sendLog,
        component,
        scope_, scope, scopeM, scoper, scoperM,
        trace,
        modifyLogConfig, modifyLogHandlers,
        ) where

import Prelude hiding (log)
import Prelude.Unicode

#if __GLASGOW_HASKELL__ >= 800
import Control.Exception (SomeException)
#endif
import Control.Monad.Catch
import Control.Monad.Except
import Control.Monad.Fail
import Control.Monad.IO.Class
import Control.Monad.Morph
import Control.Monad.Reader
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Stack
import System.Log.Simple.Base

class (MonadIO m, MonadMask m) => MonadLog m where
        askLog  m Log
        localLog  (Log  Log)  m a  m a

instance {-# OVERLAPPABLE #-} (MonadLog m, MonadTrans t, MFunctor t, MonadIO (t m), MonadMask (t m))  MonadLog (t m) where
        askLog = lift askLog
        localLog fn = hoist (localLog fn)

newtype LogT m a = LogT { runLogT  ReaderT Log m a }
        deriving (Functor, Applicative, Monad, MonadFail, MonadIO, MonadReader Log, MonadThrow, MonadCatch, MonadMask)

instance MonadTrans LogT where
        lift = LogT  lift

instance (MonadIO m, MonadMask m) => MonadLog (LogT m) where
        askLog = LogT ask
        localLog fn = LogT  local fn  runLogT

-- | Run with no logging
noLog  (MonadIO m, MonadMask m)  LogT m a  m a
noLog = runLog defCfg []

-- | Run @LogT@ monad with @Log@
withLog  Log  LogT m a  m a
withLog l act = runReaderT (runLogT act) l

-- | Run @LogT@ monad with log config and handlers
runLog  (MonadIO m, MonadMask m)  LogConfig  [LogHandler]  LogT m a  m a
runLog cfg handlers = bracket (liftIO $ newLog cfg handlers) (liftIO  stopLog)  flip withLog

-- | Ask current component
askComponent  MonadLog m  m Component
askComponent = logComponent <$> askLog

-- | Ask current scope
askScope  MonadLog m  m Scope
askScope = logScope <$> askLog

-- | Log message
log  MonadLog m  Level  Text  m ()
log lev msg = do
        l  askLog
        writeLog l lev msg

-- | Log message, same as @log@
sendLog  MonadLog m  Level  Text  m ()
sendLog = log

-- | Log component, also sets root scope
component  MonadLog m  Text  m a  m a
component c = localLog (getLog (read  T.unpack $ c) mempty)

-- | Create local scope
scope_  MonadLog m  Text  m a  m a
scope_ s = localLog (subLog mempty (read  T.unpack $ s))

#if __GLASGOW_HASKELL__ < 800
type HasCallStack = ?callStack  CallStack

callStack  HasCallStack  CallStack
callStack = ?callStack

prettyCallStack  CallStack  String
prettyCallStack = showCallStack
#endif

-- | Scope with log all exceptions
scope  (MonadLog m, HasCallStack)  Text  m a  m a
scope s act = scope_ s $ catch act onErr where
        onErr  MonadLog m  SomeException  m a
        onErr e = do
                log Error $ T.unlines [
                        T.concat ["Scope leaves with exception: ", fromString  show $ e],
                        fromString $ prettyCallStack callStack]
                throwM e

-- | Scope with log exception from @MonadError@
scopeM  (MonadLog m, MonadError e m, Show e, HasCallStack)  Text  m a  m a
scopeM s act = scope_ s $ catchError act onErr where
        onErr  (MonadLog m, MonadError e m, Show e)  e  m a
        onErr e = do
                log Error $ T.unlines [
                        T.concat ["Scope leaves with exception: ", fromString  show $ e],
                        fromString $ prettyCallStack callStack]
                throwError e

-- | Scope with tracing result
scoper  (MonadLog m, Show a, HasCallStack)  Text  m a  m a
scoper s act = do
        r  scope s act
        log Trace $ T.concat ["Scope ", s, " leaves with result: ", fromString . show $ r]
        return r

scoperM  (MonadLog m, MonadError e m, Show e, Show a, HasCallStack)  Text  m a  m a
scoperM s act = do
        r  scopeM s act
        log Trace $ T.concat ["Scope", s, " leaves with result: ", fromString . show $ r]
        return r

-- | Trace value
trace  (MonadLog m, Show a)  Text  m a  m a
trace name act = do
        v  act
        log Trace $ T.concat [name, " = ", fromString . show $ v]
        return v

-- | Modify config, same as @updateLogConfig@, but within @MonadLog@
modifyLogConfig  MonadLog m  (LogConfig  LogConfig)  m LogConfig
modifyLogConfig fn = askLog >>= flip updateLogConfig fn

-- | Modify handlers, same as @updateLogHandlers@, but within @MonadLog@
modifyLogHandlers  MonadLog m  ([LogHandler]  [LogHandler])  m ()
modifyLogHandlers fn = askLog >>= flip updateLogHandlers fn