{-
 -
 -  Copyright 2005-2007, Robert Dockins.
 -
 -}

-- | This module implements a monad for use in shell commands and in
--   evaluation functions.  It is a state moand layered over @IO@.
--   @liftIO@ may be used to execute arbitrary I\/O actions.  However,
--   the @shellPut@* commands are the preferred way to output text.

module System.Console.Shell.ShellMonad (
-- * The Shell monad
  Sh
, runSh

-- * Output functions
, shellPut
, shellPutStr, shellPutStrLn
, shellPutInfo, shellPutInfoLn
, shellPutErr, shellPutErrLn

-- * Shell state accessors
, getShellSt, putShellSt
, modifyShellSt

-- * Special actions
, shellSpecial


-- * Extracting and using the shell context
, ShellContext
, extractContext, runWithContext, updateCommandResult

) where

import Control.Monad.Reader
import Control.Monad.State

import System.Console.Shell.Backend
import System.Console.Shell.Types

-- | Execute a shell action
runSh :: st -> OutputCommand -> Sh st () -> IO (CommandResult st)
runSh st info = (flip runReaderT) info . (flip execStateT) (st,Nothing) . unSh

-- | Output a tagged string to the console
shellPut :: BackendOutput -> Sh st ()
shellPut out = Sh (lift ask >>= \f -> liftIO (f out))

-- | Prints a regular output string
shellPutStr :: String -> Sh st ()
shellPutStr = shellPut . RegularOutput

-- | Prints an informational output string
shellPutInfo :: String -> Sh st ()
shellPutInfo = shellPut . InfoOutput

-- | Prints an error output string
shellPutErr :: String -> Sh st ()
shellPutErr = shellPut . ErrorOutput

-- | Prints regular output with a line terminator
shellPutStrLn :: String -> Sh st ()
shellPutStrLn = shellPutStr . (++"\n")

-- | Prints an informational output string with a line terminator
shellPutInfoLn :: String -> Sh st ()
shellPutInfoLn = shellPutInfo . (++"\n")

-- | Prints and error output string with a line terminator
shellPutErrLn :: String -> Sh st ()
shellPutErrLn = shellPutErr . (++"\n")

-- | Get the current shell state
getShellSt :: Sh st st
getShellSt = Sh (get >>= return . fst)

-- | Set the shell state
putShellSt :: st -> Sh st ()
putShellSt st = Sh (get >>= \ (_,spec) -> put (st,spec))

-- | Apply the given funtion to the shell state
modifyShellSt :: (st -> st) -> Sh st ()
modifyShellSt f = getShellSt >>= putShellSt . f

-- | Schedule a shell \"special\" action.  Only the last call to
--   this function will affect the shell's behavior! It modifies
--   a bit of state that is overwritten on each call.
shellSpecial :: ShellSpecial st -> Sh st ()
shellSpecial spec = Sh (get >>= \ (st,_) -> put (st,Just spec))

instance MonadState st (Sh st) where
  get = getShellSt
  put = putShellSt

-- | The total context held by the shell, with @'CommandResult' st@
--   being mutable and 'OutputCommand' immutable
type ShellContext st = (CommandResult st, OutputCommand)

-- | Extract the current shell context for future use, see 'runWithContext'
extractContext :: Sh st (ShellContext st)
extractContext = (Sh . StateT) $ \s -> do
    imC <- ask               
    return ((s, imC), s)

-- | Run a shell with the supplied context, useful if you need to
--   invoke a shell within a new IO context, for example when using
--   'System.Timeout.timeout'
runWithContext :: ShellContext st -> Sh st a -> IO (a, CommandResult st)
runWithContext (mC, imC) = (flip runReaderT) imC . (flip runStateT) mC . unSh

-- | Update the mutable context of this shell
updateCommandResult :: CommandResult st -> Sh st ()
updateCommandResult s = (Sh . StateT) $ \_ -> return (() , s)