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

module Logging.Types.Handlers.RotatingFileHandler
  ( RotatingFileHandler(..)
  ) where

import           Control.Monad
import           Data.IORef
import           GHC.Generics
import           System.IO
import           Text.Format

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


-- | 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      :: IORef Handle
                                               } deriving (Generic, Eq)

instance Handler RotatingFileHandler where
  open RotatingFileHandler{..} =
    atomicWriteIORef stream =<< openLogFile file encoding

  emit handler@RotatingFileHandler{..} rcd = do
      let msg = format1 formatter rcd
      stream' <- readIORef stream
      hPutStrLn stream' msg
      hFlush stream'
      when (backupCount > 0) $ do
        pos <- hTell stream'
        when (fromEnum pos >= maxBytes) $ do
          hClose stream'
          rotate $ backupCount - 1
          tryRenameFile file $ appendBaseName file ".1"
          open handler
    where
      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{..} = hClose =<< readIORef stream