--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, TBOOX Open Source Group.
--
-- @author      ruki
-- @file        module_parser.lua
--

-- imports
import("core.project.depend")
import("core.base.hashset")

-- get depend file of module source file
function _get_dependfile_of_modulesource(target, sourcefile)
    return target:dependfile(sourcefile)
end

-- get depend file of module object file, compiler will rewrite it
function _get_dependfile_of_moduleobject(target, sourcefile)
    local objectfile = target:objectfile(sourcefile)
    return target:dependfile(objectfile)
end

-- generate module deps for the given file
function _generate_moduledeps(target, sourcefile, opt)
    local dependfile = _get_dependfile_of_modulesource(target, sourcefile)
    depend.on_changed(function ()

        -- trace
        vprint("generating.moduledeps %s", sourcefile)

        -- generating deps
        local module_name
        local module_deps
        local sourcecode = io.readfile(sourcefile)
        sourcecode = sourcecode:gsub("//.-\n", "\n")
        sourcecode = sourcecode:gsub("/%*.-%*/", "")
        for _, line in ipairs(sourcecode:split("\n", {plain = true})) do
            if not module_name then
                module_name = line:match("export%s+module%s+(.+)%s*;")
            end
            local module_depname = line:match("import%s+(.+)%s*;")
            if module_depname then
                -- partition? import :xxx;
                if module_depname:startswith(":") then
                    module_depname = module_name .. module_depname
                end
                module_deps = module_deps or {}
                table.insert(module_deps, module_depname)
            end
        end

        -- save depend data
        if module_name then
            local dependinfo = {moduleinfo = {name = module_name, deps = module_deps, file = sourcefile}}
            return dependinfo
        end

    end, {dependfile = dependfile, files = {sourcefile}})
end

-- build batch jobs with deps
function _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo)
    local targetjob_ref = jobrefs[moduleinfo.name]
    if targetjob_ref then
        batchjobs:add(targetjob_ref, rootjob)
    else
        local modulejob = batchjobs:add(moduleinfo.job, rootjob)
        if modulejob then
            jobrefs[moduleinfo.name] = modulejob
            for _, depname in ipairs(moduleinfo.deps) do
                local dep = moduledeps[depname]
                if dep then -- maybe nil, e.g. `import <string>;`
                    _build_batchjobs_with_deps(moduledeps, batchjobs, modulejob, jobrefs, dep)
                end
            end
        end
    end
end

-- generate module deps
function generate(target, sourcebatch, opt)
    for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
        _generate_moduledeps(target, sourcefile, opt)
    end
end

-- load module deps
function load(target, sourcebatch, opt)

    -- do generate first
    generate(target, sourcebatch, opt)

    -- load deps
    local moduledeps
    for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
        local dependfile = _get_dependfile_of_modulesource(target, sourcefile)
        if os.isfile(dependfile) then
            local data = io.load(dependfile)
            if data then
                local moduleinfo = data.moduleinfo
                if moduleinfo then
                    moduledeps = moduledeps or {}
                    moduledeps[moduleinfo.name] = moduleinfo
                end
            end
        end
    end

    -- get moduledeps with file map
    local moduledeps_files = {}
    for _, moduleinfo in pairs(moduledeps) do
        moduledeps_files[moduleinfo.file] = moduleinfo
    end
    return moduledeps, moduledeps_files
end

-- build batch jobs
function build_batchjobs(moduledeps, batchjobs, rootjob)
    local depset = hashset.new()
    for _, moduleinfo in pairs(moduledeps) do
        assert(moduleinfo.job)
        for _, depname in ipairs(moduleinfo.deps) do
            depset:insert(depname)
        end
    end
    local moduledeps_root = {}
    for _, moduleinfo in pairs(moduledeps) do
        if not depset:has(moduleinfo.name) then
            table.insert(moduledeps_root, moduleinfo)
        end
    end
    local jobrefs = {}
    for _, moduleinfo in pairs(moduledeps_root) do
        _build_batchjobs_with_deps(moduledeps, batchjobs, rootjob, jobrefs, moduleinfo)
    end
end
