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

-- imports
import("core.base.json")
import("core.base.option")
import("core.project.config")
import("lib.detect.find_tool")
import("private.utils.executable_path")
import("private.action.run.runenvs")

-- run gdb
function _run_gdb(program, argv, opt)
    opt = opt or {}
    local gdb = find_tool("gdb", {program = config.get("debugger")})
    if not gdb then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)
    table.insert(argv, 1, "--args")

    -- run it
    os.execv(gdb.program, argv, table.join(opt, {exclusive = true}))
    return true
end

-- run cuda-gdb
function _run_cudagdb(program, argv, opt)
    opt = opt or {}
    local gdb = find_tool("cudagdb", {program = config.get("debugger")})
    if not gdb then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)
    table.insert(argv, 1, "--args")

    -- run it
    os.execv(gdb.program, argv, table.join(opt, {exclusive = true}))
    return true
end

-- run lldb
function _run_lldb(program, argv, opt)
    opt = opt or {}
    local lldb = find_tool("lldb", {program = config.get("debugger")})
    if not lldb then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, "--")
    table.insert(argv, 1, program)
    table.insert(argv, 1, "-f")

    -- run it
    os.execv(executable_path(lldb.program), argv, table.join(opt, {exclusive = true}))
    return true
end

-- run windbg
function _run_windbg(program, argv, opt)
    local windbg = find_tool("windbg", {program = config.get("debugger")})
    if not windbg then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    opt.detach = true
    os.execv(windbg.program, argv, opt)
    return true
end

-- run cuda-memcheck
function _run_cudamemcheck(program, argv, opt)
    local cudamemcheck = find_tool("cudamemcheck", {program = config.get("debugger")})
    if not cudamemcheck then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    os.execv(cudamemcheck.program, argv, opt)
    return true
end

-- run x64dbg
function _run_x64dbg(program, argv, opt)
    local x64dbg = find_tool("x64dbg", {program = config.get("debugger")})
    if not x64dbg then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    opt.detach = true
    os.execv(x64dbg.program, argv, opt)
    return true
end

-- run ollydbg
function _run_ollydbg(program, argv, opt)
    local ollydbg = find_tool("ollydbg", {program = config.get("debugger")})
    if not ollydbg then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    opt.detach = true
    os.execv(ollydbg.program, argv, opt)
    return true
end

-- run vsjitdebugger
function _run_vsjitdebugger(program, argv, opt)
    local vsjitdebugger = find_tool("vsjitdebugger", {program = config.get("debugger")})
    if not vsjitdebugger then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    opt.detach = true
    os.execv(vsjitdebugger.program, argv, opt)
    return true
end

-- run devenv
function _run_devenv(program, argv, opt)
    local devenv = find_tool("devenv", {program = config.get("debugger")})
    if not devenv then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, "/DebugExe")
    table.insert(argv, 2, program)

    -- run it
    opt.detach = true
    os.execv(devenv.program, argv, opt)
    return true
end

-- run renderdoc
function _run_renderdoc(program, argv, opt)
    local renderdoc = find_tool("renderdoc", {program = config.get("debugger")})
    if not renderdoc then
        return false
    end

    -- build capture settings
    local environment = {}
    if opt.addenvs then
        for name, values in pairs(opt.addenvs) do
            table.insert(environment, {
                separator = "Platform style",
                type = "Append",
                value = path.joinenv(values),
                variable = name
            })
        end
    end

    if opt.setenvs then
        for name, values in pairs(opt.setenvs) do
            table.insert(environment, {
                separator = "Platform style",
                type = "Set",
                value = path.joinenv(values),
                variable = name
            })
        end
    end

    local settings = {
        rdocCaptureSettings = 1,
        settings = {
            autoStart = false,
            commandLine = table.concat(table.wrap(argv), " "),
            environment = json.mark_as_array(environment),
            executable = program,
            inject = false,
            numQueuedFrames = 0,
            queuedFrameCap = 0,
            workingDir = opt.curdir and path.absolute(opt.curdir) or "",
            options = {
                allowFullscreen = true,
                allowVSync = true,
                apiValidation = false,
                captureAllCmdLists = false,
                captureCallstacks = false,
                captureCallstacksOnlyDraws = false,
                debugOutputMute = true,
                delayForDebugger = 0,
                hookIntoChildren = false,
                refAllResources = false,
                verifyBufferAccess = false
            }
        }
    }

    -- save to temporary file
    local capturefile = os.tmpfile() .. ".cap"
    json.savefile(capturefile, settings)

    -- run renderdoc
    opt.detach = true
    opt.addenvs = nil
    opt.setenvs = nil
    os.execv(renderdoc.program, { capturefile }, opt)
    return true
