{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards       #-}

module Logging.Handler.RotatingFileHandler
  ( RotatingFileHandler(..)
  ) where

import           Control.Concurrent.MVar
import           Control.Monad
import           GHC.Generics
import           System.IO
import           Text.Format

import           Logging.Class
import           Logging.Filter
import           Logging.Level
import           Logging.Prelude


-- | A handler type which writes logging records, appropriately formatted,
-- to a file, it will rotate when file is too large.
--
-- Since 0.3.0
--
data RotatingFileHandler = RotatingFileHandler { level       :: Level
                                               , filterer    :: Filterer
                                               , formatter   :: Format1
                                               , file        :: FilePath
                                               , encoding    :: TextEncoding
                                               , maxBytes    :: Int
                                                 -- ^ Actual file size may be
                                                 -- slightly larger than this
                                                 -- value.
                                               , backupCount :: Int
                                               , stream      :: MVar Handle
                                               -- ^ Don't open file manually,
                                               -- initialized as empty 'MVar'
                                               -- and use 'open' or
                                               -- "Logger.Manager.initialize"
                                               } deriving (Generic, Eq)

instance Handler RotatingFileHandler where
  open RotatingFileHandler{..} = isEmptyMVar stream >>= safeOpen
    where
      safeOpen True  = void $ tryPutMVar stream =<< openLogFile file encoding
      safeOpen False = modifyMVar_ stream $ \h -> do
        hClose h
        openLogFile file encoding

  emit handler@RotatingFileHandler{..} rcd = do
      let msg = format1 formatter rcd
      modifyMVar_ stream $ \stream' -> do
        hPutStrLn stream' msg
        hFlush stream'
        pos <- hTell stream'
        rollover (backupCount > 0) (fromEnum pos >= maxBytes) stream'
    where
      rollover :: Bool -> Bool -> Handle -> IO Handle
      rollover True True h = do
        hClose h
        rotate $ backupCount - 1
        tryRenameFile file $ appendBaseName file ".1"
        openLogFile file encoding
      rollover _ _ h = return h

      rotate :: Int -> IO ()
      rotate n = when (n > 0) $ do
        let src = appendBaseName file $ '.' : (show n)
            dest = appendBaseName file $ '.' : (show (n + 1))
        tryRenameFile src dest
        rotate (n - 1)

  close RotatingFileHandler{..} = withMVar stream hClose