{-# LANGUAGE CPP, DeriveDataTypeable #-}

{-| This module defines the notion of a scope and operations on scopes.
-}
module Agda.Syntax.Scope.Base where

import Data.Generics (Typeable, Data)
import Data.List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Function

import Agda.Syntax.Position
import Agda.Syntax.Common
import Agda.Syntax.Fixity
import Agda.Syntax.Abstract.Name as A
import Agda.Syntax.Concrete.Name as C
import Agda.Syntax.Concrete (ImportDirective(..), UsingOrHiding(..), ImportedName(..))
import qualified Agda.Utils.Map as Map
import Agda.Utils.Tuple

#include "../../undefined.h"
import Agda.Utils.Impossible

-- * Scope representation

-- | A scope is a named collection of names partitioned into public and private
--   names.
data Scope = Scope
      { scopeName    :: A.ModuleName
      , scopePrivate :: NameSpace
      , scopePublic  :: NameSpace
      }
  deriving (Typeable, Data)

-- | The scope at a particular point in the program is determined by the stack
--   of all enclosing scopes.
type ScopeStack = [Scope]

-- | The complete information about the scope at a particular program point
--   includes the scope stack, the local variables, and the context precedence.
data ScopeInfo = ScopeInfo
      { scopeStack	:: ScopeStack
      , scopeLocals	:: LocalVars
      , scopePrecedence :: Precedence
      }
  deriving (Typeable, Data)

-- | Local variables
type LocalVars = [(C.Name, A.Name)]

-- | A @NameSpace@ contains the mappings from concrete names that the user can
--   write to the abstract fully qualified names that the type checker wants to
--   read.
data NameSpace = NameSpace
      { nsNames	  :: NamesInScope
      , nsModules :: ModulesInScope
      }
  deriving (Typeable, Data)

type NamesInScope   = Map C.QName [AbstractName]
type ModulesInScope = Map C.QName [AbstractModule]

-- | We distinguish constructor names from other names.
data KindOfName = ConName | DefName
  deriving (Eq, Show, Typeable, Data)

-- | Apart from the name, we also record whether it's a constructor or not and
--   what the fixity is.
data AbstractName = AbsName
      { anameName   :: A.QName
      , anameKind   :: KindOfName
      }
  deriving (Typeable, Data)

-- | For modules we record the arity. I'm not sure that it's every used anywhere.
data AbstractModule = AbsModule
      { amodName    :: A.ModuleName
      }
  deriving (Typeable, Data)

instance Eq AbstractName where
  (==) = (==) `on` anameName

instance Ord AbstractName where
  compare = compare `on` anameName

instance Eq AbstractModule where
  (==) = (==) `on` amodName

instance Ord AbstractModule where
  compare = compare `on` amodName

instance Show ScopeInfo where
  show (ScopeInfo stack locals ctx) =
    unlines $
      [ "ScopeInfo" ] ++
      (if null locals then [] else [ "  locals  = " ++ show locals ]) ++
      [ "  context = " ++ show ctx
      , "  stack"
      ] ++ map ("    "++) (relines . map show $ stack)
    where
      relines = filter (not . null) . lines . unlines

blockOfLines :: String -> [String] -> [String]
blockOfLines _  [] = []
blockOfLines hd ss = hd : map ("  "++) ss

instance Show Scope where
  show (Scope { scopeName = name, scopePublic = pub, scopePrivate = pri }) =
    unlines $
      [ "scope " ++ show name ]
      ++ blockOfLines "public"  (lines $ show pub)
      ++ blockOfLines "private" (lines $ show pri)

instance Show NameSpace where
  show (NameSpace names mods) =
    unlines $
      blockOfLines "names"   (map pr $ Map.toList names) ++
      blockOfLines "modules" (map pr $ Map.toList mods)
    where
      pr (x, y) = show x ++ " --> " ++ show y

instance Show AbstractName where
  show = show . anameName

instance Show AbstractModule where
  show = show . amodName

-- * Operations on names

instance HasRange AbstractName where
  getRange = getRange . anameName

instance SetRange AbstractName where
  setRange r x = x { anameName = setRange r $ anameName x }

-- * Operations on name and module maps.

mergeNames :: NamesInScope -> NamesInScope -> NamesInScope
mergeNames = Map.unionWith union

mergeModules :: ModulesInScope -> ModulesInScope -> ModulesInScope
mergeModules = Map.unionWith union

-- * Operations on name spaces

-- | The empty name space.
emptyNameSpace :: NameSpace
emptyNameSpace = NameSpace Map.empty Map.empty


-- | Map functions over the names and modules in a name space.
mapNameSpace :: (NamesInScope   -> NamesInScope  ) ->
		(ModulesInScope -> ModulesInScope) ->
		NameSpace -> NameSpace
mapNameSpace fd fm ns =
  ns { nsNames	 = fd $ nsNames ns
     , nsModules = fm $ nsModules  ns
     }

-- | Zip together two name spaces.
zipNameSpace :: (NamesInScope   -> NamesInScope   -> NamesInScope  ) ->
		(ModulesInScope -> ModulesInScope -> ModulesInScope) ->
		NameSpace -> NameSpace -> NameSpace
zipNameSpace fd fm ns1 ns2 =
  ns1 { nsNames	  = nsNames   ns1 `fd` nsNames   ns2
      , nsModules = nsModules ns1 `fm` nsModules ns2
      }

-- | Map monadic function over a namespace.
mapNameSpaceM :: Monad m =>
  (NamesInScope   -> m NamesInScope  ) ->
  (ModulesInScope -> m ModulesInScope) ->
  NameSpace -> m NameSpace
mapNameSpaceM fd fm ns = do
  ds <- fd $ nsNames ns
  ms <- fm $ nsModules ns
  return $ ns { nsNames = ds, nsModules = ms }

-- * General operations on scopes

-- | The empty scope.
emptyScope :: Scope
emptyScope = Scope { scopeName	  = noModuleName
		   , scopePublic  = emptyNameSpace
		   , scopePrivate = emptyNameSpace
		   }

-- | The empty scope info.
emptyScopeInfo :: ScopeInfo
emptyScopeInfo = ScopeInfo
		  { scopeStack	    = [emptyScope]
		  , scopeLocals	    = []
		  , scopePrecedence = TopCtx
		  }

-- | Map functions over the names and modules in a scope.
mapScope :: (Access -> NamesInScope   -> NamesInScope  ) ->
	    (Access -> ModulesInScope -> ModulesInScope) ->
	    Scope -> Scope
mapScope fd fm s =
  s { scopePrivate = mapNS PrivateAccess $ scopePrivate s
    , scopePublic  = mapNS PublicAccess  $ scopePublic  s
    }
  where
    mapNS acc = mapNameSpace (fd acc) (fm acc)

-- | Same as 'mapScope' but applies the same function to both the public and
--   private name spaces.
mapScope_ :: (NamesInScope   -> NamesInScope  ) ->
	     (ModulesInScope -> ModulesInScope) ->
	     Scope -> Scope
mapScope_ fd fm = mapScope (const fd) (const fm)

-- | Map monadic functions over the names and modules in a scope.
mapScopeM :: Monad m =>
  (Access -> NamesInScope   -> m NamesInScope  ) ->
  (Access -> ModulesInScope -> m ModulesInScope) ->
  Scope -> m Scope
mapScopeM fd fm s = do
  pri <- mapNS PrivateAccess $ scopePrivate s
  pub <- mapNS PublicAccess  $ scopePublic  s
  return $ s { scopePrivate = pri, scopePublic = pub }
  where
    mapNS acc = mapNameSpaceM (fd acc) (fm acc)

-- | Same as 'mapScopeM' but applies the same function to both the public and
--   private name spaces.
mapScopeM_ :: Monad m =>
  (NamesInScope   -> m NamesInScope  ) ->
  (ModulesInScope -> m ModulesInScope) ->
  Scope -> m Scope
mapScopeM_ fd fm = mapScopeM (const fd) (const fm)

-- | Zip together two scopes. The resulting scope has the same name as the
--   first scope.
zipScope :: (Access -> NamesInScope   -> NamesInScope   -> NamesInScope  ) ->
	    (Access -> ModulesInScope -> ModulesInScope -> ModulesInScope) ->
	    Scope -> Scope -> Scope
zipScope fd fm s1 s2 =
  s1 { scopePrivate = zipNS PrivateAccess (scopePrivate s1) (scopePrivate s2)
     , scopePublic  = zipNS PublicAccess  (scopePublic  s1) (scopePublic  s2)
     }
  where
    zipNS acc = zipNameSpace (fd acc) (fm acc)

-- | Same as 'zipScope' but applies the same function to both the public and
--   private name spaces.
zipScope_ :: (NamesInScope   -> NamesInScope   -> NamesInScope  ) ->
	     (ModulesInScope -> ModulesInScope -> ModulesInScope) ->
	     Scope -> Scope -> Scope
zipScope_ fd fm = zipScope (const fd) (const fm)

-- | Filter a scope keeping only concrete names matching the predicates.
--   The first predicate is applied to the names and the second to the modules.
filterScope :: (C.QName -> Bool) -> (C.QName -> Bool) -> Scope -> Scope
filterScope pd pm = mapScope_ (Map.filterKeys pd) (Map.filterKeys pm)

-- | Return all names in a scope, both public and private.
allNamesInScope :: Scope -> NamesInScope
allNamesInScope s = (mergeNames `on` nsNames) (scopePublic s) (scopePrivate s)

-- | Return all modules in a scope, both public and private.
allModulesInScope :: Scope -> ModulesInScope
allModulesInScope s = (mergeModules `on` nsModules) (scopePublic s) (scopePrivate s)

-- | Merge two scopes. The result has the name of the first scope.
mergeScope :: Scope -> Scope -> Scope
mergeScope = zipScope_ mergeNames mergeModules

-- | Merge a non-empty list of scopes. The result has the name of the first
--   scope in the list.
mergeScopes :: [Scope] -> Scope
mergeScopes [] = __IMPOSSIBLE__
mergeScopes ss = foldr1 mergeScope ss

-- * Specific operations on scopes

-- | Given a scope where all concrete names start with @M@, remove all the
--   @M@s. Used when opening a module @M@, in which case only those names
--   starting with @M@ are considered.
unqualifyScope :: C.QName -> Scope -> Scope
unqualifyScope m = mapScope_ unqual unqual
  where
    unqual = Map.mapKeys (unq m)

    unq _	     (C.QName _) = __IMPOSSIBLE__
    unq (C.Qual m n) (C.Qual m' q)
      | m == m'   = unq n q
      | otherwise = __IMPOSSIBLE__
    unq (C.QName m)  (C.Qual m' q)
      | m == m'	  = q
      | otherwise = __IMPOSSIBLE__

-- | Move all names in a scope to the public or private section, depending on
--   the first argument. Used when opening a module.
setScopeAccess :: Access -> Scope -> Scope
setScopeAccess a s = s { scopePublic  = pub
		       , scopePrivate = pri
		       }
  where
    one  = zipNameSpace mergeNames mergeModules (scopePublic s) (scopePrivate s)
    zero = emptyNameSpace

    (pub, pri) = case a of
      PublicAccess  -> (one, zero)
      PrivateAccess -> (zero, one)

-- | Add names to a scope.
addNamesToScope :: Access -> C.QName -> [AbstractName] -> Scope -> Scope
addNamesToScope acc x ys s = mergeScope s s1
  where
    s1 = setScopeAccess acc $ emptyScope
	 { scopePublic = emptyNameSpace { nsNames = Map.singleton x ys } }

-- | Add a name to a scope.
addNameToScope :: Access -> C.QName -> AbstractName -> Scope -> Scope
addNameToScope acc x y s = addNamesToScope acc x [y] s

-- | Add a module to a scope.
addModuleToScope :: Access -> C.QName -> AbstractModule -> Scope -> Scope
addModuleToScope acc x m s = mergeScope s s1
  where
    s1 = setScopeAccess acc $ emptyScope
	 { scopePublic = emptyNameSpace { nsModules = Map.singleton x [m] } }

-- | Apply an 'ImportDirective' to a scope.
applyImportDirective :: ImportDirective -> Scope -> Scope
applyImportDirective dir s = mergeScope usedOrHidden renamed
  where
    usedOrHidden = useOrHide (hideLHS (renaming dir) $ usingOrHiding dir) s
    renamed	 = rename (renaming dir) $ useOrHide useRenamedThings s

    useRenamedThings = Using $ map fst $ renaming dir

    hideLHS :: [(ImportedName, C.Name)] -> UsingOrHiding -> UsingOrHiding
    hideLHS _	i@(Using _) = i
    hideLHS ren (Hiding xs) = Hiding $ xs ++ map fst ren

    useOrHide :: UsingOrHiding -> Scope -> Scope
    useOrHide (Hiding xs) s = filterNames notElem notElem xs s
    useOrHide (Using  xs) s = filterNames elem	  elem	  xs s

    -- Qualified names are only ok if the top-most module is ok.
    okName pd pm (C.QName  x) = pd x
    okName pd pm (C.Qual m _) = pm m

    okModule pm (C.QName  x) = pm x
    okModule pm (C.Qual m _) = pm m

    filterNames :: (C.Name -> [C.Name] -> Bool) -> (C.Name -> [C.Name] -> Bool) ->
		   [ImportedName] -> Scope -> Scope
    filterNames pd pm xs = filterScope' (flip pd ds) (flip pm ms)
      where
	ds = [ x | ImportedName   x <- xs ]
	ms = [ m | ImportedModule m <- xs ]

    filterScope' pd pm = filterScope (okName pd pm) (okModule pm)

    -- Renaming
    rename :: [(ImportedName, C.Name)] -> Scope -> Scope
    rename rho = mapScope_ (Map.mapKeys $ renName drho mrho)
			   (Map.mapKeys $ renMod mrho)
      where
	mrho = [ (x, y) | (ImportedModule x, y) <- rho ]
	drho = [ (x, y) | (ImportedName	  x, y) <- rho ]

	ren r x = maybe x id $ lookup x r

	renName dr mr (C.QName  x) = C.QName $ ren dr x
	renName dr mr (C.Qual m x) = flip C.Qual x $ ren mr m

	renMod mr (C.QName  x) = C.QName $ ren mr x
	renMod mr (C.Qual m x) = flip C.Qual x $ ren mr m

-- | Rename the canical names in a scope.
renameCanonicalNames :: Map A.QName A.QName -> Map A.ModuleName A.ModuleName ->
			Scope -> Scope
renameCanonicalNames renD renM = mapScope_ renameD renameM
  where
    renameD = Map.map (map $ onName  rD)
    renameM = Map.map (map $ onMName rM)

    onName  f x = x { anameName = f $ anameName x }
    onMName f x = x { amodName  = f $ amodName  x }

    rD x = maybe x id $ Map.lookup x renD
    rM x = maybe x id $ Map.lookup x renM

-- * Inverse look-up

-- | Find the shortest concrete name that maps (uniquely) to a given abstract
--   name. Find defined names (first component of result) and module names
--   (second component) simultaneously.

-- | Takes the first component of 'inverseScopeLookup'.
inverseScopeLookupName :: A.QName -> ScopeInfo -> Maybe C.QName
inverseScopeLookupName x s = best $ invert x $ mergeScopes $ scopeStack s
  where
    len :: C.QName -> Int
    len (C.QName _)  = 1
    len (C.Qual _ x) = 1 + len x

    best xs = case sortBy (compare `on` len) xs of
      []    -> Nothing
      x : _ -> Just x

    invert x s = ds
      where
	ds = [ y | (y, zs) <- Map.toList $ allNamesInScope s
                 , x `elem` map anameName zs
                 , length zs == 1 || all ((==ConName) . anameKind) zs
             ]

-- | Takes the second component of 'inverseScopeLookup'.
inverseScopeLookupModule :: A.ModuleName -> ScopeInfo -> Maybe C.QName
inverseScopeLookupModule x s = best $ invert x $ mergeScopes $ scopeStack s
  where
    len :: C.QName -> Int
    len (C.QName _)  = 1
    len (C.Qual _ x) = 1 + len x

    best xs = case sortBy (compare `on` len) xs of
      []    -> Nothing
      x : _ -> Just x

    invert x s = ms
      where
	ms = [ y | (y, [m]) <- Map.toList $ allModulesInScope s, x == amodName m  ]