end

-- run gede
function _run_gede(program, argv, opt)
    opt = opt or {}

    -- 'gede --version' return with non-zero code
    local gede = find_tool("gede", {program = config.get("debugger"), norun = true})
    if not gede then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)
    table.insert(argv, 1, "--args")
    table.insert(argv, 1, "--no-show-config")

    -- run it
    os.execv(gede.program, argv, table.join(opt, {exclusive = true}))
    return true
end

-- run seergdb
function _run_seergdb(program, argv, opt)
    opt = opt or {}
    local seergdb = find_tool("seergdb", {program = config.get("debugger")})
    if not seergdb then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)
    table.insert(argv, 1, "--start")

    -- run it
    os.execv(seergdb.program, argv, table.join(opt, {exclusive = true}))
    return true
end

-- run rad debugger
function _run_raddbg(program, argv, opt)
    opt = opt or {}
    local raddbg = find_tool("raddbg", {program = config.get("debugger")})
    if not raddbg then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    opt.detach = true
    os.execv(raddbg.program, argv, opt)
    return true
end

-- run nnd
function _run_nnd(program, argv, opt)
    opt = opt or {}
    local nnd = find_tool("nnd", {program = config.get("debugger")})
    if not nnd then
        return false
    end

    -- patch arguments
    argv = argv or {}
    table.insert(argv, 1, program)

    -- run it
    os.execv(nnd.program, argv, table.join(opt, {exclusive = true}))
    return true
end

-- run program with debugger
--
-- @param program   the program name
-- @param argv      the program rguments
--
-- @code
--
-- import("devel.debugger")
--
-- debugger.run("test")
-- debugger.run("echo", {"hello xmake!"})
--
-- @endcode
--
function main(program, argv, opt)

    -- init debuggers
    local debuggers =
    {
        {"lldb"        , _run_lldb}
    ,   {"gdb"         , _run_gdb}
    ,   {"cudagdb"     , _run_cudagdb}
    ,   {"cudamemcheck", _run_cudamemcheck}
    ,   {"renderdoc"   , _run_renderdoc}
    ,   {"gede"        , _run_gede}
    ,   {"seergdb"     , _run_seergdb}
    }

    -- for windows target or on windows?
    local plat = config.plat() or os.host()
    if plat == "windows" then
        table.insert(debuggers, 1, {"windbg",           _run_windbg})
        table.insert(debuggers, 1, {"ollydbg",          _run_ollydbg})
        table.insert(debuggers, 1, {"x64dbg",           _run_x64dbg})
        table.insert(debuggers, 1, {"vsjitdebugger",    _run_vsjitdebugger})
        table.insert(debuggers, 1, {"devenv",           _run_devenv})
        table.insert(debuggers, 1, {"raddbg",           _run_raddbg})
    elseif plat == "linux" then
        table.insert(debuggers, {"nnd", _run_nnd})
    end

    -- get debugger from configuration
    opt = opt or {}
    local debugger = config.get("debugger")
    if debugger then

        -- try exactmatch first
        debugger = debugger:lower()
        local debuggername = path.basename(debugger)
        for _, _debugger in ipairs(debuggers) do
            if debuggername:startswith(_debugger[1]) then
                if _debugger[2](program, argv, opt) then
                    return
                end
            end
        end

        for _, _debugger in ipairs(debuggers) do
            if debugger:find(_debugger[1]) then
                if _debugger[2](program, argv, opt) then
                    return
                end
            end
        end
    else
        -- run debugger
        for _, _debugger in ipairs(debuggers) do
            if _debugger[2](program, argv, opt) then
                return
            end
        end
    end

    -- no debugger
    raise("debugger%s not found!", debugger and ("(" .. debugger .. ")") or "")
end
