{-# LANGUAGE CPP #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Defines a utility function for deriving 'Quasi' instances for monad
-- transformer data types.
module Language.Haskell.TH.Instances.Internal
  ( deriveQuasiTrans
  , Proxy2
  ) where

import qualified Control.Monad.Trans as MTL (lift)
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Ppr (pprint)
import Language.Haskell.TH.Syntax

deriveQuasiTrans ::
     Q Type  -- ^ The instance head. For example, this might be of the form:
             --
             --   > [t| forall r m. Quasi m => Proxy2 (ReaderT r m) |]
             --
             --   Why use 'Proxy2' instead of 'Quasi'? Sadly, GHC 7.0/7.2 will
             --   not accept it if you use the latter due to old TH bugs, so we
             --   use 'Proxy2' as an ugly workaround.
  -> Q Exp   -- ^ The implementation of 'qRecover'
  -> Q [Dec] -- ^ The 'Quasi' instance declaration
deriveQuasiTrans qInstHead qRecoverExpr = do
  instHead    <- qInstHead
  let (instCxt, mangledInstTy) = decomposeType instHead
      qInstCxt = return instCxt
      qInstTy  = case mangledInstTy of
                   ConT proxy2 `AppT` instTy
                     |  proxy2 == ''Proxy2
                     -> conT ''Quasi `appT` return instTy
                   _ -> fail $ "Unexpected type " ++ pprint mangledInstTy
  instDec <- instanceD qInstCxt qInstTy qInstMethDecs
  return [instDec]
  where
    decomposeType :: Type -> (Cxt, Type)
    decomposeType (ForallT _tvbs ctxt ty) = (ctxt, ty)
    decomposeType ty                      = ([],   ty)

    qInstMethDecs :: [Q Dec]
    qInstMethDecs =
      let instMeths :: [(Name, Q Exp)]
          instMeths =
            [ -- qRecover is different for each instance
              ('qRecover,            qRecoverExpr)

              -- The remaining methods are straightforward
            , ('qNewName,            [| MTL.lift . qNewName |])
            , ('qReport,             [| \a b -> MTL.lift $ qReport a b |])
            , ('qReify,              [| MTL.lift . qReify |])
            , ('qLocation,           [| MTL.lift qLocation |])
            , ('qRunIO,              [| MTL.lift . qRunIO |])
#if MIN_VERSION_template_haskell(2,7,0)
            , ('qReifyInstances,     [| \a b -> MTL.lift $ qReifyInstances a b |])
            , ('qLookupName,         [| \a b -> MTL.lift $ qLookupName a b |])
            , ('qAddDependentFile,   [| MTL.lift . qAddDependentFile |])
# if MIN_VERSION_template_haskell(2,9,0)
            , ('qReifyRoles,         [| MTL.lift . qReifyRoles |])
            , ('qReifyAnnotations,   [| MTL.lift . qReifyAnnotations |])
            , ('qReifyModule,        [| MTL.lift . qReifyModule |])
            , ('qAddTopDecls,        [| MTL.lift . qAddTopDecls |])
            , ('qAddModFinalizer,    [| MTL.lift . qAddModFinalizer |])
            , ('qGetQ,               [| MTL.lift qGetQ |])
            , ('qPutQ,               [| MTL.lift . qPutQ |])
# endif
# if MIN_VERSION_template_haskell(2,11,0)
            , ('qReifyFixity,        [| MTL.lift . qReifyFixity |])
            , ('qReifyConStrictness, [| MTL.lift . qReifyConStrictness |])
            , ('qIsExtEnabled,       [| MTL.lift . qIsExtEnabled |])
            , ('qExtsEnabled,        [| MTL.lift qExtsEnabled |])
# endif
#elif MIN_VERSION_template_haskell(2,5,0)
            , ('qClassInstances,     [| \a b -> MTL.lift $ qClassInstances a b |])
#endif
#if MIN_VERSION_template_haskell(2,14,0)
            , ('qAddForeignFilePath, [| \a b -> MTL.lift $ qAddForeignFilePath a b |])
            , ('qAddTempFile,        [| MTL.lift . qAddTempFile |])
#elif MIN_VERSION_template_haskell(2,12,0)
            , ('qAddForeignFile,     [| \a b -> MTL.lift $ qAddForeignFile a b |])
#endif
#if MIN_VERSION_template_haskell(2,13,0)
            , ('qAddCorePlugin,      [| MTL.lift . qAddCorePlugin |])
#endif
#if MIN_VERSION_template_haskell(2,16,0)
            , ('qReifyType,          [| MTL.lift . qReifyType |])
#endif
            ]

          mkDec :: Name -> Q Exp -> Q Dec
          mkDec methName methRhs = valD (varP methName) (normalB methRhs) []

      in map (uncurry mkDec) instMeths

-- | See the Haddocks for 'deriveQuasiTrans' for an explanation of why this
-- type needs to exist.
data Proxy2 (m :: * -> *)