const initialized_modules = Set{String}(["Base", "Main"])

const imported_packages_PkgCompiler = Set{String}()
const dependency_tree = Dict{String, Set{String}}()
const what_to_import = Dict{String, Any}()

init_packages = false

function add_to_dependency_tree(parent::String, child::String)
    if !haskey(dependency_tree, parent)
        dependency_tree[parent] = Set{String}()
    end
    push!(dependency_tree[parent], child)
end

function _init_module_pkgcompiler(into::Module, mod::String)
    uuidkey = identify_package(into, mod)
    if uuidkey === nothing
        return
    end
    modul::Module = require(uuidkey)
    parent = "$(modul)"
    if !in(parent, initialized_modules)
        if parent != "PkgCompiler"
            push!(initialized_modules, parent)
            if haskey(dependency_tree, mod)
                for mod2 in dependency_tree[mod]
                    # @info("Try to init $(mod2)")
                    if mod2 !== nothing
                        _init_module_pkgcompiler(into, mod2)
                    end
                    if !in(mod2, initialized_modules) && haskey(what_to_import, mod2)
                        # @info("PkgCompiler inits submodule \"$(mod2)\"")
                        ccall(:jl_init_restored_modules, Cvoid, (Any,), what_to_import[mod2])
                    end
                end
            end
            if haskey(what_to_import, parent)
                # @info("PkgCompiler inits \"$(modul)\"")
                ccall(:jl_init_restored_modules, Cvoid, (Any,), what_to_import[parent])
            end
        end
    end
    nothing
end

function require(into::Module, mod::Symbol)
    uuidkey = identify_package(into, String(mod))
    # Core.println("require($(PkgId(into)), $mod) -> $uuidkey")
    if uuidkey === nothing
        where = PkgId(into)
        if where.uuid === nothing
            throw(ArgumentError("""
                Package $mod not found in current path:
                - Run `import Pkg; Pkg.add($(repr(String(mod))))` to install the $mod package.
                """))
        else
            s = """
            Package $(where.name) does not have $mod in its dependencies:
            - If you have $(where.name) checked out for development and have
              added $mod as a dependency but haven't updated your primary
              environment's manifest file, try `Pkg.resolve()`.
            - Otherwise you may need to report an issue with $(where.name)"""

            uuidkey = identify_package(PkgId(string(into)), String(mod))
            uuidkey === nothing && throw(ArgumentError(s))

            # fall back to toplevel loading with a warning
            if !(where in modules_warned_for)
                @warn string(
                    full_warning_showed[] ? "" : s, "\n",
                    string("Loading $(mod) into $(where.name) from project dependency, ",
                           "future warnings for $(where.name) are suppressed.")
                ) _module = nothing _file = nothing _group = nothing
                push!(modules_warned_for, where)
            end
            full_warning_showed[] = true
        end
    end
    if _track_dependencies[]
        push!(_require_dependencies, (into, binpack(uuidkey), 0.0))
    end
    _init_module_pkgcompiler(into, String(mod))
    return require(uuidkey)
end

# these return either the array of modules loaded from the path / content given
# or an Exception that describes why it couldn't be loaded
# and it reconnects the Base.Docs.META
function _include_from_serialized(path::String, depmods::Vector{Any})
    sv = ccall(:jl_restore_incremental, Any, (Cstring, Any), path, depmods)
    if isa(sv, Exception)
        return sv
    end
    restored = sv[1]
    if !isa(restored, Exception)
        for M in restored::Vector{Any}
            M = M::Module
            if isdefined(M, Base.Docs.META)
                push!(Base.Docs.modules, M)
            end
            if parentmodule(M) === M
                register_root_module(M)
            end
        end
    end
    if isassigned(sv, 2)
        if init_packages
            ccall(:jl_init_restored_modules, Cvoid, (Any,), sv[2])
        else
            modul = restored[1]
            if length(sv[2]) > 0
                # @info "To future init $(modul)" 
                parent = "$(parentmodule(modul))"
                what_to_import[parent] = sv[2]
            end
        end
    end
    return restored
end

