{-# LANGUAGE ScopedTypeVariables #-}
module Agda.TypeChecking.Level.Solve where
import Control.Monad
import qualified Data.IntSet as IntSet
import Data.IntSet (IntSet)
import Data.Maybe
import Agda.Interaction.Options
import Agda.Syntax.Common
import Agda.Syntax.Internal
import Agda.Syntax.Internal.MetaVars
import Agda.TypeChecking.Level
import Agda.TypeChecking.MetaVars.Mention
import Agda.TypeChecking.Monad
import Agda.TypeChecking.Substitute
import Agda.TypeChecking.Telescope
import Agda.Utils.Except
import Agda.Utils.Monad
defaultOpenLevelsToZero :: MonadMetaSolver m => m a -> m a
defaultOpenLevelsToZero f = ifNotM (optCumulativity <$> pragmaOptions) f $ do
  (result , newMetas) <- metasCreatedBy f
  defaultLevelsToZero newMetas
  return result
defaultLevelsToZero :: forall m. (MonadMetaSolver m) => IntSet -> m ()
defaultLevelsToZero xs = loop =<< openLevelMetas (map MetaId $ IntSet.elems xs)
  where
    loop :: [MetaId] -> m ()
    loop xs = do
      let isOpen x = isOpenMeta . mvInstantiation <$> lookupMeta x
      xs <- filterM isOpen xs
      allMetaTypes <- getOpenMetas >>= traverse metaType
      let notInTypeOfMeta x = not $ mentionsMeta x allMetaTypes
      progress <- forM xs $ \x -> do
        cs <- filter (mentionsMeta x) <$> getAllConstraints
        if | notInTypeOfMeta x , all (`isUpperBoundFor` x) cs -> do
               m <- lookupMeta x
               TelV tel t <- telView =<< metaType x
               addContext tel $ assignV DirEq x (teleArgs tel) (Level $ ClosedLevel 0) (AsTermsOf t)
               return True
             `catchError` \_ -> return False
           | otherwise -> return False
      if | or progress -> loop xs
         | otherwise   -> return ()
    openLevelMetas :: [MetaId] -> m [MetaId]
    openLevelMetas xs = return xs
      >>= filterM (\m -> isNothing <$> isInteractionMeta m)
      >>= filterM (\m -> (== NoGeneralize) <$> isGeneralizableMeta m)
      >>= filterM isLevelMeta
    isLevelMeta :: MetaId -> m Bool
    isLevelMeta x = do
      TelV tel t <- telView =<< metaType x
      addContext tel $ isLevelType t
    isUpperBoundFor :: ProblemConstraint -> MetaId -> Bool
    isUpperBoundFor c x = case clValue (theConstraint c) of
      LevelCmp CmpLeq l u -> not $ mentionsMeta x u
      _                   -> False