module UHC.Light.Compiler.Module.Merge
( ModDbBindLetInfo''
, ModDbBindArray'
, ModPuller (..)
, modMergeByPullingInM
, modMergeByPullingIn )
where
import UHC.Light.Compiler.Base.Common
import Data.List
import Data.Maybe
import qualified Data.Set as Set
import qualified Data.Map as Map
import UHC.Util.Utils
import Control.Monad.Identity
import Control.Monad.State
import Data.Array
import qualified UHC.Util.FastSeq as Seq

{-# LINE 31 "src/ehc/Module/Merge.chs" #-}
-- | the binding info required for let bind
type ModDbBindLetInfo'' f cat bind = (cat,f bind)
type ModDbBindLetInfo'2   cat bind = ModDbBindLetInfo'' [] cat            bind

{-# LINE 37 "src/ehc/Module/Merge.chs" #-}
-- | actual bindings stored in separate array to allow for sharing
type ModDbBindArray' cat bind = Array Int (ModDbBindLetInfo'' (Array Int) cat bind)

{-# LINE 46 "src/ehc/Module/Merge.chs" #-}
-- | Abstraction of how pulling from a module to something else is done
class ModPuller modFr modsDb modsRem modTo expr cat bind
{- 20140928 TBD: not properly refactored yet, in particular the functional dpds are too restrictive...
-}
    | modFr -> expr cat bind modsRem modsDb modTo
    , expr -> modFr
    , cat -> modFr
    , bind -> modFr
    , modsRem -> modFr
    , modsDb -> modFr
    , modTo -> modFr
  where
    -- | Split main + imported into (1) root expr, (2) root bindings also visible and to be included, (3) part on which merging takes place and (4) a remainder
    mpullSplit :: modFr -> [modFr] -> (expr, Maybe (cat,[(bind,HsNameS)]), modsDb, modsRem)

    -- | Extract bindings for a name, consisting of a category, set of bindings, and the bound names (including the one asked for) pulled in.
    mpullUsedBindings :: Monad m => HsName -> modsDb -> m (Maybe (cat, [bind], HsNameS))

    -- | Extract expr's relevant for inducing further pullins
    mpullRelevantExprs :: bind -> [expr]

    -- | Extract free names/vars from expr
    mpullFreeVars :: expr -> HsNameS

    -- | Combine bindings + root expr into result module
    mpullBindingsAddToMod :: modsRem -> expr -> [(cat,[bind])] -> modTo -> modTo

{-# LINE 79 "src/ehc/Module/Merge.chs" #-}
data PullState cat bind
  = PullState
      { pullstBinds     :: Seq.Seq (ModDbBindLetInfo'2 cat bind)  	-- ^ pulled in bindings
      , pullstPulledNmS :: !HsNameS                             	-- ^ pulled in names
      , pullstToDo      :: ![HsName]                            	-- ^ names todo
      }

emptyPullState :: PullState cat bind
emptyPullState = PullState Seq.empty Set.empty []

{-# LINE 91 "src/ehc/Module/Merge.chs" #-}
type ModMergeT cat bind m a = StateT (PullState cat bind) m a

{-# LINE 95 "src/ehc/Module/Merge.chs" #-}
-- | merge by pulling in that which is required only, monadically
modMergeByPullingInM
  :: ( ModPuller modFr modsDb modsRem modTo expr cat bind
     , Monad m
     )
  => (modFr, [modFr])			    -- ^ main and imported
     -> ModMergeT cat bind m
          ( (modTo -> modTo)        -- conversion of resulting module
          , HsNameS                 -- modules from which something was taken
          )
modMergeByPullingInM (modMain,modImpL) = do
    let (rootExpr,mbExports,modDb,modRem) = mpullSplit modMain modImpL
    put $ emptyPullState {pullstToDo = Set.toList $ Set.unions $ mpullFreeVars rootExpr : (maybe [] (map snd . snd) mbExports)}
    pull modDb
    when (isJust mbExports) $
      let (Just (exportCateg,rootExports)) = mbExports
      in  modify $ \st -> st {pullstBinds = pullstBinds st `Seq.union` Seq.fromList [ (exportCateg,[b]) | (b,_) <- rootExports ]}
    final <- get
    return
      ( mpullBindingsAddToMod modRem rootExpr (Seq.toList $ pullstBinds final)
      , Set.map (panicJust "modMergeByPullingInM" . hsnQualifier) $ pullstPulledNmS final
      )
  where
    pull modDb = do
      s <- get
      case pullstToDo s of
        (nm:nmRest)
          | nm `Set.notMember` pullstPulledNmS s {- && isJust mbPull -} -> do
              mbPull@(~(Just (cat,binds,pulled))) <- lift $ mpullUsedBindings nm modDb
              if isJust mbPull
                then do
                  let pulledNms = pullstPulledNmS s `Set.union` pulled
                      newNms
                        = (Set.unions $ map (Set.unions . map mpullFreeVars . mpullRelevantExprs) binds)
                          `Set.difference` pulledNms
                  put $
                    s { pullstToDo = Set.toList newNms ++ nmRest
                      , pullstBinds = Seq.singleton (cat,binds) `Seq.union` pullstBinds s
                      , pullstPulledNmS = pulledNms
                      }
                  pull modDb
                else
                  dfltContinue
          | otherwise -> dfltContinue
          where -- mbPull@(~(Just (cat,binds,pulled))) = pullIn nm
                dfltContinue = do
                  put $ s { pullstToDo = nmRest }
                  pull modDb
        _ -> return ()

{-# LINE 147 "src/ehc/Module/Merge.chs" #-}
-- | merge by pulling in that which is required only
modMergeByPullingIn
  :: ModPuller modFr modsDb modsRem modTo expr cat bind
  => (modFr, [modFr])		     	-- ^ main and imported
     -> ( (modTo -> modTo)          -- conversion of resulting module
        , HsNameS                   -- modules from which something was taken
        )
modMergeByPullingIn mods = flip evalState emptyPullState $ modMergeByPullingInM mods