-- | Generic environment that handles both named and anonymous
--   de-bruijn binders.
module DDC.Base.Env
        ( -- * Types
          Bind   (..)
        , Bound  (..)
        , Env    (..)

          -- * Conversion
        , fromList
        , fromNameList
        , fromNameMap

          -- * Constructors
        , empty
        , singleton
        , extend,       extends
        , union,        unions

          -- * Lookup
        , member
        , lookup
        , lookupName,   lookupIx
        , depth)
where
import Data.Maybe
import Data.Map                         (Map)
import qualified Data.Map.Strict        as Map
import qualified Prelude                as P
import Prelude                          hiding (lookup)


-------------------------------------------------------------------------------
-- | A binding occurrence of a variable.
data Bind n
        -- | No binding, or alternatively, bind a fresh name that has
        --   no bound uses.
        = BNone

        -- | Anonymous binder.
        | BAnon

        -- | Named binder.
        | BName !n
        deriving (Eq, Ord, Show)


-- | A bound occurrence of a variable.
data Bound n
        -- | Index an anonymous binder.
        = UIx   !Int

        -- | Named variable.
        | UName !n
        deriving (Eq, Ord, Show)


-- | Generic environment that maps a variable to a thing of type @a@. 
data Env n a
        = Env
        { -- | Named things.
          envMap         :: !(Map n a)

          -- | Anonymous things.
        , envStack       :: ![a] 
        
          -- | Length of the stack.
        , envStackLength :: !Int }


-------------------------------------------------------------------------------
-- | Convert a list of `Bind`s to an environment.
fromList :: Ord n => [(Bind n, a)] -> Env n a
fromList bs
        = foldr (uncurry extend) empty bs


-- | Convert a list of `Bind`s to an environment.
fromNameList :: Ord n => [(n, a)] -> Env n a
fromNameList bs
        = foldr (uncurry extend) empty 
        $ [(BName n, x) | (n, x) <- bs ]


-- | Convert a map of things to an environment.
fromNameMap :: Map n a -> Env n a
fromNameMap m
        = empty { envMap = m }


---------------------------------------------------------------------------------
-- | An empty environment, with nothing in it.
empty :: Env n a
empty   = Env
        { envMap         = Map.empty
        , envStack       = [] 
        , envStackLength = 0 }


-- | Construct a singleton environment.
singleton :: Ord n => Bind n -> a -> Env n a
singleton b x
        = extend b x empty


-- | Extend an environment with a new binding.
--   Replaces bindings with the same name already in the environment.
extend :: Ord n => Bind n -> a -> Env n a -> Env n a
extend bb x env
 = case bb of
         BNone{}        -> env

         BAnon          -> env { envStack       = x : envStack env 
                               , envStackLength = envStackLength env + 1 }

         BName n        -> env { envMap         = Map.insert n x (envMap env) }


-- | Extend an environment with a list of new bindings.
--   Replaces bindings with the same name already in the environment.
extends :: Ord n => [(Bind n, a)] -> Env n a -> Env n a
extends bs env
        = foldl (flip (uncurry extend)) env bs


-- | Combine two environments.
--   If both environments have a binding with the same name,
--   then the one in the second environment takes preference.
union :: Ord n => Env n a -> Env n a -> Env n a
union env1 env2
        = Env  
        { envMap         = envMap env1 `Map.union` envMap env2
        , envStack       = envStack       env2  ++ envStack       env1
        , envStackLength = envStackLength env2  +  envStackLength env1 }


-- | Combine multiple environments,
--   with the latter ones taking preference.
unions :: Ord n => [Env n a] -> Env n a
unions envs
        = foldr union empty envs


-- | Check whether a bound variable is present in an environment.
member :: Ord n => Bound n -> Env n a -> Bool
member uu env
        = isJust $ lookup uu env


-- | Lookup a bound variable from an environment.
lookup :: Ord n => Bound n -> Env n a -> Maybe a
lookup uu env
 = case uu of
        UIx i           -> P.lookup i (zip [0..] (envStack env))
        UName n         -> Map.lookup n (envMap env) 


-- | Lookup a value from the environment based on its name.
lookupName :: Ord n => n -> Env n a -> Maybe a
lookupName n env
        = Map.lookup n (envMap env)


-- | Lookup a value from the environment based on its index.
lookupIx :: Ord n => Int -> Env n a -> Maybe a
lookupIx ix env
        = P.lookup ix (zip [0..] (envStack env))


-- | Yield the total depth of the anonymous stack.
depth :: Env n a -> Int
depth env       = envStackLength env