--!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        has_flags.lua
--

-- imports
import("core.base.option")
import("core.base.scheduler")
import("core.base.profiler")
import("core.project.config")
import("core.cache.detectcache")
import("lib.detect.find_tool")

-- has the given flags for the current tool?
--
-- @param name      the tool name
-- @param flags     the flags
-- @param opt       the argument options, e.g. {force = true, verbose = false, program = "", sysflags = {}, flagkind = "cxflag", toolkind = "[cc|cxx|ld|ar|sh|gc|rc|dc|mm|mxx]", flagskey = "custom key" }
--
-- @return          true or false
--
-- @code
-- local ok = has_flags("clang", "-g")
-- local ok = has_flags("clang", {"-g", "-O0"}, {program = "xcrun -sdk macosx clang"})
-- local ok = has_flags("clang", "-g", {toolkind = "cxx"})
-- local ok = has_flags("clang", "-g", {on_check = function (ok, errors) return ok, errors end})
-- @endcode
--
function main(name, flags, opt)

    -- wrap flags first
    flags = table.clone(flags)
    table.wrap_unlock(flags)
    flags = table.wrap(flags)

    -- init options
    opt = opt or {}
    opt.flagskey = opt.flagskey or table.concat(flags, " ")
    opt.sysflags = table.wrap(opt.sysflags)

    -- @note avoid running additional `program --version` processes
    --opt.version = true

    -- find tool program and version first
    local tool = find_tool(name, opt)
    if not tool then
        return false
    end

    -- init tool
    opt.toolname   = tool.name
    opt.program    = tool.program
    --opt.programver = tool.version

    -- get tool platform
    local plat = opt.plat or config.get("plat") or os.host()

    -- get tool architecture
    --
    -- some tools select arch by path environment, not be flags, e.g. cl.exe of msvc)
    -- so, it will affect the cache result
    --
    local arch = opt.arch or config.get("arch") or os.arch()

    -- init cache key
    local key = plat .. "_" .. arch .. "_" .. tool.program .. "_"
              .. (tool.version or "") .. "_" .. (opt.toolkind or "")
              .. "_" .. (opt.flagkind or "") .. "_" .. table.concat(opt.sysflags, " ") .. "_" .. opt.flagskey

    -- @see https://github.com/xmake-io/xmake/issues/4645
    -- @note avoid detect the same program in the same time leading to deadlock if running in the coroutine (e.g. ccache)
    scheduler.co_lock(key)

    -- attempt to get result from cache first
    local cacheinfo = detectcache:get("lib.detect.has_flags")
    if not cacheinfo then
        -- since has_flags may be switched to other concurrent processes during cache saving,
        -- we need to commit the initialized cache to avoid multiple cache objects overwriting it.
        cacheinfo = {}
        detectcache:set("lib.detect.has_flags", cacheinfo)
    end
    local result = cacheinfo[key]
    if result ~= nil and not opt.force then
        scheduler.co_unlock(key)
        return result
    end

    -- generate all checked flags
    local checkflags = table.join(flags, opt.sysflags)

    -- split flag group, e.g. "-I /xxx" => {"-I", "/xxx"}
    local results = {}
    for _, flag in ipairs(checkflags) do
        local flag = flag:trim()
        if #flag > 0 then
            if flag:find(" ", 1, true) then
                table.join2(results, os.argv(flag))
            else
                table.insert(results, flag)
            end
        end
    end
    checkflags = results

    -- start profile
    profiler.enter("has_flags", tool.name, checkflags[1])

    -- core.tools.xxx.has_flags(flags, opt)?
    local hasflags = import("core.tools." .. tool.name .. ".has_flags", {try = true})
    local errors = nil
    if hasflags then
        result, errors = hasflags(checkflags, opt)
    else
        result = try { function () os.runv(tool.program, checkflags, {envs = opt.envs}); return true end, catch { function (errs) errors = errs end }}
    end
    if opt.on_check then
        result, errors = opt.on_check(result, errors)
    end
    result = result or false

    -- stop profile
    profiler.leave("has_flags", tool.name, checkflags[1])

    -- trace
    if option.get("verbose") or option.get("diagnosis") or opt.verbose then
        cprint("${dim}checking for flags (%s) ... %s", opt.flagskey, result and "${color.success}${text.success}" or "${color.nothing}${text.nothing}")
        if option.get("diagnosis") then
            cprint("${dim}> %s \"%s\"", path.filename(tool.program), table.concat(checkflags, "\" \""))
            if errors and #tostring(errors) > 0 then
                cprint("${color.warning}checkinfo:${clear dim} %s", tostring(errors):trim())
            end
        end
    end

    -- save result to cache
    cacheinfo[key] = result
    detectcache:set("lib.detect.has_flags", cacheinfo)
    scheduler.co_unlock(key)
    return result
end

