module DDC.Core.Module
        ( -- * Modules
          Module        (..)
        , isMainModule
	, moduleKindEnv
        , moduleTypeEnv
        , modulesGetBinds

	  -- * Module maps
	, ModuleMap
	, modulesExportKinds
	, modulesExportTypes

         -- * Module Names.
        , QualName      (..)
        , ModuleName    (..)
        , isMainModuleName)
where
import DDC.Core.Exp
import Data.Typeable
import Data.Map.Strict                  (Map)
import DDC.Type.Env                     as Env
import qualified Data.Map.Strict        as Map
import Control.DeepSeq


-- Module ---------------------------------------------------------------------
-- | A module can be mutually recursive with other modules.
data Module a n
        = ModuleCore
        { -- | Name of this module.
          moduleName            :: !ModuleName

          -- Exports ------------------
          -- | Kinds of exported types.
        , moduleExportKinds     :: !(Map n (Kind n))

          -- | Types of exported values.
        , moduleExportTypes     :: !(Map n (Type n))

          -- Imports ------------------
          -- | Kinds of imported types,
          --   along with the name of the module they are from.
        , moduleImportKinds     :: !(Map n (QualName n, Kind n))

          -- | Types of imported values,
          --   along with the name of the module they are from.
        , moduleImportTypes     :: !(Map n (QualName n, Type n))

          -- Local --------------------
          -- | The module body consists of some let-bindings
          --   wrapping a unit data constructor.
          -- 
          --  We're only interested in the bindings, 
          --  with the unit being just a place-holder.
        , moduleBody            :: !(Exp a n)
        }
        deriving (Show, Typeable)


instance (NFData a, NFData n) => NFData (Module a n) where
 rnf !mm
        =     rnf (moduleName mm)
        `seq` rnf (moduleExportKinds mm)
        `seq` rnf (moduleExportTypes mm)
        `seq` rnf (moduleImportKinds mm)
        `seq` rnf (moduleImportTypes mm)
        `seq` rnf (moduleBody mm)


-- | Check if this is the `Main` module.
isMainModule :: Module a n -> Bool
isMainModule mm
        = isMainModuleName 
        $ moduleName mm


-- | Get the top-level kind environment of a module,
--   from its imported types.
moduleKindEnv :: Ord n => Module a n -> KindEnv n
moduleKindEnv mm
        = Env.fromList 
        $ [BName n k | (n, (_, k)) <- Map.toList $ moduleImportKinds mm]


-- | Get the top-level type environment of a module,
--   from its imported values.
moduleTypeEnv :: Ord n => Module a n -> TypeEnv n
moduleTypeEnv mm
        = Env.fromList 
        $ [BName n k | (n, (_, k)) <- Map.toList $ moduleImportTypes mm]


-- ModuleMap ------------------------------------------------------------------
-- | Map of module names to modules.
type ModuleMap a n 
        = Map ModuleName (Module a n)

modulesGetBinds m 
        = Env.fromList $ map (uncurry BName) (Map.assocs m)


-- | Add the kind environment exported by all these modules to the given one.
modulesExportKinds :: Ord n => ModuleMap a n -> KindEnv n -> KindEnv n
modulesExportKinds mods base
        = foldl Env.union base 
        $ map (modulesGetBinds.moduleExportKinds) (Map.elems mods)


-- | Add the type environment exported by all these modules to the given one.
modulesExportTypes :: Ord n => ModuleMap a n -> TypeEnv n -> TypeEnv n

modulesExportTypes mods base
        = foldl Env.union base 
        $ map (modulesGetBinds.moduleExportTypes) (Map.elems mods)


-- ModuleName -----------------------------------------------------------------
-- | A hierarchical module name.
data ModuleName
        = ModuleName [String]
        deriving (Show, Eq, Ord, Typeable)

instance NFData ModuleName where
 rnf (ModuleName ss)
        = rnf ss
 

-- | A fully qualified name, 
--   including the name of the module it is from.
data QualName n
        = QualName ModuleName n
        deriving Show

instance NFData n => NFData (QualName n) where
 rnf (QualName mn n)
        = rnf mn `seq` rnf n


-- | Check whether this is the name of the \"Main\" module.
isMainModuleName :: ModuleName -> Bool
isMainModuleName mn
 = case mn of
        ModuleName ["Main"]     -> True
        _                       -> False