{- |
We add implicit imports are for public nested modules.  This allows
using definitions from nested modules without having to explicitly import
them, for example:

module A where

  submodule B where
    x = 0x20

  y = x     // This works because of the implicit import of `B`

Restriction:
============

We only add impicit imports of modules that are syntactically visiable
in the source code.  Consider the following example:

module A where
  submodule M = F {X}   -- F,X are external modules (e.g., top-level)

We will add an implicit import for `M`, but *NO* implicit imports for
any modules imported vial `M` as those are not sytnactically visible
in the source (i.e., we have to know what `F` refers to).

This restriction allows us to add implicit imports before doing the
`Imports` pass.
-}

module Cryptol.ModuleSystem.Renamer.ImplicitImports
  ( addImplicitNestedImports
  ) where

import Data.List(partition)

import Cryptol.Utils.Ident(identIsNormal, packModName)
import Cryptol.Utils.Panic(panic)
import Cryptol.Parser.Position(Range)
import Cryptol.Parser.AST

{- | Add additional imports for modules nested withing this one -}
addImplicitNestedImports :: [TopDecl PName] -> [TopDecl PName]
addImplicitNestedImports = snd . addImplicitNestedImports'

{- | Returns:

  * declarations with additional imports and
  * the public module names of this module and its children.
-}
addImplicitNestedImports' ::
  [TopDecl PName] -> ([[Ident]], [TopDecl PName])
addImplicitNestedImports' decls =
  (concat exportedMods, concat newDecls ++ other)
  where
  (mods,other)            = partition isNestedMod decls
  (newDecls,exportedMods) = unzip (map processModule mods)


processModule :: TopDecl PName -> ([TopDecl PName], [[Ident]])
processModule dcl =
  case dcl of
    DModule m ->
      let NestedModule m1 = tlValue m
          mname           = getIdent (thing (mName m1))
          loc             = srcRange (mName m1)
      in
      case mDef m1 of
        _ | not (identIsNormal mname) -> ([dcl],[])
        NormalModule ds ->
          let (childExs, ds1) = addImplicitNestedImports' ds
              imps            = map (mname :) ([] : childExs) -- this & nested
          in ( DModule m { tlValue = NestedModule m1 { mDef = NormalModule ds1 } }
             : map (mkImp loc) imps
             , case tlExport m of
                 Public  -> imps
                 Private -> []
             )

        FunctorInstance {} ->
          let imps = [[mname]]
          in ( dcl : map (mkImp loc) imps
             , case tlExport m of
                 Public  -> imps
                 Private -> []
             )
        InterfaceModule {} -> ([dcl], [])
    _ -> panic "processModule" ["Not a module"]



isNestedMod :: TopDecl name -> Bool
isNestedMod d =
  case d of
    DModule tl -> case tlValue tl of
                    NestedModule m -> not (mIsFunctor m)
    _          -> False

-- | Make a name qualifier out of a list of identifiers.
isToQual :: [Ident] -> ModName
isToQual is = packModName (map identText is)

-- | Make a module name out of a list of identifier.
-- This is the name of the module we are implicitly importing.
isToName :: [Ident] -> PName
isToName is = case is of
                [i] -> mkUnqual i
                _   -> mkQual (isToQual (init is)) (last is)

-- | Make an implicit import declaration.
mkImp :: Range -> [Ident] -> TopDecl PName
mkImp loc xs =
  DImport
    Located
      { srcRange = loc
      , thing    = Import
                     { iModule = ImpNested (isToName xs)
                     , iAs     = Just (isToQual xs)
                     , iSpec   = Nothing
                     , iInst   = Nothing
                     , iDoc    = Nothing
                     }
      }


