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

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


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

import           Logging.Class
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, IsMessage s, ToJSON c)
    => Logger -> Level -> s -> c -> (String, String, String, Int) -> m ()
log logger level msg ctx location = liftIO $ do
  manager <- readIORef ref
  M.runLoggingT (M.log logger level msg ctx location) manager

{-| Run the global logging environment.

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
      ...
  @

Note: If there is an unhandled 'Exception' in your application, it will be
logged into the __root__ 'Sink', and then rethrown.
-}
run :: Manager -> IO a -> IO a
run manager@Manager{..} io = do
    bracket_ (initialize manager >> atomicWriteIORef ref manager)
             (terminate manager)
             (catch io logThrow)
  where
    unknownLoc = ("unknown file", "unknown package", "unknown module", 0)

    logThrow :: SomeException -> IO a
    logThrow e | Just UserInterrupt <- fromException e = throw e
    logThrow e = log "" "ERROR" e Null unknownLoc >> throw e