-- |
-- Module      :  LogParserPrim
-- Copyright   :  (c) Vitaliy Rukavishnikov 2011
-- License     :  BSD-style (see the file LICENSE)
-- 
-- Maintainer  :  virukav@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Log files parsing primitives 

module Database.Sybase.Sysmon.LogParserPrim 
       ( LogState (..)
       , Field
       , matchLine
       , matchField
       , optLine
       , optString
       , optField
       , string
       , field
       , look
       , lookAhead
       , whileJust
       , goto
       ) where
import Data.List
import Control.Monad.State

type LogState a = State [String] a 
type Field = String

-- | Discard until predicate and split a string to the fields
line :: (String -> Bool) -> LogState [Field]
line p = do
   xs <- get
   let ys = dropWhile (not . p)  xs   
   if null ys then do
      put ys
      return []
    else do
      put $ drop 1 ys
      return $ (words . head) ys 

-- | Recursively collect values contained in the Just  
whileJust :: Monad m => m (Maybe a) -> m [a]
whileJust p = do
  x <- p
  case x of
    Nothing -> return mzero
    Just x -> do
      xs <- whileJust p
      return $ return x `mplus` xs

-- | Get field after discarding the prefix 
matchField :: Read a => String -> Int -> LogState a
matchField s i = do
  ls <- line (isInfixOf s)
  return $ field i ls

-- | Get fields after discarding the prefix
matchLine :: String -> LogState [Field]
matchLine s = line (isInfixOf s) 

-- | Parse string to the field value
field :: Read a => Int -> [Field] -> a
field i xs = read $ xs !! i

-- | Concatenate fields to the string value
string :: Int -> Int -> [Field] -> Field
string i j = unwords . take (j - i) . drop i

-- | Look ahead for the first substring until the second substring 
look :: String -> String -> LogState Bool
look s f = lookAhead (isInfixOf s) (isInfixOf f) 

-- | Get fields if the first substring matches otherwise return empty
optLine :: String -> String -> LogState [Field]
optLine f g = do
  b <- lookAhead (isInfixOf f) (isInfixOf g)
  if b then matchLine f
   else return []

-- | Get field if the first substring matches otherwise return a default value
optField :: Read a => String -> String -> Int -> a -> LogState a
optField f g i x = do
  b <- lookAhead (isInfixOf f) (isInfixOf g)
  if b then do 
      h <- matchLine f
      return $ field i h
     else return x

-- | Get string field if the first substring matches otherwise return 
-- a default value
optString :: String -> String -> Int -> Int -> String -> LogState Field
optString f g i j x = do
  b <- lookAhead (isInfixOf f) (isInfixOf g)
  if b then do 
      h <- matchLine f
      return $ string i j h
     else return x

-- | Discard the matching lines 
goto :: [String] -> LogState [Field]
goto ss = do 
  mapM_ matchLine ss
  return []

-- | Test the first predicate until the second predicate  
lookAhead :: (String -> Bool) -> (String -> Bool) -> LogState Bool
lookAhead p g = do
   xs <- get
   let ys = dropWhile (\x -> (not . p) x && (not . g) x) xs
   if null ys || g (head ys) then return False else return True