{-|
    This module extends the @safe@ library's functions with corresponding
    versions compatible with 'Either' and 'EitherT'.

    All functions take an exceptional value to return should they fail.

    I suffix the 'Either'-compatible functions with @Err@ and prefix the
    'EitherT'-compatible functions with @try@.

    Note that this library re-exports the 'Maybe' compatible functions from
    @safe@ in the "Control.Error" module, so they are not provided here.
-}

module Control.Error.Safe (
    -- * Either-compatible functions
    tailErr,
    initErr,
    headErr,
    lastErr,
    minimumErr,
    maximumErr,
    foldr1Err,
    foldl1Err,
    foldl1Err',
    atErr,
    readErr,
    assertErr,
    -- * EitherT-compatible functions
    tryTail,
    tryInit,
    tryHead,
    tryLast,
    tryMinimum,
    tryMaximum,
    tryFoldr1,
    tryFoldl1,
    tryFoldl1',
    tryAt,
    tryRead,
    tryAssert,
    ) where

import Control.Error.Util
import Control.Monad.Trans.Either
import Safe

-- | A 'tail' that fails in the 'Either' monad
tailErr :: e -> [a] -> Either e [a]
tailErr e = note e . tailMay

-- | An 'init' that fails in the 'Either' monad
initErr :: e -> [a] -> Either e [a]
initErr e = note e . initMay

-- | A 'head' that fails in the 'Either' monad
headErr :: e -> [a] -> Either e a
headErr e = note e . headMay

-- | A 'last' that fails in the 'Either' monad
lastErr :: e -> [a] -> Either e a
lastErr e = note e . lastMay

-- | A 'minimum' that fails in the 'Either' monad
minimumErr :: (Ord a) => e -> [a] -> Either e a
minimumErr e = note e . minimumMay

-- | A 'maximum' that fails in the 'Either' monad
maximumErr :: (Ord a) => e -> [a] -> Either e a
maximumErr e = note e . maximumMay

-- | A 'foldr1' that fails in the 'Either' monad
foldr1Err :: e -> (a -> a -> a) -> [a] -> Either e a
foldr1Err e step xs = note e $ foldr1May step xs

-- | A 'foldl1' that fails in the 'Either' monad
foldl1Err :: e -> (a -> a -> a) -> [a] -> Either e a
foldl1Err e step xs = note e $ foldl1May step xs

-- | A 'foldl1'' that fails in the 'Either' monad
foldl1Err' :: e -> (a -> a -> a) -> [a] -> Either e a
foldl1Err' e step xs = note e $ foldl1May' step xs

-- | A ('!!') that fails in the 'Either' monad
atErr :: e -> [a] -> Int -> Either e a
atErr e xs n = note e $ atMay xs n

-- | A 'read' that fails in the 'Either' monad
readErr :: (Read a) => e -> String -> Either e a
readErr e = note e . readMay

-- | An assertion that fails in the 'Either' monad
assertErr :: e -> Bool -> a -> Either e a
assertErr e p a = if p then Right a else Left e

-- | A 'tail' that fails in the 'EitherT' monad
tryTail :: (Monad m) => e -> [a] -> EitherT e m [a]
tryTail e xs = liftEither $ tailErr e xs

-- | An 'init' that fails in the 'EitherT' monad
tryInit :: (Monad m) => e -> [a] -> EitherT e m [a]
tryInit e xs = liftEither $ initErr e xs

-- | A 'head' that fails in the 'EitherT' monad
tryHead :: (Monad m) => e -> [a] -> EitherT e m a
tryHead e xs = liftEither $ headErr e xs

-- | A 'last' that fails in the 'EitherT' monad
tryLast :: (Monad m) => e -> [a] -> EitherT e m a
tryLast e xs = liftEither $ lastErr e xs

-- | A 'minimum' that fails in the 'EitherT' monad
tryMinimum :: (Monad m, Ord a) => e -> [a] -> EitherT e m a
tryMinimum e xs = liftEither $ maximumErr e xs

-- | A 'maximum' that fails in the 'EitherT' monad
tryMaximum :: (Monad m, Ord a) => e -> [a] -> EitherT e m a
tryMaximum e xs = liftEither $ maximumErr e xs

-- | A 'foldr1' that fails in the 'EitherT' monad
tryFoldr1 :: (Monad m) => e -> (a -> a -> a) -> [a] -> EitherT e m a
tryFoldr1 e step xs = liftEither $ foldr1Err e step xs

-- | A 'foldl1' that fails in the 'EitherT' monad
tryFoldl1 :: (Monad m) => e -> (a -> a -> a) -> [a] -> EitherT e m a
tryFoldl1 e step xs = liftEither $ foldl1Err e step xs

-- | A 'foldl1'' that fails in the 'EitherT' monad
tryFoldl1' :: (Monad m) => e -> (a -> a -> a) -> [a] -> EitherT e m a
tryFoldl1' e step xs = liftEither $ foldl1Err' e step xs

-- | A ('!!') that fails in the 'EitherT' monad
tryAt :: (Monad m) => e -> [a] -> Int -> EitherT e m a
tryAt e xs n = liftEither $ atErr e xs n

-- | A 'read' that fails in the 'EitherT' monad
tryRead :: (Monad m, Read a) => e -> String -> EitherT e m a
tryRead e str = liftEither $ readErr e str

-- | An assertion that fails in the 'EitherT' monad
tryAssert :: (Monad m) => e -> Bool -> a -> EitherT e m a
tryAssert e p a = liftEither $ assertErr e p a