{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-deprecations #-}

module Control.Monad.Introspect.Class where

import Control.Applicative
import Control.Monad hiding (fail)
import Control.Monad.Error (ErrorT(..), mapErrorT)
import Control.Monad.Error.Class
import Control.Monad.Except (ExceptT(..), mapExceptT)
import Control.Monad.Trans.Class
import Control.Monad.Trans.Compose
import Control.Monad.Trans.Cont (ContT(..), mapContT)
import Control.Monad.Trans.Identity (IdentityT(..), mapIdentityT)
import Control.Monad.Trans.List (ListT(..), mapListT)
import Control.Monad.Trans.Maybe (MaybeT(..), mapMaybeT)
import Control.Monad.Trans.Reader (ReaderT(..), mapReaderT)
import Control.Monad.Trans.RWS (RWST(..), mapRWST)
import qualified Control.Monad.Trans.State.Lazy as L
import qualified Control.Monad.Trans.State.Strict as S
import qualified Control.Monad.Trans.Writer.Lazy as L
import qualified Control.Monad.Trans.Writer.Strict as S
import Data.Coerce
import Prelude hiding (fail)
import Data.Type.Role.Representational

-- * Abstract interface

-- | A monad with introspection capability is able to query an environment @r@
-- that is parameterized by the monad itself, i.e. can contain effectful
-- functions.
--
-- The caveat is that sometimes the monad changes (e.g. we locally run a
-- transformer, or we globally run our transformer steck), so the monad in the
-- environment can "desync" from the ambient monad. This warrants a more general
-- class: 'MonadIntrospectTrans', of which 'MonadIntrospect' is a special case.
--
-- The machinery sometimes requires newtype wrapping/unwrapping the monad that
-- goes to the environment. We use/require 'Coercible' for that, as the
-- "functorial" operations are deemed expensive. The constraint
-- @'Representational' r@ ensures that the environment can be coerced provided
-- the monad can be coerced.
--
-- Otherwise the interface is identical to that of
-- 'Control.Monad.Reader.Class.MonadReader'.
class (Representational r, MonadIntrospectTrans IdentityT r m)
  => MonadIntrospect (r :: (* -> *) -> *) (m :: * -> *) where
  -- | Essentially 'Control.Monad.Reader.Class.ask'.
  introspect :: m (r m)
  -- | Essentially 'Control.Monad.Reader.Class.local'.
  substitute :: (r m -> r m) -> m a -> m a

-- | If the ambient monad is @m@ and the environment @r@ has additional effects
-- @t@, we can ask for that environment, or substitute it. Multiple (or zero)
-- effects can be combined into a single @t@ with 'ComposeT' (or 'IdentityT').
class (Monad m, MonadTrans t) => MonadIntrospectTrans
  (t :: (* -> *) -> * -> *)
  (r :: (* -> *) -> *)
  (m :: * -> *)
  | m -> t where
  -- | Essentially 'Control.Monad.Reader.Class.ask'.
  introspectTrans :: m (r (t m))
  -- | Essentially 'Control.Monad.Reader.Class.local'.
  substituteTrans :: (r (t m) -> r (t m)) -> m a -> m a

instance (Representational r, MonadIntrospectTrans IdentityT r m)
  => MonadIntrospect r m where
  introspect = liftTransEnv <$> introspectTrans
  substitute = substituteTrans . liftTransMapper

-- * Utility functions for coercing environments

liftTransEnv :: (Representational r, Coercible m n) => r m -> r n
liftTransEnv = coerce

liftTransMapper :: (Representational r, Coercible m n)
  => (r m -> r m) -> r n -> r n
liftTransMapper = coerce

-- Other effects proxy MonadIntrospect

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t IdentityT) r m )
  => MonadIntrospectTrans t r (IdentityT m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapIdentityT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t ListT) r m )
  => MonadIntrospectTrans t r (ListT m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapListT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (ContT e)) r m )
  => MonadIntrospectTrans t r (ContT e m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapContT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (ExceptT e)) r m )
  => MonadIntrospectTrans t r (ExceptT e m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapExceptT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , Error e
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (ErrorT e)) r m )
  => MonadIntrospectTrans t r (ErrorT e m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapErrorT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (ReaderT e)) r m )
  => MonadIntrospectTrans t r (ReaderT e m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapReaderT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t MaybeT) r m )
  => MonadIntrospectTrans t r (MaybeT m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapMaybeT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (L.StateT s)) r m )
  => MonadIntrospectTrans t r (L.StateT s m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = L.mapStateT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (S.StateT s)) r m )
  => MonadIntrospectTrans t r (S.StateT s m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = S.mapStateT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , Monoid w
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (L.WriterT w)) r m )
  => MonadIntrospectTrans t r (L.WriterT w m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = L.mapWriterT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , Monoid w
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (S.WriterT w)) r m )
  => MonadIntrospectTrans t r (S.WriterT w m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = S.mapWriterT . substituteTrans . liftTransMapper

instance
  ( Representational r
  , Monoid w
  , MonadTrans t
  , MonadIntrospectTrans (ComposeT t (RWST e w s)) r m )
  => MonadIntrospectTrans t r (RWST e w s m) where
  introspectTrans = lift $ liftTransEnv <$> introspectTrans
  substituteTrans = mapRWST . substituteTrans . liftTransMapper