-- | This module provides the proxy transformer equivalent of 'EitherT'.

{-# LANGUAGE KindSignatures, CPP #-}

module Control.Proxy.Trans.Either (
    -- * EitherP
    EitherP(..),
    runEitherK,
    -- * Either operations
    left,
    right,
    -- * Symmetric monad
    -- $symmetry
    throw,
    catch,
    handle
    ) where

import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))
import Control.Monad (MonadPlus(mzero, mplus))
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Trans.Class (MonadTrans(lift))
import Control.MFunctor (MFunctor(hoist))
import Control.PFunctor (PFunctor(hoistP))
import Control.Proxy.Class
import Control.Proxy.Trans (ProxyTrans(liftP))
#if MIN_VERSION_base(4,6,0)
#else
import Prelude hiding (catch)
#endif

-- | The 'Either' proxy transformer
newtype EitherP e p a' a b' b (m :: * -> *) r
  = EitherP { runEitherP :: p a' a b' b m (Either e r) }

instance (Proxy              p, Monad m)
       => Functor (EitherP e p a' a b' b m) where
    fmap f p = EitherP (
        runEitherP p ?>= \e ->
        return_P (case e of
            Left  l -> Left l
            Right r -> Right (f r) ) )
 -- fmap f = EitherP . liftM (fmap f) . runEitherP

instance (Proxy                  p, Monad m)
       => Applicative (EitherP e p a' a b' b m) where
    pure = return
    fp <*> xp = EitherP (
        runEitherP fp ?>= \e1 ->
        case e1 of
            Left  l -> return_P (Left l)
            Right f ->
                 runEitherP xp ?>= \e2 ->
                 return_P (case e2 of
                      Left l  -> Left  l
                      Right x -> Right (f x) ) )
 -- fp <*> xp = EitherP ((<*>) <$> (runEitherP fp) <*> (runEitherP xp))

instance (Proxy            p, Monad m)
       => Monad (EitherP e p a' a b' b m) where
    return = return_P
    (>>=) = (?>=)

instance (MonadPlusP             p, Monad m)
       => Alternative (EitherP e p a' a b' b m) where
    empty = mzero
    (<|>) = mplus

instance (MonadPlusP            p )
       => MonadPlusP (EitherP e p) where
    mzero_P = EitherP mzero_P
    mplus_P m1 m2 = EitherP (mplus_P (runEitherP m1) (runEitherP m2))

instance (MonadPlusP           p, Monad m)
       => MonadPlus (EitherP e p a' a b' b m) where
    mzero = mzero_P
    mplus = mplus_P

instance (Proxy                 p )
       => MonadTrans (EitherP e p a' a b' b) where
    lift = lift_P

instance (MonadIOP            p )
       => MonadIOP (EitherP e p) where
    liftIO_P m = EitherP (liftIO_P (m >>= \x -> return (Right x)))
 -- liftIO = EitherP . liftIO . liftM Right

instance (MonadIOP           p, MonadIO m)
       => MonadIO (EitherP e p a' a b' b m) where
    liftIO = liftIO_P

instance (Proxy               p )
       => MFunctor (EitherP e p a' a b' b) where
    hoist = hoist_P

instance (Proxy            p )
       => Proxy (EitherP e p) where
    p1 >-> p2 = \c'1 -> EitherP (
        ((\b' -> runEitherP (p1 b')) >-> (\c'2 -> runEitherP (p2 c'2))) c'1 )
 -- p1 >-> p2 = (EitherP .) $ runEitherP . p1 >-> runEitherP . p2

    p1 >~> p2 = \c'1 -> EitherP (
        ((\b' -> runEitherP (p1 b')) >~> (\c'2 -> runEitherP (p2 c'2))) c'1 )
 -- p1 >~> p2 = (EitherP .) $ runEitherP . p1 >~> runEitherP . p2

    request = \a' -> EitherP (request a' ?>= \a  -> return_P (Right a ))
    respond = \b  -> EitherP (respond b  ?>= \b' -> return_P (Right b'))

    return_P = right
    m ?>= f = EitherP (
        runEitherP m ?>= \e ->
        runEitherP (case e of
            Left  l -> left l
            Right r -> f    r ) )

    lift_P m = EitherP (lift_P (m >>= \x -> return (Right x)))
 -- lift = EitherP . lift . liftM Right

    hoist_P nat p = EitherP (hoist_P nat (runEitherP p))
 -- hoist nat = EitherP . hoist nat . runEitherP

instance ProxyTrans (EitherP e) where
    liftP p = EitherP (p ?>= \x -> return_P (Right x))
 -- liftP = EitherP . liftM Right

instance PFunctor (EitherP e) where
    hoistP nat = EitherP . nat . runEitherP

-- | Run an 'EitherP' \'@K@\'leisi arrow, returning either a 'Left' or 'Right'
runEitherK
 :: (q -> EitherP e p a' a b' b m r) -> (q -> p a' a b' b m (Either e r))
runEitherK p q = runEitherP (p q)
-- runEitherK = (runEitherP .)

-- | Abort the computation and return a 'Left' result
left :: (Monad m, Proxy p) => e -> EitherP e p a' a b' b m r
left e = EitherP (return_P (Left e))
-- left = EitherP . return . Left

-- | Synonym for 'return'
right :: (Monad m, Proxy p) => r -> EitherP e p a' a b' b m r
right r = EitherP (return_P (Right r))
-- right = EitherP . return . Right

{- $symmetry
    'EitherP' forms a second symmetric monad over the left type variable.

    'throw' is symmetric to 'return'

    'catch' is symmetric to ('>>=')

    These two functions obey the monad laws:

> catch m throw = m
>
> catch (throw e) f = f e
>
> catch (catch m f) g = catch m (\e -> catch (f e) g)
-}

-- | Synonym for 'left'
throw :: (Monad m, Proxy p) => e -> EitherP e p a' a b' b m r
throw = left

-- | Resume from an aborted operation
catch
 :: (Monad m, Proxy p)
 => EitherP e p a' a b' b m r        -- ^ Original computation
 -> (e -> EitherP f p a' a b' b m r) -- ^ Handler
 -> EitherP f p a' a b' b m r        -- ^ Handled computation
catch m f = EitherP (
    runEitherP m ?>= \e ->
    runEitherP (case e of
        Left  l -> f     l
        Right r -> right r ))

-- | 'catch' with the arguments flipped
handle
 :: (Monad m, Proxy p)
 => (e -> EitherP f p a' a b' b m r) -- ^ Handler
 -> EitherP e p a' a b' b m r        -- ^ Original computation
 -> EitherP f p a' a b' b m r        -- ^ Handled computation
handle f m = catch m f
-- handle = flip catch