{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

module Logging.Global.Internal
  ( log
  , run
  ) where


import           Control.Exception
import           Control.Monad
import           Control.Monad.IO.Class
import           Data.IORef
import           GHC.Conc
import           Prelude                hiding (log)
import           System.IO.Unsafe

import           Logging.Level
import           Logging.Logger
import           Logging.Manager
import qualified Logging.Monad.Internal as M

{-# NOINLINE ref #-}
ref :: IORef Manager
ref = unsafePerformIO $ newIORef $
  error "Global logging manager is not set, see Logging.Global.run"


log :: MonadIO m
     => Logger -> Level -> String -> (String, String, String, Int)
     -> m ()
log logger level message location = liftIO $ do
  manager <- readIORef ref
  M.runLoggingT (M.log logger level message location) manager

{-| Run the global logging environment.

You should always run you application in the global logging environment.

  @
    main :: IO ()
    main = run manager app

    app :: IO ()
    app = do
      $(info) \"App\" "..."
      ...
  @

Never run multiple global logging environments concurrently.

  @
    -- bad useage
    main :: IO ()
    main = do
      forkIO $ run manager1 app1
      forkIO $ run manager2 app2
      ...

    -- correct useage
    main :: IO ()
    main = run manager $ do
      forkIO app1
      forkIO app2
      ...
  @
-}
run :: Manager -> IO a -> IO a
run manager@Manager{..} io = do
    oldHandler <- getUncaughtExceptionHandler
    when catchUncaughtException $ setUncaughtExceptionHandler logException
    bracket_ (initialize manager >> atomicWriteIORef ref manager)
             (terminate manager >> setUncaughtExceptionHandler oldHandler)
             io
  where
    unknownLoc = ("unknown file", "unknown package", "unknown module", 0)

    logException :: SomeException -> IO ()
    logException e = log "" "ERROR" (show e) unknownLoc