module Control.Monad.Ghc ( Ghc, runGhc,
                           GhcT, runGhcT,
                           GHC.GhcMonad(..),
                           module Control.Monad.Trans )

where

#if __GLASGOW_HASKELL__ < 706
import Prelude hiding ( catch )
#endif

import qualified Control.Exception.Extensible as E

import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import qualified Control.Monad.Trans as MTL

import Control.Monad.Catch

import qualified GHC ( runGhc, runGhcT )
import qualified MonadUtils as GHC
import qualified Exception  as GHC
#if __GLASGOW_HASKELL__ >= 702
import qualified GhcMonad   as GHC
#else
import qualified HscTypes   as GHC
#endif

#if __GLASGOW_HASKELL__ >= 706
import qualified DynFlags as GHC
#endif

newtype Ghc a = Ghc{ unGhc :: GHC.Ghc a }
    deriving (Functor
             ,Monad
#if __GLASGOW_HASKELL__ < 702
             ,GHC.WarnLogMonad
#elif __GLASGOW_HASKELL__ >= 706
             ,GHC.HasDynFlags
#endif
             ,GHC.ExceptionMonad
#if __GLASGOW_HASKELL__ < 708
             ,GHC.MonadIO
#else
             ,MTL.MonadIO
             ,Applicative
#endif
             ,GHC.GhcMonad)


#if __GLASGOW_HASKELL__ < 708
instance Applicative Ghc where
  pure  = return
  (<*>) = ap

instance MTL.MonadIO Ghc where
    liftIO = GHC.liftIO
#endif

instance MonadThrow Ghc where
    throwM  = liftIO . E.throwIO

instance MonadCatch Ghc where
    catch   = GHC.gcatch

instance MonadMask Ghc where
#if __GLASGOW_HASKELL__ >= 700
    -- @gmask@ is available...
    -- ...but it doesn't have a rank-n type like @mask@, so we need
    -- to use @Control.Exception.mask@ directly... (sigh)
    -- (this is type-directed, write only code)
    mask f = wrap $ \s ->
               mask $ \io_restore ->
                 unwrap (f $ \m -> (wrap $ \s' -> io_restore (unwrap m s'))) s
     where
        wrap   = Ghc . GHC.Ghc
        unwrap = GHC.unGhc . unGhc
#else
    -- this implementation, of course, offers less guarantees than the real @mask@,
    -- but we have no @mask@ available in the first place!
    mask io = GHC.gblock $ io GHC.gunblock
#endif

    uninterruptibleMask = mask

runGhc :: Maybe FilePath -> Ghc a -> IO a
runGhc f (Ghc m) = GHC.runGhc f m

newtype GhcT m a = GhcT { unGhcT :: GHC.GhcT (MTLAdapter m) a }
                 deriving (Functor
                          ,Monad
#if __GLASGOW_HASKELL__ >= 706
                          ,GHC.HasDynFlags
#endif
                          )

instance (Functor m, Monad m) => Applicative (GhcT m) where
  pure  = return
  (<*>) = ap

runGhcT :: (Functor m, MonadIO m, MonadCatch m, MonadMask m) => Maybe FilePath -> GhcT m a -> m a
runGhcT f = unMTLA . GHC.runGhcT f . unGhcT

instance MTL.MonadTrans GhcT where
    lift = GhcT . GHC.liftGhcT . MTLAdapter

instance MTL.MonadIO m => MTL.MonadIO (GhcT m) where
    liftIO = GhcT . GHC.liftIO

#if __GLASGOW_HASKELL__ < 708
  -- ghc started using transformers at some point
instance MTL.MonadIO m => GHC.MonadIO (GhcT m) where
    liftIO = MTL.liftIO
#endif

instance MonadCatch m => MonadThrow (GhcT m) where
    throwM = lift . throwM

instance (MonadIO m,MonadCatch m, MonadMask m) => MonadCatch (GhcT m) where
    m `catch` f = GhcT ((unGhcT m) `GHC.gcatch` (unGhcT . f))

instance (MonadIO m, MonadMask m) => MonadMask (GhcT m) where
    mask f = wrap $ \s ->
               mask $ \io_restore ->
                 unwrap (f $ \m -> (wrap $ \s' -> io_restore (unwrap m s'))) s
      where
        wrap g   = GhcT $ GHC.GhcT $ \s -> MTLAdapter (g s)
        unwrap m = \s -> unMTLA ((GHC.unGhcT $ unGhcT $  m) s)

    uninterruptibleMask = mask

instance (MonadIO m, MonadCatch m, MonadMask m) => GHC.ExceptionMonad (GhcT m) where
    gcatch  = catch
#if __GLASGOW_HASKELL__ >= 700
    gmask f = mask (\x -> f x)
#else
    gblock = mask_
#endif


#if __GLASGOW_HASKELL__ < 702
instance MTL.MonadIO m => GHC.WarnLogMonad (GhcT m) where
    setWarnings = GhcT . GHC.setWarnings
    getWarnings = GhcT GHC.getWarnings
#endif

instance (Functor m, MonadIO m, MonadCatch m, MonadMask m) => GHC.GhcMonad (GhcT m) where
    getSession = GhcT GHC.getSession
    setSession = GhcT . GHC.setSession

-- | We use the 'MTLAdapter' to convert between similar classes
--   like 'MTL'''s 'MonadIO' and 'GHC'''s 'MonadIO'.
newtype MTLAdapter m a = MTLAdapter {unMTLA :: m a} deriving (Functor, Applicative, Monad)


instance MTL.MonadIO m => GHC.MonadIO (MTLAdapter m) where
    liftIO = MTLAdapter . MTL.liftIO


instance (MonadIO m, MonadCatch m, MonadMask m) => GHC.ExceptionMonad (MTLAdapter m) where
  m `gcatch` f = MTLAdapter $ (unMTLA m) `catch` (unMTLA . f)

#if __GLASGOW_HASKELL__ >= 700
  gmask io = MTLAdapter $ mask (\f -> unMTLA $ io (MTLAdapter . f . unMTLA))
#else
  gblock = MTLAdapter . mask_ . unMTLA -- use block instead
#endif
