-- elua module system

local M = {}

local cutil = select(2, ...)

local preload = {
    ffi   = package.preload["ffi"],
    cutil = function() return cutil end
}

local loaded = {
}

M.loaded  = loaded
M.preload = preload

for k, v in pairs(package.loaded) do loaded[k] = v end

M.path  = "./?.lua;/?/init.lua"
M.cpath = ""
M.apath = "./?.lua"

local loadlib = package.loadlib

local cloader_load = function(soname, modname)
    local hyp = modname:find("%-")
    if hyp then modname = modname:sub(hyp + 1) end
    local  f, err = loadlib(soname, "luaopen_" .. (modname:gsub("%.", "_")))
    if not f then
        error("error loading module '" .. modname .. "' from file '"
            .. fname .. "':\n" .. err, 3)
    end
    return f
end

local loaders = {
    function(modname)
        local  v = preload[modname]
        if not v then
            return ("\no field preload['%s']"):format(modname)
        end
        return v
    end,
    function(modname)
        local  fname, err = package.searchpath(modname, M.path)
        if not fname then return err end
        local  f, err = loadfile(fname)
        if not f then
            error("error loading module '" .. modname .. "' from file '"
                .. fname .. "':\n" .. err, 2)
        end
        return f
    end,
    function(modname)
        local  fname, err = package.searchpath(modname, M.cpath)
        if not fname then return err end
        local hyp = modname:find("%-")
        if hyp then modname = modname:sub(hyp + 1) end
        return cloader_load(fname, modname)
    end,
    function(modname)
        local rootname, dot = modname, modname:find("%.")
        if dot then
            rootname = rootname:sub(dot + 1)
        end
        local  fname, err = package.searchpath(rootname, M.cpath)
        if not fname then return err end
        return cloader_load(fname, modname)
    end
}
M.loaders = loaders

local find_loader = function(modname, env)
    env = env or _G
    local err = { ("module '%s' not found\n"):format(modname) }
    for i = 1, #loaders do
        local v = loaders[i](modname)
        if type(v) == "function" then
            local status, ret = pcall(function() return setfenv(v, env) end)
            return status and ret or v
        elseif type(v) == "string" then
            err[#err + 1] = v
        end
    end
    return nil, table.concat(err)
end

M.require = function(modname, ...)
    local v = loaded[modname]
    if v ~= nil then return v end
    local  loader, err = find_loader(modname)
    if not loader then error(err, 2) end
    local ret = loader(modname, ...)
    if ret ~= nil then
        loaded[modname] = ret
        return ret
    elseif loaded[modname] == nil then
        loaded[modname] = true
        return true
    end
    return loaded[modname]
end

M.load_app = function(appname)
    local  fname, err = package.searchpath(appname, M.apath)
    if not fname then return nil, err end
    local f, err = loadfile(fname)
    if not f then return nil, err end
    return f
end

M.preload = preload
M.loaded  = loaded

-- register require
M.path, M.apath = (...)(M.require, M.load_app, M.path, M.apath)

M.config     = package.config
M.searchpath = package.searchpath
M.loadlib    = package.loadlib

_G["require"] = M.require
_G["package"] = M

return M
