-- File created: 2008-01-12 16:06:32

-- |Holds the 'Rule' and 'Coadjute' types and relevant functions which act on
-- them. For user code, the important parts here are the 'rule' family of
-- functions.
module Coadjute.Rule(
   Rule(..),
   Coadjute, runCoadjute,
   getUserArgs,
   rule, ruleM,
   rule', ruleM'
) where

import Control.Arrow       (first)
import Control.Monad.State (StateT(..), modify, gets)
import Control.Monad.Trans (MonadIO, liftIO)
import qualified Data.Set as Set

import Coadjute.CoData
import Coadjute.Task (Task(..), Source, Target)

data Rule =
   Rule { rName  :: String
        , rTasks :: [Task]
        }

data RuleList = RL [Rule]

addRule :: Rule -> RuleList -> RuleList
addRule r (RL rs) = RL (r:rs)

-- |Coadjute is the main monad you'll be working in. You can use the 'rule'
-- family of functions to add rules whilst within this monad, and you can have
-- a look at what arguments were given to you with 'getUserArgs'.
--
-- For your convenience, a 'MonadIO' instance is provided.
newtype Coadjute a = Co { unCo :: StateT (RuleList, [String]) CoData a }

instance Monad Coadjute where
   return        = Co . return
   (Co rs) >>= f = Co (rs >>= unCo.f)

instance MonadIO Coadjute where liftIO = Co . liftIO

runCoadjute :: Coadjute a -> CoData ([Rule], a)
runCoadjute (Co st) = do
   ua <- asks coUserArgs
   (ret, (RL l, _)) <- runStateT st (RL [], ua)
   return (reverse l, ret)

-- | You should use this instead of 'System.Environment.getArgs', to let
-- Coadjute handle its own command line arguments first.
getUserArgs :: Coadjute [String]
getUserArgs = Co $ gets snd

-- |A rule for building targets individually.
rule :: String
     -> [String]
     -> ([Source] -> Target -> IO ())
     -> [([Source],Target)]
     -> Coadjute ()
rule name args action =
   Co . modify . first . addRule . Rule name .
      map (\(d,t) -> Task name (Set.fromList args) [t] d (action d t))

-- |A rule for building multiple targets at once.
ruleM :: String
      -> [String]
      -> ([Source] -> [Target] -> IO ())
      -> [([Source],[Target])]
      -> Coadjute ()
ruleM name args action =
   Co . modify . first . addRule . Rule name .
      map (\(d,t) -> Task name (Set.fromList args) t d (action d t))

-- | > rule' = flip rule []
rule' :: String
      -> ([Source] -> Target -> IO ())
      -> [([Source],Target)]
      -> Coadjute ()
rule' = flip rule []

-- | > ruleM' = flip ruleM []
ruleM' :: String
       -> ([Source] -> [Target] -> IO ())
       -> [([Source],[Target])]
       -> Coadjute ()
ruleM' = flip ruleM []