{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}

module Language.Haskell.TH.TestUtils.QState
  ( QState(..)
  , ReifyInfo(..)
  , loadNames
  , unmockedState
  ) where

import Language.Haskell.TH
import Language.Haskell.TH.Instances ()
import Language.Haskell.TH.Syntax (Lift)
#if MIN_VERSION_template_haskell(2,16,0)
import qualified Language.Haskell.TH.Syntax as TH
#endif

import Language.Haskell.TH.TestUtils.QMode (MockedMode(..), QMode(..))

-- | State information for mocking Q functionality.
data QState (mode :: MockedMode) = QState
  { mode       :: QMode mode
  , knownNames :: [(String, Name)]
    -- ^ Names that can be looked up with 'lookupTypeName' or 'lookupValueName'
  , reifyInfo  :: [(Name, ReifyInfo)]
    -- ^ Reification information for Names to return when 'reify' is called.
  } deriving (Show, Lift)

data ReifyInfo = ReifyInfo
  { reifyInfoInfo   :: Info
  , reifyInfoFixity :: Maybe Fixity
  , reifyInfoRoles  :: Maybe [Role]
  , reifyInfoType   :: Type
  } deriving (Show, Lift)

-- | A helper for loading names for 'reifyInfo'
--
-- Usage:
--
-- > QState
-- >   { reifyInfo = $(loadNames [''Int, ''Maybe])
-- >   , ...
-- >   }
loadNames :: [Name] -> ExpQ
loadNames names = listE $ flip map names $ \name -> do
  info <- reify name
  fixity <- reifyFixity name
  roles <- recover (pure Nothing) $ Just <$> reifyRoles name
#if MIN_VERSION_template_haskell(2,16,0)
  let infoType = reifyType name >>= TH.lift
#else
  let infoType = [| error "Your version of template-haskell does not have 'reifyType'" |]
#endif

  [| (name, ReifyInfo info fixity roles $infoType) |]

-- | A shortcut for defining an unmocked Q.
unmockedState :: QState 'NotMocked
unmockedState = QState
  { mode = AllowQ
  , knownNames = []
  , reifyInfo = []
  }