{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
module Twitch.InternalRule where
import Filesystem.Path
import Data.Time.Clock
import System.FSNotify ( Event (..)
                       , WatchConfig
                       , watchDir
                       , startManagerConf
                       , WatchManager
                       , defaultConfig
                       )
import Data.Default
import Control.Monad
import Data.Monoid
import Prelude hiding (FilePath)
import Twitch.Rule (Rule, RuleIssue)
import qualified Twitch.Rule as Rule

-- | The actions that are run when file events are triggered
type Action   = FilePath -> UTCTime -> IO ()
-- | The test function to determine if a event 'Action' should get fired
type FileTest = FilePath -> UTCTime -> Bool

data InternalRule = InternalRule 
  { name     :: String
  -- ^ A name for debugging mostly
  , fileTest :: FileTest
  -- ^ The test to determine if the rule actions should fire
  , modify   :: Action 
  -- ^ The action to run on Modify events
  , add      :: Action 
  -- ^ The action to run on Add events
  , delete   :: Action 
  -- ^ The action to run on Delete events
  }
  
instance Default InternalRule where
  def = InternalRule 
    { name     = mempty
    , fileTest = \_ _ -> False
    , modify   = def
    , add      = def
    , delete   = def
    }

instance Show InternalRule where
  show InternalRule {..} 
    =  "Rule { name = " 
    <> name 
    <> " }"

toInternalRule :: FilePath -> Rule -> Either RuleIssue InternalRule
toInternalRule currentDir rule = do
  test <- Rule.compilePattern currentDir $ Rule.pattern rule
  return $ InternalRule 
    { name     = Rule.name rule
    , fileTest = \x _ -> test x
    , add      = \x _ -> Rule.add    rule x
    , modify   = \x _ -> Rule.modify rule x
    , delete   = \x _ -> Rule.delete rule x
    }

-- | Configuration to run the file watcher
data Config = Config 
  { logger      :: Issue -> IO ()
  -- ^ A logger for the issues 
  , dirs        :: [FilePath]
  -- ^ The directories to watch
  , watchConfig :: WatchConfig
  -- ^ config for the file watcher
  } 
  
instance Show Config where
  show Config {..} 
    =  "Config { dirsToWatch = " 
    ++ show dirs
    ++ "}"

instance Default Config where
  def = Config
    { logger      = def
    , dirs        = def
    , watchConfig = defaultConfig
    }
    
-- | A sum type for the various issues that can be logged
data Issue 
  = IEvent     Event
  -- ^ logged every time an event is fired
  | IRuleFired Event InternalRule
  -- ^ logged every time an rule is fired
  deriving Show

-- | Retrieve the filePath of an Event
filePath :: Event -> FilePath
filePath e = case e of
  Added    x _ -> x
  Modified x _ -> x
  Removed  x _ -> x

-- | Retrieve the time of an Event
time :: Event -> UTCTime
time e = case e of
  Added    _ x -> x
  Modified _ x -> x
  Removed  _ x -> x

-- | Run the Rule action associated with the an event 
fireRule :: Event -> InternalRule -> IO ()
fireRule event rule = case event of
  Added    file time -> modify rule file time
  Modified file time -> add    rule file time
  Removed  file time -> delete rule file time

-- | Test to see if the rule should fire and fire it
testAndFireRule :: Config -> Event -> InternalRule -> IO ()
testAndFireRule Config {..} event rule = do
  let shouldFire = fileTest rule (filePath event) (time event)
  when shouldFire $ do 
    logger $ IRuleFired event rule
    fireRule event rule 

-- TODO in the future this should use the recursive directory functions
--      when appropiate
-- | Start watching a directory, and run the rules on it.
setupRuleForDir :: Config -> WatchManager -> [InternalRule] -> FilePath -> IO ()
setupRuleForDir config@(Config {..}) man rules dirPath = do
  -- TODO Instead of const True, this should use the rule's fileTests
  void $ watchDir man dirPath (const True) $ \event -> do 
    logger $ IEvent event
    forM_ rules $ testAndFireRule config event

-- | Setup all of the directory watches using the rules
setupRules :: Config -> [InternalRule] -> IO WatchManager
setupRules config@(Config {..}) rules = do 
  man <- startManagerConf watchConfig
  forM_ dirs $ setupRuleForDir config man rules
  return man