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

-- imports
import("core.base.option")
import("core.base.semver")
import("core.project.config")
import("core.tool.toolchain")
import("core.platform.platform")
import("lib.detect.find_tool")
import("devel.git")
import("net.fasturl")
import("private.utils.toolchain", {alias = "toolchain_utils"})

-- get build env
function _conan_get_build_env(name, plat)
    local value = config.get(name)
    if value == nil then
        value = platform.toolconfig(name, plat)
    end
    if value == nil then
        value = platform.tool(name, plat)
    end
    value = table.unique(table.wrap(value))
    if #value > 0 then
        value = table.unwrap(value)
        return value
    end
end

-- get build directory
function _conan_get_build_directory(name)
    return path.absolute(path.join(config.builddir() or os.tmpdir(), ".conan", name))
end

-- generate conanfile.txt
function _conan_generate_conanfile(name, configs, opt)

    -- trace
    dprint("generate %s ..", path.join(_conan_get_build_directory(name), "conanfile.txt"))

    -- get conan options, imports and build_requires
    local options        = table.wrap(configs.options)
    local imports        = table.wrap(configs.imports)
    local build_requires = table.wrap(configs.build_requires or "xmake_generator/0.1.0@bincrafters/testing")

    -- @see https://docs.conan.io/en/latest/systems_cross_building/cross_building.html
    -- generate it
    local conanfile = io.open("conanfile.txt", "w")
    if conanfile then
        conanfile:print("[generators]")
        conanfile:print("xmake")
        conanfile:print("[requires]")
        local require_version = opt.require_version
        if require_version ~= nil and require_version ~= "latest" then
            conanfile:print("%s/%s", name, require_version)
        else
            conanfile:print("%s", name)
        end
        if #options > 0 then
            conanfile:print("[options]")
            for _, item in ipairs(options) do
                local item = item
                if not item:find(":", 1, true) then
                    item = name .. ":" .. item
                end
                conanfile:print("%s", item)
            end
        end
        if #imports > 0 then
            conanfile:print("[imports]")
            conanfile:print("%s", table.concat(imports, "\n"))
        end
        if #build_requires > 0 then
            conanfile:print("[build_requires]")
            conanfile:print("%s", table.concat(build_requires, "\n"))
        end
        conanfile:close()
    end
end

-- install xmake generator
function _conan_install_xmake_generator(conan)
    local xmake_generator_localdir = path.join(config.directory(), "conan", "xmake_generator")
    if not os.isdir(xmake_generator_localdir) then

        -- sort main urls
        local mainurls = {"https://github.com/xmake-io/conan-xmake_generator.git",
                          "https://gitlab.com/xmake-io/conan-xmake_generator.git",
                          "https://gitee.com/xmake-io/conan-xmake_generator.git"}
        fasturl.add(mainurls)
        mainurls = fasturl.sort(mainurls)

        -- clone xmake generator repository
        local ok = false
        for _, url in ipairs(mainurls) do
            ok = try { function () git.clone(url, {depth = 1, branch = "0.1.0/testing", outputdir = xmake_generator_localdir}); return true end }
            if ok then
                break
            end
        end
        if ok then
            os.vrunv(conan.program, {"export", xmake_generator_localdir, "bincrafters/testing"})
        end
    end
end

