-- |
-- Module      :  Log
-- Copyright   :  (c) Vitaliy Rukavishnikov 2011
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  virukav@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable
--
-- The module provides the generic api to parse the logs, to store the parsed
-- data in the IntervalMap and to query data from the IntervalMap based on the
-- given time interval. 
--   

module Database.Sybase.Sysmon.Log 
  ( Interval (..) 
  , LogRequest
  , merge
  , parse
  , hints 
  , fmtHints
  , average
  , list
  , intervals
  , hasInterval
  , mkInterval
  ) where

import Data.IntervalMap.FingerTree hiding (empty)
import Data.Time
import Data.Maybe (fromMaybe)
import Data.ConfigFile hiding (merge)
import Control.Monad.State (forM)
import qualified System.FilePath.Glob as G (compile, globDir1) 
import System.FilePath (splitFileName)
import System.IO.Unsafe (unsafePerformIO)
import System.IO
import System.Locale (defaultTimeLocale)
import Text.PrettyPrint
import Database.Sybase.Sysmon.LogTypes
import Database.Sybase.Sysmon.Average

-- | The request time interval to query sysmon reports. 
-- If the value of the request interval is Nothing the default max time
-- interval request will be used. See function maxInterval below.
type LogRequest = Maybe LogInterval

-- | Merge two log trees
merge :: LogEntry a => LogTree a -> LogTree a -> LogTree a
merge = union

-- | Generic parse the log files and store the data in the log tree. 
-- To parse sysmon logs use parseSysmon from SysmonLog package.
-- This package implements Sysmon instance of LogEntry class (see Sample.hs)
parse :: LogEntry a => FilePath -> IO (LogTree a)
parse path = do
    let (dir, fp) = splitFileName path
    fs <- G.globDir1 (G.compile fp) dir
    if null fs then error $ fp ++ ": No such file or directory"
      else do
         mons <- forM fs $ \name -> do
           h <- openFile name ReadMode
           c <- hGetContents h
           return [mkParse c]
         return $ mkLogTree $ map mkNode (concat mons)

-- | Max interval to cover all intervals in the log tree 
maxInterval = mkInterval startOfTime (unsafePerformIO getCurrentTime)
   where startOfTime = buildTime defaultTimeLocale []

-- | Get hints for the average sysmon report corresponding to the request 
-- time interval. To override the default hints parameters use ConfigFile
-- api. See HConfig data type in SysmonTypes package for the list of the
-- configuartion parameters.
hints :: (Averageable a, LogEntry a) => LogRequest -> ConfigParser -> LogTree a -> [Hint]
hints x cp = mkHints cp . average x 

-- | Pretty print the hints
fmtHints :: [Hint] -> Doc
fmtHints hs = vcat [text "Hints:", foldr fmtHint empty hs]
  where
    fmtHint (ruleId, msg, details) doc = 
       vcat [doc, 
             vcat [nest 2 (text msg), 
                   nest 4 (text $ show details), 
                   nest 6 (text "Rule Name:" <+> text ruleId) 
                  ]
            ]

-- | Average sysmon report corresponding to the requested time interval
average :: (Averageable a, LogEntry a) => LogRequest -> LogTree a -> a        
average x = avg . list x

-- | Get log reports which intersecs with the requested time interval
list :: LogEntry a => LogRequest -> LogTree a -> [a]
list x = map (\(LogNode x) -> snd x) . find (fromMaybe maxInterval x)

-- | Get intervals which intersect with the requested interval 
intervals :: LogEntry a => LogRequest -> LogTree a -> [LogInterval]
intervals x = map (\(LogNode x) -> fst x) . find (fromMaybe maxInterval x)

-- | Check if the log tree contains an interval corresponding to the
-- requested time interval
hasInterval :: LogEntry a => LogRequest -> LogTree a -> Bool
hasInterval x = not . null . intervals x

-- | Create log time interval
mkInterval :: UTCTime -> UTCTime -> LogInterval
mkInterval = Interval

-- | All intervals that intersect with the given interval, in 
-- lexicographical order
find :: LogEntry a => LogInterval -> LogTree a -> [LogNode a]
find x = map LogNode . intersections x