-- | Error utilities
module Lambdabot.Error where

import Control.Monad       (liftM)
import Control.Monad.Error (MonadError (..))

-- | 'catchErrorJust' is an error catcher for the Maybe type. As input is given
--   a deciding function, a monad and a handler. When an error is caught, the
--   decider is executed to decide if the error should be handled or not.
--   Then the handler is eventually called to handle the error.
catchErrorJust :: MonadError e m => (e -> Maybe b) -- ^ Decider function
               -> m a -- ^ Monad
               -> (b -> m a) -- ^ Handler function
               -> m a -- ^ Result: A monadic operation on type a
catchErrorJust decide m handler
 = catchError m (\e -> case decide e of
                         Just b -> handler b
                         Nothing -> throwError e)

-- | 'handleError' is the flipped version of 'catchError'.
handleError :: MonadError e m => (e -> m a) -- ^ Error handler
            -> m a -- ^ Monad
            -> m a -- ^ Resulting monad
handleError = flip catchError

-- | 'handleErrorJust' is the flipped version of 'catchErrorJust'.
handleErrorJust :: MonadError e m => (e -> Maybe b) -- ^ Decider
                -> (b -> m a) -- ^ Handler
                -> m a -- ^ Monad
                -> m a -- ^ Resulting Monad
handleErrorJust = flip . catchErrorJust

-- | 'tryError' uses Either to explicitly define the outcome of a
--   monadic operation. An error is caught and placed into Right,
--   whereas successful operation is placed into Left.
tryError :: MonadError e m => m a -- ^ Monad to operate on
         -> m (Either e a) -- ^ Returns: Explicit Either type
tryError m = catchError (liftM Right m) (return . Left)

-- | 'tryErrorJust' is the 'catchErrorJust' version of 'tryError'
--   given is a decider guarding whether or not the error should be
--   handled. The handler will always Right and no errors are Left'ed
--   through. If the decider returns Nothing, the error will be thrown
--   further up.
tryErrorJust :: MonadError e m => (e -> Maybe b) -- ^ Decider
             -> m a -- ^ Monad
             -> m (Either b a) -- ^ Returns: Explicit Either type
tryErrorJust decide m = catchErrorJust decide (liftM Right m) (return . Left)

-- | 'finallyError' is a monadic version of the classic UNWIND-PROTECT of
--   lisp fame. Given parameters m and after (both monads) we proceed to
--   work on m. If an error is caught, we execute the out-guard, after,
--   before rethrowing the error. If m does not fail, after is executed
--   and the value of m is returned.
finallyError :: MonadError e m => m a -- ^ Monadic operation
             -> m b -- ^ Guard
             -> m a -- ^ Returns: A new monad.
finallyError m after = do a <- catchError m (\e -> after >> throwError e)
                          _ <- after
                          return a

-- | 'bracketError' is the monadic version of DYNAMIC-WIND from Scheme
--   fame. Parameters are: before, after and m. before is the in-guard
--   being executed before m. after is the out-guard and protects fails
--   of the m.
--   In the Haskell world, this scheme is called a bracket and is often
--   seen employed to manage resources.
bracketError :: MonadError e m => m a -- ^ Before (in-guard) monad
             -> (a -> m b) -- ^ After (out-guard) operation. Fed output of before
             -> (a -> m c) -- ^ Monad to work on. Fed with output of before
             -> m c -- ^ Resulting monad.
bracketError before after m = do v <- before; finallyError (m v) (after v)

-- | 'bracketError_' is the non-bound version of 'bracketError'. The
--   naming scheme follows usual Haskell convention.
bracketError_ :: MonadError e m => m a -- ^ Before (in-guard)
              -> m b -- ^ After (out-guard)
              -> m c -- ^ Monad to work on
              -> m c -- ^ Resulting monad
bracketError_ before after m = bracketError before (\_ -> after) (\_ -> m)
