module Agda.Interaction.SearchAbout (findMentions) where

import Control.Applicative
import Control.Monad
import Control.Monad.State
import qualified Data.Map as Map
import Data.List (isInfixOf)
import Data.Either (partitionEithers)

import Agda.Syntax.Position (Range)
import Agda.Syntax.Scope.Base
import Agda.Syntax.Scope.Monad
import Agda.TypeChecking.Monad
import qualified Agda.Syntax.Common as Com
import Agda.Syntax.Internal
import Agda.Interaction.BasicOps (normalForm)
import qualified Agda.Syntax.Translation.ConcreteToAbstract as TCA
import qualified Agda.Syntax.Abstract as A
import qualified Agda.Syntax.Concrete as C

import Agda.Interaction.BasicOps
import Debug.Trace

collectNamesInType :: Type -> [A.QName]
collectNamesInType = collectNamesInTerm . unEl

collectNamesInTerm :: Term -> [A.QName]
collectNamesInTerm (Var _ els)  = collectNamesInElims els
collectNamesInTerm (Lam ty t)   = collectNamesInTerm $ unAbs t
collectNamesInTerm (Def n els)  = n : collectNamesInElims els
collectNamesInTerm (Con n args) = conName n : collectNamesInArgs args
collectNamesInTerm (Pi dom cod) = collectNamesInType (Com.unDom dom) ++ collectNamesInType (unAbs cod)
collectNamesInTerm (Shared t)   = collectNamesInTerm $ ignoreSharing $ derefPtr t
collectNamesInTerm _            = []

collectNamesInElims :: Elims -> [A.QName]
collectNamesInElims = concatMap collectNamesInElim

collectNamesInElim :: Elim -> [A.QName]
collectNamesInElim (Apply a) = collectNamesInTerm $ Com.unArg a
collectNamesInElim (Proj n)  = [n]

collectNamesInArgs :: Args -> [A.QName]
collectNamesInArgs = concatMap (collectNamesInTerm . Com.unArg)

findMentions :: Rewrite -> Range -> String -> TCM [(C.Name, Type)]
findMentions norm rg nm = do
  let (strs, nms) = partitionEithers $ fmap isString $ words nm
  cnms <- mapM (parseName rg) nms
  rnms <- mapM resolveName cnms
  let defs = fmap (fmap anameName . anames) rnms
  scp  <- getNamedScope =<< currentModule
  let names = nsNames $ allThingsInScope scp
  concat <$> mapM (\ (x, n) -> do
              t <- normalForm norm =<< typeOfConst (anameName n)
              let defName  = show x
              let namesInT = collectNamesInType t
              return $ do
                guard (all (`isInfixOf` defName) strs)
                guard (all (any (`elem` namesInT)) defs)
                return (x, t)
           ) (concatMap (\ (x, ns) -> map ((,) x) ns) $ Map.toList names)
  where
    isString str
      |  not (null str)
      && head str == '"'
      && last str == '"' = Left $ filter (/= '"') str
      | otherwise        = Right str

    anames (DefinedName _ an)     = [an]
    anames (FieldName     an)     = [an]
    anames (ConstructorName ans)  = ans
    anames (PatternSynResName an) = [an]
    anames _                      = []
