{-# LANGUAGE CPP                  #-}
{-# LANGUAGE ExplicitNamespaces   #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE PatternSynonyms      #-}
{-# LANGUAGE ViewPatterns         #-}

{-# OPTIONS_GHC -Wno-orphans #-}

{-# OPTIONS_HADDOCK show-extensions #-}

-- | 'T.ResultT' interfaces with 'Result'.
module Control.Monad.Trans.Result
  ( -- * The Result monad
    type T.Result
  , pattern Result
  , runResult
  , pattern Error
  , pattern Success
  , result
  , fromEither
  , toEither
  , fromSuccess
  , toMonadFail
    -- * The ResultT monad transformer
  , type T.ResultT
  , pattern ResultT
  , runResultT
  , mapResultT
    -- * Exception operations
  , T.throwE
  , T.catchE
    -- * Lifting other operations
  , T.liftCallCC
  , T.liftListen
  , T.liftPass
  ) where

import           Control.Monad.Trans.Except        (ExceptT (ExceptT), runExcept, runExceptT)
import qualified Control.Monad.Trans.Except.Result as T
import           Data.Functor.Identity             (Identity (runIdentity))
import qualified GHC.Show                          as S
import           Text.Read                         (Read (readPrec))
import qualified Text.Read                         as R
import qualified Text.Read.Lex                     as R

#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail (MonadFail)
#endif

instance {-# OVERLAPPING #-} Show a => Show (T.Result a) where
  showsPrec d (Error e)   = showParen (S.appPrec < d) $ showString "Error " . showsPrec S.appPrec1 e
  showsPrec d (Success a) = showParen (S.appPrec < d) $ showString "Success " . showsPrec S.appPrec1 a

instance {-# OVERLAPPING #-} Read a => Read (T.Result a) where
  readPrec =
    R.parens $
      R.prec S.appPrec (
        do
          R.lift $ R.expect $ R.Ident "Error"
          e <- R.step readPrec
          pure $ Error e
      )
      R.+++
      R.prec S.appPrec (
        do
          R.lift $ R.expect $ R.Ident "Success"
          a <- R.step readPrec
          pure $ Success a
      )

instance Semigroup (T.Result a) where
  Error _ <> a = a
  a <> _ = a
  {-# INLINE (<>) #-}

instance Monoid (T.Result a) where
  mempty = Error "mempty"
  {-# INLINE mempty #-}

  mappend = (<>)
  {-# INLINE mappend #-}

-- | Wrap @'D.Result' a@.
pattern Result :: Either String a -> T.Result a
pattern Result r <- (runIdentity . runExceptT . T.runResultT -> r)
  where Result r = T.ResultT $ ExceptT $ pure r

-- | Unrap @'T.Result' a@.
runResult :: T.Result a -> Either String a
runResult = runExcept . T.runResult
{-# INLINE runResult #-}

-- | 'Error' means errors and failures etc.
pattern Error :: String -> T.Result a
pattern Error e = Result (Left e)

-- | 'Success' means successes and OKs etc.
pattern Success :: a -> T.Result a
pattern Success a = Result (Right a)

{-# COMPLETE Error, Success #-}

-- | Case analysis for the 'Result' type.
--
-- ==== __Examples__
--
-- >>> let s = Success 0
-- >>> let e = Error "critical"
-- >>> result ("Bad: " ++) (("OK: " ++) . show) s
-- "OK: 0"
-- >>> result ("Bad: " ++) (("OK: " ++) . show) e
-- "Bad: critical"
result :: (String -> b) -> (a -> b) -> T.Result a -> b
result f _ (Error e)   = f e
result _ g (Success a) = g a
{-# INLINE result #-}

-- | Convert @'Either' 'String' a@ to @'Result' a@.
fromEither :: Either String a -> T.Result a
fromEither = Result
{-# INLINE fromEither #-}

-- | Convert @'Result' a@ to @'Either' 'String' a@.
toEither :: T.Result a -> Either String a
toEither = runResult
{-# INLINE toEither #-}

-- | Convert @'Result' a@ to @a@ with a default value.
fromSuccess :: a -> T.Result a -> a
fromSuccess _ (Success a) = a
fromSuccess a _           = a
{-# INLINE fromSuccess #-}

-- | Convert @'Result' a@ to @'MonadFail' m => m a@.
toMonadFail :: MonadFail m => T.Result a -> m a
toMonadFail (Success a) = pure a
toMonadFail (Error e)   = fail e
{-# INLINE toMonadFail #-}

-- | Construct and destruct 'T.Result'.
pattern ResultT :: Functor m => m (Either String a) -> T.ResultT m a
pattern ResultT m <- (runExceptT . T.runResultT -> m)
  where ResultT m = T.ResultT $ ExceptT m

{-# COMPLETE ResultT #-}

-- | Unwrap 'ResultT'.
runResultT :: Functor m => T.ResultT m a -> m (Either String a)
runResultT (ResultT m) = m
{-# INLINE runResultT #-}

-- | Map the unwrapped computation using the given function.
mapResultT :: (Functor m, Functor n) => (m (Either String a) -> n (Either String b)) -> T.ResultT m a -> T.ResultT n b
mapResultT f = T.mapResultT $ T.runResultT . ResultT . f . runResultT . T.ResultT
{-# INLINE mapResultT #-}