{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFoldable #-}
-- | See <https://github.com/ezyang/ghc-proposals/blob/backpack/proposals/0000-backpack.rst>
module Distribution.Backpack.ModuleScope (
    -- * Module scopes
    ModuleScope(..),
    ModuleProvides,
    ModuleRequires,
    ModuleSource(..),
    dispModuleSource,
    WithSource(..),
    unWithSource,
    getSource,
    ModuleWithSource,
    emptyModuleScope,
) where

import Prelude ()
import Distribution.Compat.Prelude

import Distribution.ModuleName
import Distribution.Types.IncludeRenaming
import Distribution.Types.PackageName
import Distribution.Types.ComponentName

import Distribution.Backpack
import Distribution.Backpack.ModSubst
import Distribution.Text

import qualified Data.Map as Map
import Text.PrettyPrint


-----------------------------------------------------------------------
-- Module scopes

-- Why is ModuleProvides so complicated?  The basic problem is that
-- we want to support this:
--
--  package p where
--      include q (A)
--      include r (A)
--      module B where
--          import "q" A
--          import "r" A
--
-- Specifically, in Cabal today it is NOT an error have two modules in
-- scope with the same identifier.  So we need to preserve this for
-- Backpack.  The modification is that an ambiguous module name is
-- OK... as long as it is NOT used to fill a requirement!
--
-- So as a first try, we might try deferring unifying provisions that
-- are being glommed together, and check for equality after the fact.
-- But this doesn't work, because what if a multi-module provision
-- is used to fill a requirement?!  So you do the equality test
-- IMMEDIATELY before a requirement fill happens... or never at all.
--
-- Alternate strategy: go ahead and unify, and then if it is revealed
-- that some requirements got filled "out-of-thin-air", error.


-- | A 'ModuleScope' describes the modules and requirements that
-- are in-scope as we are processing a Cabal package.  Unlike
-- a 'ModuleShape', there may be multiple modules in scope at
-- the same 'ModuleName'; this is only an error if we attempt
-- to use those modules to fill a requirement.  A 'ModuleScope'
-- can influence the 'ModuleShape' via a reexport.
data ModuleScope = ModuleScope {
    modScopeProvides :: ModuleProvides,
    modScopeRequires :: ModuleRequires
    }

-- | An empty 'ModuleScope'.
emptyModuleScope :: ModuleScope
emptyModuleScope = ModuleScope Map.empty Map.empty

-- | Every 'Module' in scope at a 'ModuleName' is annotated with
-- the 'PackageName' it comes from.
type ModuleProvides = Map ModuleName [ModuleWithSource]
-- | INVARIANT: entries for ModuleName m, have msrc_module is OpenModuleVar m
type ModuleRequires = Map ModuleName [ModuleWithSource]
-- TODO: consider newtping the two types above.

-- | Description of where a module participating in mixin linking came
-- from.
data ModuleSource
    = FromMixins         PackageName ComponentName IncludeRenaming
    | FromBuildDepends   PackageName ComponentName
    | FromExposedModules ModuleName
    | FromOtherModules   ModuleName
    | FromSignatures     ModuleName
-- We don't have line numbers, but if we did, we'd want to record that
-- too

-- TODO: Deduplicate this with Distribution.Backpack.UnifyM.ci_msg
dispModuleSource :: ModuleSource -> Doc
dispModuleSource (FromMixins pn cn incls)
  = text "mixins:" <+> dispComponent pn cn <+> disp incls
dispModuleSource (FromBuildDepends pn cn)
  = text "build-depends:" <+> dispComponent pn cn
dispModuleSource (FromExposedModules m)
  = text "exposed-modules:" <+> disp m
dispModuleSource (FromOtherModules m)
  = text "other-modules:" <+> disp m
dispModuleSource (FromSignatures m)
  = text "signatures:" <+> disp m

-- Dependency
dispComponent :: PackageName -> ComponentName -> Doc
dispComponent pn cn =
    -- NB: This syntax isn't quite the source syntax, but it
    -- should be clear enough.  To do source syntax, we'd
    -- need to know what the package we're linking is.
    case cn of
        CLibName -> disp pn
        CSubLibName ucn -> disp pn <<>> colon <<>> disp ucn
        -- Case below shouldn't happen
        _ -> disp pn <+> parens (disp cn)

-- | An 'OpenModule', annotated with where it came from in a Cabal file.
data WithSource a = WithSource ModuleSource a
    deriving (Functor, Foldable, Traversable)
unWithSource :: WithSource a -> a
unWithSource (WithSource _ x) = x
getSource :: WithSource a -> ModuleSource
getSource (WithSource s _) = s
type ModuleWithSource = WithSource OpenModule

instance ModSubst a => ModSubst (WithSource a) where
    modSubst subst (WithSource s m) = WithSource s (modSubst subst m)