module Agda2Hs.AgdaUtils where

import Data.Data
import Data.Monoid ( Any(..) )
import qualified Data.Map as Map
import Data.Maybe ( fromMaybe )

import Agda.Compiler.Backend hiding ( Args )

import Agda.Interaction.FindFile ( findFile' )

import Agda.Syntax.Common ( Arg, defaultArg )
import Agda.Syntax.Common.Pretty ( prettyShow )
import qualified Agda.Syntax.Concrete as C
import Agda.Syntax.Internal
import Agda.Syntax.Internal.Names
import Agda.Syntax.Scope.Base
import Agda.Syntax.Scope.Monad
import Agda.Syntax.TopLevelModuleName

import Agda.TypeChecking.Monad ( topLevelModuleName )
import Agda.TypeChecking.Pretty
import Agda.TypeChecking.Substitute

import Agda.Utils.Either ( isRight )
import Agda.Utils.List ( initMaybe )
import qualified Agda.Utils.List1 as List1
import Agda.Utils.Monad ( ifM )
import Agda.Utils.Impossible ( __IMPOSSIBLE__ )

import AgdaInternals

multilineText :: Monad m => String -> m Doc
multilineText s = vcat $ map text $ lines s

(~~) :: QName -> String -> Bool
q ~~ s = prettyShow q == s

-- | Check whether a module is an *immediate* parent of another.
isFatherModuleOf :: ModuleName -> ModuleName -> Bool
isFatherModuleOf m = maybe False (mnameToList m ==) . initMaybe . mnameToList

-- | Apply a clause's telescope arguments to a local where definition.
-- i.e. reverse Agda's λ-lifting
applyUnderTele :: Definition -> Args -> Definition
applyUnderTele d as = raise (length as) d `apply` as

-- | Check whether the given name (1) is the name of an extended
--   lambda and (2) is used anywhere inside the second argument.
extLamUsedIn :: NamesIn a => QName -> a -> Bool
extLamUsedIn n x = isExtendedLambdaName n && getAny (namesIn' (Any . (n ==)) x)

-- | All mentions of local definitions that occur anywhere inside the argument.
getLocalUses :: NamesIn a => [QName] -> a -> [QName]
getLocalUses ls = namesIn' $ \q -> [ q | q `elem` ls ]

-- | Convert the final 'Proj' projection elimination into a
--   'Def' projection application.
unSpine1 :: Term -> Term
unSpine1 v =
  case hasElims v of
    Just (h, es) -> fromMaybe v $ loop h [] es
    Nothing      -> v
  where
    loop :: (Elims -> Term) -> Elims -> Elims -> Maybe Term
    loop h res es =
      case es of
        []             -> Nothing
        Proj o f : es' -> Just $ fromMaybe (Def f (Apply (defaultArg v) : es')) $ loop h (Proj o f : res) es'
        e        : es' -> loop h (e : res) es'
      where v = h $ reverse res

mapDef :: (Term -> Term) -> Definition -> Definition
mapDef f d = d{ theDef = mapDefn (theDef d) }
  where
    mapDefn def@Function{} = def{ funClauses = map mapClause (funClauses def) }
    mapDefn defn = defn -- We only need this for Functions

    mapClause c = c{ clauseBody = f <$> clauseBody c }

topLevelModuleNameForModuleName :: ModuleName -> TCM TopLevelModuleName
topLevelModuleNameForModuleName = topLevelModuleName . rawTopLevelModuleNameForModuleName

isTopLevelModule :: ModuleName -> TCM (Maybe TopLevelModuleName)
isTopLevelModule m = do
  tlm <- topLevelModuleNameForModuleName m
  ifM (isRight <$> findFile' tlm) (return $ Just tlm) (return Nothing)

getTopLevelModuleForModuleName :: ModuleName -> TCM (Maybe TopLevelModuleName)
getTopLevelModuleForModuleName = loop . mnameToList
  where
    loop ns
      | null ns   = return Nothing
      | otherwise = isTopLevelModule (MName ns) >>= \case
        Nothing      -> loop (init ns)
        tlm@(Just _) -> return tlm

getTopLevelModuleForQName :: QName -> TCM (Maybe TopLevelModuleName)
getTopLevelModuleForQName = getTopLevelModuleForModuleName . qnameModule

lookupModuleInCurrentModule :: C.Name -> TCM [AbstractModule]
lookupModuleInCurrentModule x =
  List1.toList' . Map.lookup x . nsModules . thingsInScope [PublicNS, PrivateNS] <$> getCurrentScope