-- install package
function main(conan, name, opt)

    -- get configs
    opt = opt or {}
    local configs = opt.configs or {}

    -- get build directory
    local builddir = _conan_get_build_directory(name)

    -- clean the build directory
    os.tryrm(builddir)
    if not os.isdir(builddir) then
        os.mkdir(builddir)
    end

    -- enter build directory
    local oldir = os.cd(builddir)

    -- install xmake generator
    _conan_install_xmake_generator(conan)

    -- generate conanfile.txt
    _conan_generate_conanfile(name, configs, opt)

    -- install package
    local argv = {"install", "."}
    if configs.build then
        if configs.build == "all" then
            table.insert(argv, "--build")
        else
            table.insert(argv, "--build=" .. configs.build)
        end
    end

    -- set platform
    local plats = {macosx = "Macos", windows = "Windows", mingw = "Windows", linux = "Linux", cross = "Linux", iphoneos = "iOS", android = "Android"}
    table.insert(argv, "-s")
    local plat = plats[opt.plat]
    if plat then
        table.insert(argv, "os=" .. plat)
    else
        raise("cannot install package(%s) on platform(%s)!", name, opt.plat)
    end

    -- set architecture
    local archs = {x86_64          = "x86_64",
                   x64             = "x86_64",
                   i386            = "x86",
                   x86             = "x86",
                   armv7           = "armv7",
                   ["armv7-a"]     = "armv7",  -- for android, deprecated
                   ["armeabi"]     = "armv7",  -- for android, removed in ndk r17
                   ["armeabi-v7a"] = "armv7",  -- for android
                   armv7s          = "armv7s", -- for iphoneos
                   arm64           = "armv8",  -- for iphoneos
                   ["arm64-v8a"]   = "armv8",  -- for android
                   mips            = "mips",
                   mips64          = "mips64"}
    table.insert(argv, "-s")
    local arch = archs[opt.arch]
    if arch then
        table.insert(argv, "arch=" .. arch)
    else
        raise("cannot install package(%s) for arch(%s)!", name, opt.arch)
    end

    -- set build mode
    table.insert(argv, "-s")
    if opt.mode == "debug" then
        table.insert(argv, "build_type=Debug")
    else
        table.insert(argv, "build_type=Release")
    end

    -- set compiler settings
    if opt.plat == "windows" then
        local vs = assert(config.get("vs"), "vs not found!")
        table.insert(argv, "-s")
        table.insert(argv, "compiler=Visual Studio")
        table.insert(argv, "-s")
        table.insert(argv, "compiler.version=" .. toolchain_utils.get_vsver(vs))
        if configs.runtimes then
            table.insert(argv, "-s")
            table.insert(argv, "compiler.runtime=" .. configs.runtimes)
        end
    elseif opt.plat == "iphoneos" then
        local target_minver = nil
        local toolchain_xcode = toolchain.load("xcode", {plat = opt.plat, arch = opt.arch})
        if toolchain_xcode then
            target_minver = toolchain_xcode:config("target_minver")
        end
        if target_minver and tonumber(target_minver) > 10 and (arch == "armv7" or arch == "armv7s" or arch == "x86") then
            target_minver = "10" -- iOS 10 is the maximum deployment target for 32-bit targets
        end
        if target_minver then
            table.insert(argv, "-s")
            table.insert(argv, "os.version=" .. target_minver)
        end
    elseif opt.plat == "android" then
        local ndk_sdkver = config.get("ndk_sdkver")
        if ndk_sdkver then
            table.insert(argv, "-s")
            table.insert(argv, "os.api_level=" .. ndk_sdkver)
        end
    end

    -- set custom settings
    for _, setting in ipairs(configs.settings) do
        table.insert(argv, "-s")
        table.insert(argv, setting)
    end

    -- set remote
    if configs.remote then
        table.insert(argv, "-r")
        table.insert(argv, configs.remote)
    end

    -- TODO set environments
    if opt.plat == "android" then
        local envs = {}
        local cflags   = table.join(table.wrap(_conan_get_build_env("cxflags", opt.plat)), _conan_get_build_env("cflags", opt.plat))
        local cxxflags = table.join(table.wrap(_conan_get_build_env("cxflags", opt.plat)), _conan_get_build_env("cxxflags", opt.plat))
        envs.CC        = _conan_get_build_env("cc", opt.plat)
        envs.CXX       = _conan_get_build_env("cxx", opt.plat)
        envs.AS        = _conan_get_build_env("as", opt.plat)
        envs.AR        = _conan_get_build_env("ar", opt.plat)
        envs.LD        = _conan_get_build_env("ld", opt.plat)
        envs.LDSHARED  = _conan_get_build_env("sh", opt.plat)
        envs.CPP       = _conan_get_build_env("cpp", opt.plat)
        envs.RANLIB    = _conan_get_build_env("ranlib", opt.plat)
        envs.CFLAGS    = table.concat(cflags, ' ')
        envs.CXXFLAGS  = table.concat(cxxflags, ' ')
        envs.ASFLAGS   = table.concat(table.wrap(_conan_get_build_env("asflags", opt.plat)), ' ')
        envs.ARFLAGS   = table.concat(table.wrap(_conan_get_build_env("arflags", opt.plat)), ' ')
        envs.LDFLAGS   = table.concat(table.wrap(_conan_get_build_env("ldflags", opt.plat)), ' ')
        envs.SHFLAGS   = table.concat(table.wrap(_conan_get_build_env("shflags", opt.plat)), ' ')
        local toolchain_ndk = toolchain.load("ndk", {plat = opt.plat, arch = opt.arch})
        local ndk_sysroot = toolchain_ndk:config("ndk_sysroot")
        if ndk_sysroot then
            table.insert(argv, "-e")
            table.insert(argv, "CONAN_CMAKE_FIND_ROOT_PATH=" .. ndk_sysroot)
        end
        for k, v in pairs(envs) do
            table.insert(argv, "-e")
            table.insert(argv, k .. "=" .. v)
        end
    end

    -- do install
    os.vrunv(conan.program, argv)

    -- leave build directory
    os.cd(oldir)
end
