------------------------------------------------------------------------
-- |
-- Module      :  ALife.Creatur.Logger.SimpleRotatingLogger
-- Copyright   :  (c) Amy de Buitléir 2011-2014
-- License     :  BSD-style
-- Maintainer  :  amy@nualeargais.ie
-- Stability   :  experimental
-- Portability :  portable
--
-- A simple rotating log, tailored to the needs of the Créatúr 
-- framework.
--
------------------------------------------------------------------------
{-# OPTIONS_GHC -fno-warn-type-defaults #-}

module ALife.Creatur.Logger.SimpleRotatingLogger
  (
    Logger(..),
    SimpleRotatingLogger,
    mkSimpleRotatingLogger
  ) where

import ALife.Creatur.Util (getLift)
import ALife.Creatur.Logger (Logger(..), timestamp)
import Control.Conditional (whenM, unlessM)
import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State (StateT, gets, modify)
import System.Directory (createDirectoryIfMissing, doesFileExist,
  renameFile)

-- | A rotating logger.
data SimpleRotatingLogger = SimpleRotatingLogger {
    initialised :: Bool,
    directory :: FilePath,
    logFilename :: FilePath,
    expFilename :: FilePath,
    maxRecordsPerFile :: Int,
    recordCount :: Int,
    logCount :: Int
  } deriving (Show, Eq)

-- | @'mkSimpleRotatingLogger' d prefix n@ creates a logger that will write to
--   directory @d@. The log \"rotates\" (starts a new log file) every @n@
--   records. Log files follow the naming convention @prefix@./k/, where /k/ 
--   is the number of the last log record contained in the file. If logging
--   has already been set up in @directory@, then logging will continue where
--   it left off; appending to the most recent log file.
mkSimpleRotatingLogger :: FilePath -> String -> Int -> SimpleRotatingLogger
mkSimpleRotatingLogger d pre n =
  SimpleRotatingLogger False d fLog fExp n 0 0
  where fLog = d ++ "/" ++ pre ++ ".log"
        fExp = d ++ "/" ++ pre ++ ".exp"

instance Logger SimpleRotatingLogger where
  writeToLog msg = do
    initIfNeeded
    bumpRecordCount
    rotateLogIfNeeded
    getLift $ write' msg
    saveState

initIfNeeded :: StateT SimpleRotatingLogger IO ()
initIfNeeded =
  unlessM (gets initialised) initialise

initialise :: StateT SimpleRotatingLogger IO ()
initialise = do
  d <- gets directory
  liftIO $ createDirectoryIfMissing True d
  fExp <- gets expFilename
  whenM (liftIO $ doesFileExist fExp) readState
  modify (\l -> l { initialised=True } )
  debug "initialise"

debug :: String -> StateT SimpleRotatingLogger IO ()
debug s = do
  n <- gets recordCount
  k <- gets logCount
  getLift . write' $ "DEBUG " ++ s ++ ": n=" ++ show n ++ ": k=" ++ show k 
  fExp <- gets expFilename
  whenM (liftIO $ doesFileExist fExp) $ do
    s' <- liftIO $ readFile fExp
    let (n',k') = read s' :: (Int,Int)
    getLift . write' $ "DEBUG2 " ++ s ++ ": n'=" ++ show n' ++ ": k'=" ++ show k' 
  
readState :: StateT SimpleRotatingLogger IO ()
readState = do
  fExp <- gets expFilename
  s <- liftIO $ readFile fExp
  let (n,k) = read s
  modify (\l -> l { recordCount=n, logCount=k } )

saveState :: StateT SimpleRotatingLogger IO ()
saveState = do
  e <- gets expFilename
  n <- gets recordCount
  k <- gets logCount
  liftIO . writeFile e $ "(" ++ show n ++ "," ++ show k ++ ")"

write' :: String -> SimpleRotatingLogger -> IO ()
write' msg logger = do
  ts <- timestamp
  appendFile (logFilename logger) $ ts ++ "\t" ++ msg ++ "\n"

bumpRecordCount :: StateT SimpleRotatingLogger IO ()
bumpRecordCount = modify (\l -> l { recordCount=recordCount l + 1 })

rotateLogIfNeeded :: StateT SimpleRotatingLogger IO ()
rotateLogIfNeeded = do
  debug "rotateLogIfNeeded"
  n <- gets recordCount
  m <- gets maxRecordsPerFile
  when (n >= m) $ rotateLog
  
rotateLog :: StateT SimpleRotatingLogger IO ()
rotateLog = do
  debug "rotateLog"
  f <- gets logFilename
  n <- gets logCount
  let fPrev = f ++ '.' : show n
  getLift . write' $ "Continued in log " ++ show (n+1)
  liftIO $ renameFile f fPrev
  getLift . write' $ "Continued from log " ++ show n
  modify (\l -> l { recordCount=0, logCount=n+1 })