{-# LANGUAGE FlexibleContexts, TypeFamilies #-}
module System.Process.Read.Verbosity
    ( quieter
    , noisier
    , withModifiedVerbosity
    , defaultVerbosity
    , verbosity
    -- * Process functions controlled by the VERBOSITY level.
    , runProcess
    , runProcessF
    -- * Output functions controlled by the VERBOSITY level.  We want these
    -- to output whenever the runProcess functions are not silent, and we want
    -- them to output at the first silent output setting, but then to stop.
    , qPutStr
    , qPutStrLn
    , qMessage
    , qMessageLn
    , qBracket
    ) where

import Control.Monad (when)
import Control.Monad.Trans (MonadIO, liftIO)
import System.Process (CreateProcess)
import System.Posix.EnvPlus (getEnv, modifyEnv)
import System.Process.Read.Chunks (Output, NonBlocking)
import System.Process.Read.Convenience (ePutStr, ePutStrLn)
import System.Process.Read.Monad (runProcessS, runProcessQ, runProcessD, runProcessV,
                                  runProcessSF, {-runProcessQF, runProcessDF,-} runProcessVF,
                                  runProcessSE, runProcessQE, runProcessDE)

quieter :: MonadIO m => Int -> m a -> m a
quieter n action = withModifiedVerbosity (\ v -> v - n) action
noisier :: MonadIO m => Int -> m a -> m a
noisier n action = withModifiedVerbosity (\ v -> v + n) action

withModifiedVerbosity :: MonadIO m => (Int -> Int) -> m a -> m a
withModifiedVerbosity f action =
    verbosity >>= \ v ->
    liftIO (modifyEnv "VERBOSITY" (const (Just (show (f v))))) >>
    -- ePutStr ("[" ++ show (f v) ++ "]") >>
    action >>= \ result ->
    -- ePutStr ("[" ++ show v ++ "]") >>
    liftIO (modifyEnv "VERBOSITY" (const (Just (show v)))) >>
    return result

defaultVerbosity :: Int
defaultVerbosity = 1

verbosity :: MonadIO m => m Int
verbosity = liftIO $ getEnv "VERBOSITY" >>= return . maybe 1 read

-- | Select from the runProcess* functions in Monad based on a verbosity level.
runProcess :: (NonBlocking s c, Enum c, MonadIO m) => CreateProcess -> s -> m [Output s]
runProcess p input = liftIO $
    verbosity >>= \ v ->
    case v of
      _ | v <= 0 -> runProcessS p input
      1 -> runProcessQ p input
      2 -> runProcessD p input
      _ -> runProcessV p input

-- | A version of 'runProcess' that throws an exception on failure.
runProcessF :: (NonBlocking s c, Enum c, MonadIO m) => Maybe (s, s) -> CreateProcess -> s -> m [Output s]
runProcessF prefixes p input = liftIO $
    verbosity >>= \ v ->
    case v of
      _ | v < 0 -> runProcessSF p input
      0 -> runProcessSE prefixes p input
      1 -> runProcessQE prefixes p input
      2 -> runProcessDE prefixes p input
      _ -> runProcessVF p input

qPutStrLn :: MonadIO m => String -> m ()
qPutStrLn s = verbosity >>= \ v -> when (v > 0) (ePutStrLn s)

qPutStr :: MonadIO m => String -> m ()
qPutStr s = verbosity >>= \ v -> when (v > 0) (ePutStr s)

qMessage :: MonadIO m => String -> a -> m a
qMessage s x = qPutStr s >> return x

qMessageLn :: MonadIO m => String -> a -> m a
qMessageLn s x = qPutStrLn s >> return x

qBracket :: MonadIO m => String -> m a -> m a
qBracket message action = do
  v <- verbosity
  case v of
    n | n < 1 -> action
    1 -> do
      qPutStr (message ++ "...")
      result <- quieter 1 action
      qPutStrLn "done."
      return result
    n -> do
      qPutStrLn message
      result <- quieter 1 action
      qPutStrLn (message ++ "...done.")
      return result