module Sos
  ( Sos
  , runSos
  , sosEnqueueJobs
  , module Sos.Exception
  , module Sos.FileEvent
  , module Sos.Job
  , module Sos.JobQueue
  , module Sos.Rule
  , module Sos.Template
  ) where

import Sos.Exception
import Sos.FileEvent
import Sos.Job
import Sos.JobQueue
import Sos.Rule
import Sos.Template

import Control.Applicative
import Control.Monad.Except
import Control.Monad.Trans.Resource
import Data.List.NonEmpty           (NonEmpty(..))
import Streaming
import System.Exit
import Text.Regex.TDFA              (match)

import qualified Streaming.Prelude as S


type Sos a = ResourceT (ExceptT SosException IO) a

-- | Run an 'Sos' action in IO, exiting if any 'SosException's are thrown.
runSos :: Sos a -> IO a
runSos act =
  runExceptT (runResourceT act) >>= \case
    Left err -> do
      print err
      exitFailure
    Right x -> return x

-- | Enqueue jobs on the given job queue resulting from to apply the given
-- rules to each emitted file event from the given stream. This function
-- returns when the stream is exhausted.
sosEnqueueJobs
  :: (Applicative m, MonadError SosException m, MonadResource m)
  => [Rule]
  -> Stream (Of FileEvent) m a
  -> JobQueue
  -> m a
sosEnqueueJobs rules events queue =
  S.mapM_
    (\(event, cmds) -> liftIO (enqueueJob event cmds queue))
    (jobStream rules events)

jobStream
  :: forall m a.
     (Applicative m, MonadError SosException m)
  => [Rule]
  -> Stream (Of FileEvent) m a
  -> Stream (Of (FileEvent, NonEmpty ShellCommand)) m a
jobStream rules events =
  S.for events
    (\event ->
      lift (commands event) >>= \case
        []     -> pure ()
        (c:cs) -> S.yield (event, c :| cs))
 where
  commands :: FileEvent -> m [ShellCommand]
  commands event = concat <$> mapM go rules
   where
    go :: Rule -> m [ShellCommand]
    go rule =
      case match (ruleRegex rule) (fileEventPath event) of
        []     -> pure []
        (xs:_) -> mapM (instantiateTemplate xs) (ruleTemplates rule)