{-# LANGUAGE OverloadedStrings               #-}
{-# LANGUAGE FlexibleInstances               #-}
module Twitch.Rule where
import Prelude hiding (FilePath)
import Control.Monad ( void )
import System.FilePath ( FilePath, isRelative, (</>) )
import System.FilePath.Glob ( simplify, match, tryCompileWith, compDefault )
import Data.String ( IsString(..) )
import Data.Default ( Default(..) )
import Control.Arrow ( ArrowChoice(left) )


type Name        = String
type PatternText = String

-- | TODO maybe change this to have the timestamp
type RuleAction = FilePath -> IO ()

-- | The pattern entity holds a name and pattern that is compiled 
-- when the rules are evaluated

-- It is worth noting that the entire API could just be this
-- Record with Default
-- There are actually three apis
-- "foo.x" .# "name" .$ \x -> print x
-- "foo.x" |> \x -> print x
-- "foo.txt" 
--    { add    = \x -> print x
--    , modify = \x -> print x
--    }

data Rule = Rule
  { name          :: String
  , pattern       :: PatternText
  , add           :: RuleAction
  , modify        :: RuleAction
  , delete        :: RuleAction
  }

instance Default Rule where
  def = Rule
          { name    = ""
          , pattern = ""
          , add     = def
          , modify  = def
          , delete  = def
          }

instance IsString Rule where
  fromString x = def { pattern = x, name = x}

--- Infix API------------------------------------------------------------------
infixl 8 |+, |%, |-, |>, |#
(|+), (|%), (|-), (|>) :: Rule -> (FilePath -> IO a) -> Rule
-- | Set the 'add' field
--   ex.
-- 
--   > "doodle.md |+ ringBell "
x |+ f = x { add = void . f }
-- | Set the modify field
x |% f = x { modify = void . f }
-- | Set the delete field
x |- f = x { delete = void . f }
-- | Set both the 'add' and 'modify' field to the same value
x |> f = x |+ f |% f

-- | Set the name
(|#) :: Rule -> String -> Rule
r |# p = r { name = p }

-- Prefix API -----------------------------------------------------------------
addF, modifyF, deleteF, addModifyF :: (FilePath -> IO a) -> Rule -> Rule
addF       = flip (|+)
modifyF    = flip (|%)
deleteF    = flip (|-)
addModifyF = flip (|>)

nameF :: String -> Rule -> Rule
nameF = flip (|#)

-- def & add foo & modify foo & delete foo & test tester
-- def & add foo & modify foo & delete foo & pattern tester

data RuleIssue
  = PatternCompliationFailed PatternText String
  deriving (Show, Eq)

makeAbsolutePath :: FilePath -> FilePath -> FilePath
makeAbsolutePath currentDir path =
  if isRelative path then
    currentDir </> path
  else
    path

makeAbsolute :: FilePath -> Rule -> Rule
makeAbsolute currentDir rule
  = rule { pattern = makeAbsolutePath currentDir $ pattern rule }

compilePattern :: PatternText
               -> Either RuleIssue (FilePath -> Bool)
compilePattern pat = left (PatternCompliationFailed pat) $ do
  tryCompileWith compDefault pat >>= return . match . simplify