{-# LANGUAGE CPP #-}
module Log.Monad (
    Logger
  , LoggerEnv(..)
  , InnerLogT
  , LogT(..)
  , runLogT
  , mapLogT
  , logMessageIO
  , getLoggerIO
  ) where
import Control.Applicative
import Control.DeepSeq
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Error.Class
import Control.Monad.IO.Unlift
import Control.Monad.Morph (MFunctor (..))
import Control.Monad.Reader
import Control.Monad.State.Class
import Control.Monad.Trans.Control
import Control.Monad.Writer.Class
import Data.Aeson
import Data.Text (Text)
import Prelude
import qualified Control.Exception as E
import qualified Data.HashMap.Strict as H
import Log.Class
import Log.Data
import Log.Logger
type InnerLogT = ReaderT LoggerEnv
newtype LogT m a = LogT { unLogT :: InnerLogT m a }
  deriving (Alternative, Applicative, Functor, Monad, MonadBase b, MonadCatch
           ,MonadIO, MonadMask, MonadPlus, MonadThrow, MonadTrans
           ,MonadError e, MonadWriter w, MonadState s)
instance MonadReader r m => MonadReader r (LogT m) where
    ask   = lift ask
    local = mapLogT . local
runLogT :: Text     
        -> Logger   
        -> LogT m a 
        -> m a
runLogT component logger m = runReaderT (unLogT m) LoggerEnv {
  leLogger = logger
, leComponent = component
, leDomain = []
, leData = []
} 
  
mapLogT :: (m a -> n b) -> LogT m a -> LogT n b
mapLogT f = LogT . mapReaderT f . unLogT
logMessageIO :: LoggerEnv -> UTCTime -> LogLevel -> Text -> Value -> IO ()
logMessageIO LoggerEnv{..} time level message data_ =
  execLogger leLogger =<< E.evaluate (force lm)
  where
    lm = LogMessage
      { lmComponent = leComponent
      , lmDomain = leDomain
      , lmTime = time
      , lmLevel = level
      , lmMessage = message
      , lmData = case data_ of
          Object obj -> Object . H.union obj $ H.fromList leData
          _ | null leData -> data_
            | otherwise -> object $ ("_data", data_) : leData
      }
getLoggerIO :: MonadLog m => m (UTCTime -> LogLevel -> Text -> Value -> IO ())
getLoggerIO = logMessageIO <$> getLoggerEnv
instance MFunctor LogT where
    hoist = mapLogT
instance MonadTransControl LogT where
#if MIN_VERSION_monad_control(1,0,0)
  type StT LogT m = StT InnerLogT m
  liftWith = defaultLiftWith LogT unLogT
  restoreT = defaultRestoreT LogT
#else
  newtype StT LogT m = StLogT { unStLogT :: StT InnerLogT m }
  liftWith = defaultLiftWith LogT unLogT StLogT
  restoreT = defaultRestoreT LogT unStLogT
#endif
  {-# INLINE liftWith #-}
  {-# INLINE restoreT #-}
instance MonadBaseControl b m => MonadBaseControl b (LogT m) where
#if MIN_VERSION_monad_control(1,0,0)
  type StM (LogT m) a = ComposeSt LogT m a
  liftBaseWith = defaultLiftBaseWith
  restoreM     = defaultRestoreM
#else
  newtype StM (LogT m) a = StMLogT { unStMLogT :: ComposeSt LogT m a }
  liftBaseWith = defaultLiftBaseWith StMLogT
  restoreM     = defaultRestoreM unStMLogT
#endif
  {-# INLINE liftBaseWith #-}
  {-# INLINE restoreM #-}
instance MonadUnliftIO m => MonadUnliftIO (LogT m) where
  askUnliftIO = do
    UnliftIO runInIO <- LogT askUnliftIO
    return $ UnliftIO $ runInIO . unLogT
instance (MonadBase IO m, MonadTime m) => MonadLog (LogT m) where
  logMessage time level message data_ = LogT . ReaderT $ \logEnv ->
    liftBase $ logMessageIO logEnv time level message data_
  localData data_ =
    LogT . local (\e -> e { leData = data_ ++ leData e }) . unLogT
  localDomain domain =
    LogT . local (\e -> e { leDomain = leDomain e ++ [domain] }) . unLogT
  getLoggerEnv = LogT ask