--!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, Xmake Open Source Community.
--
-- @author      ruki
-- @file        idl.lua
--

-- imports
import("lib.detect.find_tool")
import("core.project.depend")
import("utils.progress")
import("private.action.build.object", {alias = "build_objectfiles"})

function generate_single(target, sourcefile, opt)
    local msvc = target:toolchain("msvc") or target:toolchain("clang-cl") or target:toolchain("clang")
    local midl = assert(find_tool("midl", {envs = msvc:runenvs(), toolchain = msvc}), "midl not found!")

    local name = path.basename(sourcefile)
    local fileconfig = target:fileconfig(sourcefile)
    local autogendir = path.join(target:autogendir(), "platform/windows/idl")

    local enable_server
    local enable_client
    local defs = table.wrap(target:get("defines") or {})
    local incs = table.wrap(target:get("includedirs") or {})
    local undefs = table.wrap(target:get("undefines") or {})

    if fileconfig then
        enable_server = fileconfig.server
        enable_client = fileconfig.client
        if fileconfig.includedirs then
            table.join2(incs, fileconfig.includedirs)
        end
        if fileconfig.defines then
            table.join2(defs, fileconfig.defines)
        end
        if fileconfig.undefines then
            table.join2(undefs, fileconfig.undefines)
        end
    end

    local flags = {"/nologo"}
    table.join2(flags, table.wrap(target:values("idl.flags")))

    -- specify warn levels
    local warns = target:get("warnings")
    if warns == "none" then
        table.insert(flags, "/W0")
        table.insert(flags, "/no_warn")
    elseif warns == "less" then
        table.insert(flags, "/W1")
    elseif warns == "more" then
        table.insert(flags, "/W2")
    elseif warns == "extra" then
        table.insert(flags, "/W3")
    elseif warns == "error" then
        table.insert(flags, "/WX")
        table.insert(flags, "/W4")
    end

    -- add include dirs, defines and undefines from compiler flags
    for _, inc in ipairs(incs) do
        table.insert(flags, "/I")
        table.insert(flags, path(inc))
    end

    for _, def in ipairs(defs) do
        table.insert(flags, "/D")
        table.insert(flags, def)
    end

    for _, undef in ipairs(undefs) do
        table.insert(flags, "/U")
        table.insert(flags, undef)
    end

    table.join2(flags, {
        "/out",    path(autogendir),
        "/header", name .. ".h",
        "/iid",    name .. "_i.c",
        "/proxy",  name .. "_p.c",
        "/tlb",    name .. ".tlb",
        "/cstub",  name .. "_c.c",
        "/sstub",  name .. "_s.c",
        "/server", (enable_server and "stub" or "none"),
        "/client", (enable_client and "stub" or "none"),
        path(sourcefile)
    })

    depend.on_changed(function()
        progress.show(opt.progress or 0, "${color.build.object}generating.idl %s", sourcefile)
        os.vrunv(midl.program, flags, { envs = msvc:runenvs() })
    end, {
        files = sourcefile,
        dependfile = target:dependfile(sourcefile),
        changed = target:is_rebuilt()
    })
end

function configure(target)
    local sourcebatch = target:sourcebatches()["platform.windows.idl"]
    if sourcebatch then
        local autogendir = path.join(target:autogendir(), "platform/windows/idl")
        os.mkdir(autogendir)
        target:add("includedirs", autogendir, {public = true})
    end
end

function generate_idl(target, jobgraph, sourcebatch, opt)
    for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
        local midljob = target:fullname() .. "/generate/" .. sourcefile
        jobgraph:add(midljob, function (index, total, opt)
            generate_single(target, sourcefile, opt)
        end)
    end
end

function build_idlfiles(target, jobgraph, sourcebatch, opt)
    local autogendir = path.join(target:autogendir(), "platform/windows/idl")
    for _, sourcefile in ipairs(sourcebatch.sourcefiles) do
        local fileconfig = target:fileconfig(sourcefile)
        local files = {"_i.c", "_c.c", "_s.c"}
        if fileconfig and fileconfig.proxy then
            table.insert(files, "_p.c")
        end

        local name = path.basename(sourcefile)
        for _, suffix in ipairs(files) do
            local c_sourcefile = path.join(autogendir, name .. suffix)
            local objectfile = target:objectfile(c_sourcefile)
            local dependfile = target:dependfile(objectfile)
            local cc_job_name = path.join(target:fullname(), "/obj/", c_sourcefile)
            jobgraph:add(cc_job_name, function (index, total, jobopt)
                -- we don't have a way to detect which midl files are generated
                if os.exists(c_sourcefile) then
                    table.insert(target:objectfiles(), objectfile)
                    local build_opt = table.join({objectfile = objectfile, dependfile = dependfile, sourcekind = "cc", progress = jobopt.progress}, opt)
                    build_objectfiles.build_object(target, c_sourcefile, build_opt)
                end
            end, {distcc = opt.distcc})
        end
    end
end
