{-# LANGUAGE CPP #-}
module System.Logging.Facade.Sink (
  LogSink
, defaultLogSink
, getLogSink
, setLogSink
, swapLogSink
, withLogSink
) where

import           Control.Concurrent
import           Data.IORef
import           System.IO
import           System.IO.Unsafe (unsafePerformIO)
import           Control.Exception

import           System.Logging.Facade.Types

-- | A consumer for log records
type LogSink = LogRecord -> IO ()

-- use the unsafePerformIO hack to share one sink across a process
logSink :: IORef LogSink
logSink = unsafePerformIO (defaultLogSink >>= newIORef)
{-# NOINLINE logSink #-}

-- | Return the global log sink.
getLogSink :: IO LogSink
getLogSink = readIORef logSink

-- | Set the global log sink.
setLogSink :: LogSink -> IO ()
setLogSink = atomicWriteIORef logSink

-- | Return the global log sink and set it to a new value in one atomic
-- operation.
swapLogSink :: LogSink -> IO LogSink
swapLogSink new = atomicModifyIORef logSink $ \old -> (new, old)

-- | Set the global log sink to a specified value, run given action, and
-- finally restore the global log sink to its previous value.
withLogSink :: LogSink -> IO () -> IO ()
withLogSink sink action = bracket (swapLogSink sink) setLogSink (const action)

-- | A thread-safe log sink that writes log messages to `stderr`
defaultLogSink :: IO LogSink
defaultLogSink = defaultLogSink_ `fmap` newMVar ()

defaultLogSink_ :: MVar () -> LogSink
defaultLogSink_ mvar record = withMVar mvar (\() -> hPutStrLn stderr output)
  where
    level = logRecordLevel record
    mLocation = logRecordLocation record
    message = logRecordMessage record
    output = shows level . location . showString ": " . showString message $ ""
    location = maybe (showString "") ((showString " " .) . formatLocation) mLocation

formatLocation :: Location -> ShowS
formatLocation loc = showString (locationFile loc) . colon . shows (locationLine loc) . colon . shows (locationColumn loc)
  where colon = showString ":"

#if !MIN_VERSION_base(4,6,0)
atomicWriteIORef :: IORef a -> a -> IO ()
atomicWriteIORef ref a = do
    x <- atomicModifyIORef ref (\_ -> (a, ()))
    x `seq` return ()
#endif