{-# 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.IO.Class

import Control.Monad.Morph

import Control.Monad.Reader

import Control.Monad.Except

import Control.Monad.Catch

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, 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  Text  m a  m a

scope s act = scope_ s $ catch act onError where

	onError  (MonadLog m, HasCallStack)  SomeException  m a

	onError 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)  Text  m a  m a

scopeM s act = scope_ s $ catchError act onError where

	onError  (MonadLog m, MonadError e m, Show e, HasCallStack)  e  m a

	onError 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)  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)  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