{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}

-- | The multi-valued version of mtl's RWS / RWST
module Control.Monad.Trans.MultiRWS.Strict
  (
  -- * MultiRWST
    MultiRWST(..)
  , MultiRWSTNull
  , MultiRWS
  -- * MonadMulti classes
  , MonadMultiReader(..)
  , MonadMultiWriter(..)
  , MonadMultiGet(..)
  , MonadMultiState(..)
  -- * run-functions (extracting from RWST)
  , runMultiRWST
  , runMultiRWSTASW
  , runMultiRWSTW
  , runMultiRWSTAW
  , runMultiRWSTSW
  , runMultiRWSTNil
  , runMultiRWSTNil_
  -- * with-functions (extending an RWST)
  , withMultiReader
  , withMultiReader_
  , withMultiReaders
  , withMultiReaders_
  , withMultiWriter
  , withMultiWriterAW
  , withMultiWriterWA
  , withMultiWriterW
  , withMultiWriters
  , withMultiWritersAW
  , withMultiWritersWA
  , withMultiWritersW
  , withMultiState
  , withMultiStateAS
  , withMultiStateSA
  , withMultiStateA
  , withMultiStateS
  , withMultiState_
  , withMultiStates
  , withMultiStatesAS
  , withMultiStatesSA
  , withMultiStatesA
  , withMultiStatesS
  , withMultiStates_
  -- * without-functions (reducing an RWST; inverse of with)
  , withoutMultiReader
  , withoutMultiState
  -- * inflate-functions (run simple transformer in MultiRWST)
  , inflateReader
  , inflateMultiReader
  , inflateWriter
  , inflateMultiWriter
  , inflateState
  , inflateMultiState
  -- * other functions
  , mapMultiRWST
  , mGetRawR
  , mGetRawW
  , mGetRawS
  , mPutRawR
  , mPutRawW
  , mPutRawS
  )
where



import Data.HList.HList
import Data.HList.ContainsType

import Control.Monad.Trans.MultiReader.Class  ( MonadMultiReader(..) )
import Control.Monad.Trans.MultiWriter.Class  ( MonadMultiWriter(..) )
import Control.Monad.Trans.MultiState.Class
import Control.Monad.Trans.MultiReader.Strict ( MultiReaderT(..)
                                              , runMultiReaderT )
import Control.Monad.Trans.MultiWriter.Strict ( MultiWriterT(..)
                                              , runMultiWriterT )
import Control.Monad.Trans.MultiState.Strict  ( MultiStateT(..)
                                              , runMultiStateT )

import Control.Monad.State.Strict      ( StateT(..)
                                       , MonadState(..)
                                       , execStateT
                                       , evalStateT
                                       , mapStateT )
import Control.Monad.Reader            ( ReaderT(..) )
import Control.Monad.Writer.Strict     ( WriterT(..) )
import Control.Monad.Trans.Class       ( MonadTrans
                                       , lift )

import Data.Functor.Identity           ( Identity )

import Control.Applicative             ( Applicative(..)
                                       , Alternative(..)
                                       )
import Control.Monad                   ( MonadPlus(..)
                                       , liftM
                                       , ap
                                       , void )
import Control.Monad.Base              ( MonadBase(..)
                                       , liftBaseDefault
                                       )
import Control.Monad.Trans.Control     ( MonadTransControl(..)
                                       , MonadBaseControl(..)
                                       , ComposeSt
                                       , defaultLiftBaseWith
                                       , defaultRestoreM
                                       )
import Control.Monad.Fix               ( MonadFix(..) )
import Control.Monad.IO.Class          ( MonadIO(..) )

import Data.Monoid



newtype MultiRWST r w s m a = MultiRWST {
  runMultiRWSTRaw :: StateT (HList r, HList w, HList s) m a
}

type MultiRWSTNull = MultiRWST '[] '[] '[]

type MultiRWS r w s = MultiRWST r w s Identity

