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

{-|
Module      : $header$
Copyright   : (c) Laurent P René de Cotret, 2020
License     : GNU GPL, version 2 or above
Maintainer  : laurent.decotret@outlook.com
Stability   : internal
Portability : portable

Logging primitives.
-}

module Text.Pandoc.Filter.Plot.Monad.Logging
    ( Verbosity(..)
    , LogSink(..)
    , Logger(..)
    , withLogger
    ) where


import           Control.Concurrent           (forkIO)
import           Control.Concurrent.Chan      (Chan, newChan, readChan, writeChan)
import           Control.Concurrent.MVar      (MVar, newEmptyMVar, putMVar, takeMVar)

import           Control.Monad                (forever)

import           Data.Char                    (toLower)
import           Data.String                  (IsString(..))
import           Data.Text                    (Text, unpack)
import           Data.Text.IO                 (hPutStr)
import           Data.Yaml

import           System.IO                    (stderr, withFile, IOMode (AppendMode) )



-- | Verbosity of the logger.
data Verbosity = Debug    -- ^ Log all messages, including debug messages.
               | Error    -- ^ Log information, warnings, and errors.
               | Warning  -- ^ Log information and warning messages.
               | Info     -- ^ Only log information messages.
               | Silent   -- ^ Don't log anything. 
               deriving (Eq, Ord, Show)


-- | Description of the possible ways to sink log messages.
data LogSink = StdErr           -- ^ Standard error stream.
             | LogFile FilePath -- ^ Appended to file.
             deriving (Eq, Show)


-- | The logging implementation is very similar to Hakyll's.
data Logger = Logger
    { lVerbosity :: Verbosity
    , lChannel   :: Chan (Maybe Text)
    , lSink      :: Text -> IO ()
    , lSync      :: MVar ()
    }


-- | Perform an IO action with a logger. Using this function
-- ensures that logging will be gracefully shut down.
withLogger :: Verbosity -> LogSink -> (Logger -> IO a) -> IO a
withLogger v s f = do
    logger <- Logger <$> pure v
                     <*> newChan
                     <*> pure (sink s)
                     <*> newEmptyMVar

    -- The logger either logs messages (if Just "message"),
    -- or stops working on Nothing.
    _ <- forkIO $ forever $
            readChan (lChannel logger)
                >>= maybe (putMVar (lSync logger) ()) (lSink logger)

    result <- f logger

    -- Flushing the logger
    -- To signal to the logger that logging duties are over,
    -- we append Nothing to the channel, and wait for it to finish
    -- dealing with all items in the channel.
    writeChan (lChannel logger) Nothing
    () <- takeMVar (lSync logger)

    return result

    where
        sink StdErr       = hPutStr stderr
        sink (LogFile fp) = \t -> withFile fp AppendMode $ \h -> hPutStr h t


instance IsString Verbosity where
    fromString s
        | ls == "silent"  = Silent
        | ls == "info"    = Info
        | ls == "warning" = Warning
        | ls == "error"   = Error
        | ls == "debug"   = Debug
        | otherwise = error $ "Unrecognized verbosity " <> s
        where
            ls = toLower <$> s

instance FromJSON Verbosity where
    parseJSON (String t) = pure $ fromString . unpack $ t
    parseJSON _ = fail $ "Could not parse the logging verbosity."