----- MISSION OPTIONS DEFAULT -----
mission = mission or {}
mission.requires = mission.requires or {}

----- DEBUG OPTIONS DEFAULT (default = off) -----
MISSION_DIR = MISSION_DIR or "/"
mission.DEBUG = mission.DEBUG or false
mission.MREG = mission.MREG or {}
mission.INIT_FILE = mission.INIT_FILE or ""

--- returns pointer to table required by the name string
---
--- EXAMPLE: "mission.test.abc" will retun mission["test"]["abc"]
---
--- INFO: this function is implemented via recursion
--- @param mName string name of module
--- @param parent? table parent of module. Defaults to _G (global registry)
--- @return table
function mission.getModuleTable(mName, parent)
    parent = parent or _G

    local del = mName:find(".", 1, true)

    -- if there is a "." inside the name => there must be a submodule ("." in modlue names are forbidden)
    if del ~= nil then
        -- retrieve pointer to parent table out of the last table
        parent = parent[mName:sub(1, del - 1)]

        -- since there was a ".", the next table must exist. If not our module could not be found
        if parent == nil then
            return nil
        end

        -- go further down the module stack, by removing the "." from the name paremeter and calling function again
        return(mission.getModuleTable(mName:sub(del + 1, #mName), parent))
    end

    -- there is no more submodules => return the current module
    return(parent[mName])
end

--- used for checking mission requirements (modules)
---
--- WARNING: if not present inside the MREG this function assumes for subomdules to be present in its parent meta folder
---
--- WARNING: this function throws uncaught errors if modules are unknown or not importable
--- @param required string[] required modules. maps to mission.requires when first called
--- @param known? string[] known fields. Used, to check wich module already got imported
--- @return table @pointer to known table so caller can check if every module got imported correctly
function mission.requireCheck(required, known)
    required = required or {}
    known = known or {}

    for _i, mName in pairs(required) do
        -- if module already known => no need to check if its loaded
        if not known[mName] then
            -- attempt to find module in global or load by debug reg
            local module = mission.getModuleTable(mName) or mission.requireDebug(mName)

            -- module could not be found or loaded => not present
            if module == nil then
                error("Module " .. mName .. " required but not loaded.\nAborting Mission load")
            end

            -- unpack is technically deprecated (moved to table.unpack).
            -- this workaround is in preperation if DCS ever updates its lua version.
            local upck = table.unpack or unpack

            -- requires and contains are technically indentical => set all of them as next import
            local modRequires = { upck(module.requires or {}), upck(module.contains or {}) }

            -- module is loaded by mission Editor or external scripts before mission script init
            known[mName] = true
            mission.requireCheck(modRequires, known)
        end
    end

    -- return a list of all used modules so the caller can check if every module was imported correctly
    return(known)
end

--- used to check if moduels can / must be imported
---
--- WARNING: this function throws uncaught errors if modules are unknown or not importable
---
--- INFO: this function requires the existence of mission.DEBUG and mission.MREG to work as intended
--- @param mName string name of module
--- @return table
function mission.requireDebug(mName)
    -- just to be a bit more fail proof (default)
    mission.DEBUG = mission.DEBUG or false
    mission.MREG = mission.MREG or {}

    -- mission.DEBUG flag not set => abort loading
    if not mission.DEBUG then
        return nil
    end

    -- check if module is a submodule (includes ".")
    local delimiter = mName:find("%.")

    -- find name of child and parent module. If the tested module is not a submodule both paths will be equal
    local parentName = mName:sub(1, (delimiter or #mName + 1) - 1)
    local childName = mName:sub((delimiter or 0) + 1, #mName)

    -- determine path of parent and path of the module itself
    local pPath, cPath = mission.MREG[parentName], mission.MREG[mName]

    if  pPath == nil and cPath == nil then
        error("no valid path for module " .. childName .. " or its parent " .. parentName .. " was found")
    end

    -- determine the path of the module itself. This is either the child or a combination of its parent (- the module.lua) and the meta folder
    local mPath = (cPath or pPath .. "/meta") .. "/" .. childName .. ".lua"

    -- import module file and return pointer to table
    if not pcall(dofile, mPath) then
        error("ERROR on Import.\nModule " .. mName .. " not found inside either the specified MREG entry or its parent META folder\nSearched Path: " .. tostring(mPath))
    end

    return mission.getModuleTable(mName)
end

--- executetd one time on mission start
function mission.init()
    -- check / import required modules
    mission.requireCheck(mission.requires)

    -- initialize all required modules and their submodules
    for _i, mName in pairs(mission.requires) do
        local mTable = mission.getModuleTable(mName)

        if mTable ~= nil and type(mTable.init) == "function" then
            mTable.init()
        elseif not mTable.IS_COLLECTION then
            env.warning("Module " .. tostring(mName) .. " loaded without init(). For function collections set " .. tostring(mName) .. ".IS_COLLECTION to true", true)
        end
    end

    -- register all loaded modules (used for better and faster error stack building)
    util.error.registerModules()

    -- trigger mission loaded info
    env.info("\n---------------- MISSION SCRIPT INITIALIZED ----------------")
    trigger.action.outText("SCRIPT INITIALIZED", 10, false)
end