instance (Functor f) => Functor (MultiRWST r w s f) where
  fmap f = MultiRWST . fmap f . runMultiRWSTRaw

instance (Applicative m, Monad m) => Applicative (MultiRWST r w s m) where
  pure = MultiRWST . pure
  (<*>) = ap

instance (Monad m) => Monad (MultiRWST r w s m) where
  return = pure
  k >>= f = MultiRWST $ runMultiRWSTRaw k >>= runMultiRWSTRaw . f

instance MonadTrans (MultiRWST r w s) where
  lift = MultiRWST . lift

instance
#if MIN_VERSION_base(4,8,0)
  {-# OVERLAPPING #-}
#endif
    (Monad m, ContainsType a r)
      => MonadMultiReader a (MultiRWST r w s m) where
  mAsk = MultiRWST $ liftM (\(r,_,_) -> getHListElem r) get

instance
#if MIN_VERSION_base(4,8,0)
  {-# OVERLAPPING #-}
#endif
    (Monad m, ContainsType a w, Monoid a)
      => MonadMultiWriter a (MultiRWST r w s m) where
  mTell v = MultiRWST $ do
    (r,w,s) <- get
    let !x' = getHListElem w `mappend` v
    put $ (r, setHListElem x' w, s)

instance
#if MIN_VERSION_base(4,8,0)
  {-# OVERLAPPING #-}
#endif
    (Monad m, ContainsType a s)
      => MonadMultiGet a (MultiRWST r w s m) where
  mGet = MultiRWST $ do
    (_,_,s) <- get
    return $ getHListElem s

instance
#if MIN_VERSION_base(4,8,0)
  {-# OVERLAPPING #-}
#endif
    (Monad m, ContainsType a s)
      => MonadMultiState a (MultiRWST r w s m) where
  mSet !v = MultiRWST $ do
    (r,w,s) <- get
    put (r, w, setHListElem v s)

instance MonadFix m => MonadFix (MultiRWST r w s m) where
  mfix f = MultiRWST $ mfix (runMultiRWSTRaw . f)

-- methods

runMultiRWST    :: ( Monad m
                   , Monoid (HList w)
                   )
                => HList r
                -> HList s
                -> MultiRWST r w s m a
                -> m (a, HList s, HList w)
runMultiRWSTASW :: ( Monad m
                   , Monoid (HList w)
                   )
                => HList r
                -> HList s
                -> MultiRWST r w s m a
                -> m (a, HList s, HList w)
runMultiRWSTW   :: ( Monad m
                   , Monoid (HList w)
                   )
                => HList r
                -> HList s
                -> MultiRWST r w s m a
                -> m (HList w)
runMultiRWSTAW  :: ( Monad m
                    , Monoid (HList w)
                    )
                 => HList r
                 -> HList s
                 -> MultiRWST r w s m a
                 -> m (a, HList w)
runMultiRWSTSW  :: ( Monad m
                    , Monoid (HList w)
                    )
                 => HList r
                 -> HList s
                 -> MultiRWST r w s m a
                 -> m (HList s, HList w)

runMultiRWSTNil :: ( Monad m )
                => MultiRWST '[] '[] '[] m a
                -> m a
runMultiRWSTNil_ :: ( Monad m, Functor m )
                 => MultiRWST '[] '[] '[] m a
                 -> m ()
runMultiRWST = runMultiRWSTASW
runMultiRWSTASW r s k = do
  (x, (_, w, s')) <- runStateT (runMultiRWSTRaw k) (r, mempty, s)
  return $ (x, s', w)
runMultiRWSTW r s k = do
  (_, w, _) <- execStateT (runMultiRWSTRaw k) (r, mempty, s)
  return $ w
runMultiRWSTAW r s k = do
  (x, (_, w, _)) <- runStateT (runMultiRWSTRaw k) (r, mempty, s)
  return $ (x, w)
runMultiRWSTSW r s k = do
  (_, w, s') <- execStateT (runMultiRWSTRaw k) (r, mempty, s)
  return $ (s', w)
runMultiRWSTNil  k = evalStateT (runMultiRWSTRaw k) (HNil, HNil, HNil)
runMultiRWSTNil_ k = void $ runStateT (runMultiRWSTRaw k) (HNil, HNil, HNil)

withMultiReader  :: Monad m => r -> MultiRWST (r ': rs) w s m a -> MultiRWST rs w s m a
withMultiReader_ :: (Functor m, Monad m) => r -> MultiRWST (r ': rs) w s m a -> MultiRWST rs w s m ()
withMultiReaders  :: Monad m => HList r1 -> MultiRWST (Append r1 r2) w s m a -> MultiRWST r2 w s m a
withMultiReaders_ :: (Functor m, Monad m) => HList r1 -> MultiRWST (Append r1 r2) w s m a -> MultiRWST r2 w s m ()
withMultiReader x k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (x :+: r, w, s)
  put (r, w', s')
  return a
withMultiReader_ x k = MultiRWST $ do
  (r, w, s) <- get
  (_, w', s') <- lift $ execStateT (runMultiRWSTRaw k) (x :+: r, w, s)
  put (r, w', s')
withMultiReaders HNil       = id
withMultiReaders (x :+: xs) = withMultiReaders xs . withMultiReader x
withMultiReaders_ HNil       = void
withMultiReaders_ (x :+: xs) = withMultiReaders_ xs . withMultiReader x

withMultiWriter   :: (Monoid w, Monad m) => MultiRWST r (w ': ws) s m a -> MultiRWST r ws s m (a, w)
withMultiWriterAW :: (Monoid w, Monad m) => MultiRWST r (w ': ws) s m a -> MultiRWST r ws s m (a, w)
withMultiWriterWA :: (Monoid w, Monad m) => MultiRWST r (w ': ws) s m a -> MultiRWST r ws s m (w, a)
withMultiWriterW  :: (Monoid w, Monad m) => MultiRWST r (w ': ws) s m a -> MultiRWST r ws s m w
withMultiWriters   :: forall r w1 w2 s m a
                    . (Monoid (HList w1), Monad m, HInit w1)
                   => MultiRWST r (Append w1 w2) s m a
                   -> MultiRWST r w2 s m (a, HList w1)
withMultiWritersAW :: forall r w1 w2 s m a
                    . (Monoid (HList w1), Monad m, HInit w1)
                   => MultiRWST r (Append w1 w2) s m a
                   -> MultiRWST r w2 s m (a, HList w1)
withMultiWritersWA :: forall r w1 w2 s m a
                    . (Monoid (HList w1), Monad m, HInit w1)
                   => MultiRWST r (Append w1 w2) s m a
                   -> MultiRWST r w2 s m (HList w1, a)
withMultiWritersW  :: forall r w1 w2 s m a
                    . (Monoid (HList w1), Monad m, HInit w1)
                   => MultiRWST r (Append w1 w2) s m a
                   -> MultiRWST r w2 s m (HList w1)
withMultiWriter = withMultiWriterAW
withMultiWriterAW k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, mempty :+: w, s)
  case w' of
    x' :+: wr' -> do
      put (r, wr', s')
      return (a, x')
withMultiWriterWA k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, mempty :+: w, s)
  case w' of
    x' :+: wr' -> do
      put (r, wr', s')
      return (x', a)
withMultiWriterW k = MultiRWST $ do
  (r, w, s) <- get
  (_, w', s') <- lift $ execStateT (runMultiRWSTRaw k) (r, mempty :+: w, s)
  case w' of
    x' :+: wr' -> do
      put (r, wr', s')
      return x'
withMultiWriters = withMultiWritersAW
withMultiWritersAW k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, hAppend (mempty :: HList w1) w, s)
  let (o, wr') = hSplit w'
  put (r, wr', s')
  return (a, o)
withMultiWritersWA k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, hAppend (mempty :: HList w1) w, s)
  let (o, wr') = hSplit w'
  put (r, wr', s')
  return (o, a)
withMultiWritersW k = MultiRWST $ do
  (r, w, s) <- get
  (_, w', s') <- lift $ execStateT (runMultiRWSTRaw k) (r, hAppend (mempty :: HList w1) w, s)
  let (o, wr') = hSplit w'
  put (r, wr', s')
  return o

withMultiState   :: Monad m => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m (a, s)
withMultiStateAS :: Monad m => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m (a, s)
withMultiStateSA :: Monad m => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m (s, a)
withMultiStateA  :: Monad m => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m a
withMultiStateS  :: Monad m => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m s
withMultiState_  :: (Functor m, Monad m) => s -> MultiRWST r w (s ': ss) m a -> MultiRWST r w ss m ()
withMultiStates   :: Monad m => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m (a, HList s1)
withMultiStatesAS :: Monad m => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m (a, HList s1)
withMultiStatesSA :: Monad m => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m (HList s1, a)
withMultiStatesA  :: Monad m => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m a
withMultiStatesS  :: Monad m => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m (HList s1)
withMultiStates_  :: (Functor m, Monad m) => HList s1 -> MultiRWST r w (Append s1 s2) m a -> MultiRWST r w s2 m ()
withMultiState = withMultiStateAS
withMultiStateAS x k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, w, (x :+: s))
  case s' of
    x' :+: sr' -> do
      put (r, w', sr')
      return (a, x')
withMultiStateSA x k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, w, (x :+: s))
  case s' of
    x' :+: sr' -> do
      put (r, w', sr')
      return (x', a)
withMultiStateA x k = MultiRWST $ do
  (r, w, s) <- get
  (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, w, (x :+: s))
  case s' of
    _ :+: sr' -> do
      put (r, w', sr')
      return a
withMultiStateS x k = MultiRWST $ do
  (r, w, s) <- get
  (_, w', s') <- lift $ execStateT (runMultiRWSTRaw k) (r, w, (x :+: s))
  case s' of
    x' :+: sr' -> do
      put (r, w', sr')
      return x'
withMultiState_ x k = MultiRWST $ do
  (r, w, s) <- get
  (_, w', s') <- lift $ execStateT (runMultiRWSTRaw k) (r, w, (x :+: s))
  case s' of _ :+: sr' -> put (r, w', sr')
withMultiStates                = withMultiStatesAS
withMultiStatesAS HNil       k = do a <- k; return (a, HNil)
withMultiStatesAS (x :+: xs) k = do
  ((a, x'), xs') <- withMultiStates xs $ withMultiState x k
  return (a, x' :+: xs')
withMultiStatesSA HNil       k = do a <- k; return (HNil, a)
withMultiStatesSA (x :+: xs) k = do
  ((a, x'), xs') <- withMultiStates xs $ withMultiState x k
  return (x' :+: xs', a)
withMultiStatesA HNil       = id
withMultiStatesA (x :+: xs) = withMultiStatesA xs . withMultiStateA x
withMultiStatesS HNil       k = k >> return HNil
withMultiStatesS (x :+: xs) k = do
  (x', xs') <- withMultiStates xs $ withMultiStateS x k
  return (x' :+: xs')
withMultiStates_ HNil       = void
withMultiStates_ (x :+: xs) = withMultiStates_ xs . withMultiState_ x

withoutMultiReader :: Monad m => MultiRWST rs w s m a -> MultiRWST (r ': rs) w s m a
withoutMultiReader k = MultiRWST $ get >>= \case
  (rs@(_ :+: rr), w, s) -> do
    (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (rr, w, s)
    put (rs, w', s')
    return a

withoutMultiState :: Monad m => MultiRWST r w ss m a -> MultiRWST r w (s ': ss) m a
withoutMultiState k = MultiRWST $ get >>= \case
  (r, w, s :+: sr) -> do
    (a, (_, w', s')) <- lift $ runStateT (runMultiRWSTRaw k) (r, w, sr)
    put (r, w', s :+: s')
    return a

inflateReader :: (Monad m, ContainsType r rs)
              => ReaderT r m a
              -> MultiRWST rs w s m a
inflateReader k = mAsk >>= lift . runReaderT k
inflateMultiReader :: Monad m => MultiReaderT r m a -> MultiRWST r w s m a
inflateMultiReader k = do
  r <- mGetRawR
  lift $ runMultiReaderT r k
inflateWriter :: (Monad m, ContainsType w ws, Monoid w)
              => WriterT w m a
              -> MultiRWST r ws s m a
inflateWriter k = do
  (x, w) <- lift $ runWriterT k
  mTell w
  return x
inflateMultiWriter :: (Functor m, Monad m, Monoid (HList w))
                   => MultiWriterT w m a
                   -> MultiRWST r w s m a
inflateMultiWriter k = do
  (x, w) <- lift $ runMultiWriterT k
  mPutRawW w
  return x
inflateState :: (Monad m, MonadTrans t, MonadMultiState s (t m))
             => StateT s m a
             -> t m a
inflateState k = do
  s <- mGet
  (x, s') <- lift $ runStateT k s
  mSet s'
  return x
inflateMultiState :: (Functor m, Monad m)
                  => MultiStateT s m a
                  -> MultiRWST r w s m a
inflateMultiState k = do
  s <- mGetRawS
  (x, s') <- lift $ runMultiStateT s k
  mPutRawS s'
  return x

mGetRawR :: Monad m => MultiRWST r w s m (HList r)
mPutRawR :: Monad m => HList r -> MultiRWST r w s m ()
mGetRawW :: Monad m => MultiRWST r w s m (HList w)
mPutRawW :: Monad m => HList w -> MultiRWST r w s m ()
mGetRawS :: Monad m => MultiRWST r w s m (HList s)
mPutRawS :: Monad m => HList s -> MultiRWST r w s m ()
mGetRawR = (\(r, _, _) -> r) `liftM` MultiRWST get
mPutRawR r = MultiRWST $ do
  ~(_, w, s) <- get
  put (r, w, s)
mGetRawW = (\(_, w, _) -> w) `liftM` MultiRWST get
mPutRawW w = MultiRWST $ do
  ~(r, _, s) <- get
  put (r, w, s)
mGetRawS = (\(_, _, s) -> s) `liftM` MultiRWST get
mPutRawS s = MultiRWST $ do
  ~(r, w, _) <- get
  put (r, w, s)

mapMultiRWST :: (ss ~ (HList r, HList w, HList s))
             => (m (a, ss) -> m' (a', ss))
             -> MultiRWST r w s m a
             -> MultiRWST r w s m' a'
mapMultiRWST f = MultiRWST . mapStateT f . runMultiRWSTRaw

-- foreign lifting instances

instance MonadIO m => MonadIO (MultiRWST r w s m) where
  liftIO = lift . liftIO

instance (Functor m, Applicative m, MonadPlus m) => Alternative (MultiRWST r w s m) where
  empty = lift mzero
  MultiRWST m <|> MultiRWST n = MultiRWST $ m <|> n

instance MonadPlus m => MonadPlus (MultiRWST r w s m) where
  mzero = MultiRWST $ mzero
  MultiRWST m `mplus` MultiRWST n = MultiRWST $ m `mplus` n

instance MonadBase b m => MonadBase b (MultiRWST r w s m) where
  liftBase = liftBaseDefault

instance MonadTransControl (MultiRWST r w s) where
  type StT (MultiRWST r w s) a = (a, (HList r, HList w, HList s))
  liftWith f = MultiRWST $ liftWith $ \s -> f $ \r -> s $ runMultiRWSTRaw r
  restoreT = MultiRWST . restoreT

instance MonadBaseControl b m => MonadBaseControl b (MultiRWST r w s m) where
  type StM (MultiRWST r w s m) a = ComposeSt (MultiRWST r w s) m a
  liftBaseWith = defaultLiftBaseWith
  restoreM = defaultRestoreM
