{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances   #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE RebindableSyntax       #-}
{-# LANGUAGE TypeFamilies           #-}

-- | This module duplicates the Control.Monad.Reader module from the mtl, for
-- constrained monads.
module Control.Monad.Constrained.Reader
  (MonadReader(..)
  ,ReaderT(..)
  ,Reader
  )where

import           GHC.Exts

import           Control.Monad.Constrained
import           Control.Monad.Constrained.Trans

import           Control.Monad.Trans.Reader hiding (reader, ask, local)

import qualified Control.Monad.Trans.State.Lazy   as State.Lazy
import qualified Control.Monad.Trans.State.Strict as State.Strict
import qualified Control.Monad.Trans.Cont         as Cont
import qualified Control.Monad.Trans.Identity     as Identity
import qualified Control.Monad.Trans.Maybe        as Maybe
import qualified Control.Monad.Trans.Except       as Except

import qualified Prelude

-- | A class for reader monads.
class Monad m =>
      MonadReader r m  | m -> r where
    type ReaderSuitable m a :: Constraint
    {-# MINIMAL reader , local #-}
    -- | Retrieves the environment
    ask
        :: (ReaderSuitable m r)
        => m r
    ask = reader id
    -- | Executes a computation in a modified environment.
    local
        :: (ReaderSuitable m a, ReaderSuitable m r)
        => (r -> r) -- ^ The function to modify the environment.
        -> m a      -- ^ @Reader@ to run in the modified environment.
        -> m a

    -- | Retrieves a function of the current environment.
    reader
        :: (ReaderSuitable m r, ReaderSuitable m a)
        => (r -> a) -- ^ The selector function to apply to the environment.
        -> m a

instance MonadReader r ((->) r) where
    type ReaderSuitable ((->) r) a = ()
    ask = id
    local f m = m . f
    reader = id

instance Monad m => MonadReader r (ReaderT r m) where
    type ReaderSuitable (ReaderT r m) a = Suitable m a
    ask = ReaderT pure
    local = local
    reader f = ReaderT (pure . f)

instance MonadReader r' m =>
         MonadReader r' (Cont.ContT r m) where
    type ReaderSuitable (Cont.ContT r m) a
        = (ReaderSuitable m a, Suitable m r, ReaderSuitable m r)
    ask = lift ask
    local = liftLocal ask local
    reader = lift . reader

liftLocal
    :: (Monad m, Suitable m r)
    => m r'
    -> ((r' -> r') -> m r -> m r)
    -> (r' -> r')
    -> Cont.ContT r m a
    -> Cont.ContT r m a
liftLocal ask' local' f m =
    Cont.ContT $
    \c -> do
        r <- ask'
        local' f (Cont.runContT m (local' (const r) . c))

instance (MonadReader r m, Prelude.Monad (Unconstrained m)) =>
         MonadReader r (Except.ExceptT e m) where
    type ReaderSuitable (Except.ExceptT e m) a
        = (ReaderSuitable m a, Suitable m (Either e a), ReaderSuitable m (Either e a))
    ask = lift ask
    local = Except.mapExceptT . local
    reader = lift . reader

instance MonadReader r m => MonadReader r (Identity.IdentityT m) where
    type ReaderSuitable (Identity.IdentityT m) a = ReaderSuitable m a
    ask = lift ask
    local = Identity.mapIdentityT . local
    reader = lift . reader

instance (MonadReader r m, Prelude.Monad (Unconstrained m)) =>
         MonadReader r (Maybe.MaybeT m) where
    type ReaderSuitable (Maybe.MaybeT m) a
        = (ReaderSuitable m a
          ,ReaderSuitable m (Maybe a)
          ,Suitable m (Maybe a))
    ask = lift ask
    local = Maybe.mapMaybeT . local
    reader = lift . reader

instance (MonadReader r m, Prelude.Monad (Unconstrained m)) =>
         MonadReader r (State.Lazy.StateT s m) where
    type ReaderSuitable (State.Lazy.StateT s m) a
        = (ReaderSuitable m a
          ,ReaderSuitable m (a,s)
          ,Suitable m (a,s))
    ask = lift ask
    local = State.Lazy.mapStateT . local
    reader = lift . reader

instance (MonadReader r m, Prelude.Monad (Unconstrained m)) =>
         MonadReader r (State.Strict.StateT s m) where
    type ReaderSuitable (State.Strict.StateT s m) a
        = (ReaderSuitable m a
          ,ReaderSuitable m (a,s)
          ,Suitable m (a,s))
    ask = lift ask
    local = State.Strict.mapStateT . local
    reader = lift . reader