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

-- imports
import("core.theme.theme")
import("core.base.option")
import("core.base.semver")
import("core.project.config")
import("core.project.depend")
import("core.tool.toolchain")
import("lib.detect.find_file")
import("utils.progress")

-- escape path
function _escape_path(p)
    return os.args(p, {escape = true})
end

-- deploy application package for android
function main(target, opt)

    -- get ndk toolchain
    local toolchain_ndk = toolchain.load("ndk", {plat = target:plat(), arch = target:arch()})

    -- get target apk path
    local target_apk = path.join(target:targetdir(), target:basename() .. ".apk")

    -- need re-generate this apk?
    local targetfile = target:targetfile()
    local dependfile = target:dependfile(target_apk)
    local dependinfo = target:is_rebuilt() and {} or (depend.load(dependfile) or {})
    if not depend.is_changed(dependinfo, {lastmtime = os.mtime(dependfile)}) then
        return
    end

    -- trace progress info
    progress.show(opt.progress, "${color.build.target}generating.qt.app %s.apk", target:basename())

    -- get qt sdk
    local qt = target:data("qt")

    -- get ndk
    local ndk = path.translate(assert(toolchain_ndk:config("ndk"), "cannot get NDK!"))
    local ndk_sdkver = assert(toolchain_ndk:config("ndk_sdkver"), "cannot get the sdk version of NDK!")

    -- get ndk host
    local ndk_host = os.host() .. "-" .. os.arch()
    if is_host("windows") then
        ndk_host = os.arch() == "x64" and "windows-x86_64" or "windows-x86"
    elseif is_host("macosx") then
        ndk_host = "darwin-x86_64"
    elseif is_host("linux") then
        ndk_host = "linux-x86_64"
    end

    -- get androiddeployqt
    local search_dirs = {}
    if qt.bindir_host then table.insert(search_dirs, qt.bindir_host) end
    if qt.bindir then table.insert(search_dirs, qt.bindir) end
    local androiddeployqt = find_file("androiddeployqt" .. (is_host("windows") and ".exe" or ""), search_dirs)
    assert(os.isexec(androiddeployqt), "androiddeployqt not found!")

    -- get working directory
    local workdir = path.join(config.builddir(), ".qt", "app", "android", target:name())

    -- get android-build directory
    local android_buildir = path.join(workdir, "android-build")

    -- get android platform
    local android_platform = "android-" .. tostring(ndk_sdkver)

    -- get java home
    local java_home = assert(os.getenv("JAVA_HOME"), "please set $JAVA_HOME environment variable first!")

    -- get android sdk directory
    local android_sdkdir = path.translate(assert(toolchain_ndk:config("android_sdk"), "please run `xmake f --android_sdk=xxx` to set the android sdk directory!"))

    -- get android build-tools version
    local android_build_toolver = assert(toolchain_ndk:config("build_toolver"), "please run `xmake f --build_toolver=xxx` to set the android build-tools version!")

    -- get qt sdk version
    local qt_sdkver = config.get("qt_sdkver")
    if qt_sdkver then
        qt_sdkver = try { function () return semver.new(qt_sdkver) end}
    end

    -- get the target architecture
    local target_archs =
    {
        ["armv5te"]     = "armeabi"       -- deprecated
    ,   ["armv7-a"]     = "armeabi-v7a"   -- deprecated
    ,   ["armeabi"]     = "armeabi"
    ,   ["armeabi-v7a"] = "armeabi-v7a"
    ,   ["arm64-v8a"]   = "arm64-v8a"
    ,   i386            = "x86"
    ,   x86_64          = "x86_64"
    ,   mips            = "mips"          -- removed in ndk r71
    ,   mips64          = "mips64"        -- removed in ndk r71
    }
    local target_arch = assert(target_archs[config.arch()], "unsupport target arch(%s)!", config.arch())

    -- install target to android-build/libs first
    if qt_sdkver and qt_sdkver:ge("5.14") then
        -- we need to copy target to android-build/libs/armeabi/libxxx_armeabi.so after Qt 5.14.0
        os.cp(target:targetfile(), path.join(android_buildir, "libs", target_arch, "lib" .. target:basename() .. "_" .. target_arch .. ".so"))
    else
        os.cp(target:targetfile(), path.join(android_buildir, "libs", target_arch, path.filename(target:targetfile())))
    end

    -- get stdcpp path
    local stdcpp_path = path.join(ndk, "sources/cxx-stl/llvm-libc++/libs", target_arch, "libc++_shared.so")
    if qt_sdkver and qt_sdkver:ge("5.14") then
        local ndk_sysroot = assert(toolchain_ndk:config("ndk_sysroot"), "NDK sysroot directory not found!")
        stdcpp_path = path.join(ndk_sysroot, "usr", "lib")
    end

    -- get toolchain version
    local ndk_toolchains_ver = toolchain_ndk:config("ndk_toolchains_ver") or "4.9"

    -- generate android-deployment-settings.json file
    local android_deployment_settings = path.join(workdir, "android-deployment-settings.json")
    local settings_file = io.open(android_deployment_settings, "w")
    if settings_file then
        settings_file:print('{')
        settings_file:print('   "description": "This file is generated by qmake to be read by androiddeployqt and should not be modified by hand.",')
        settings_file:print('   "qt": "%s",', _escape_path(qt.sdkdir))
        settings_file:print('   "sdk": "%s",', _escape_path(android_sdkdir))
        settings_file:print('   "ndk": "%s",', _escape_path(ndk))
        settings_file:print('   "sdkBuildToolsRevision": "%s",', android_build_toolver)
        settings_file:print('   "toolchain-prefix": "llvm",')
        settings_file:print('   "tool-prefix": "llvm",')
        settings_file:print('   "toolchain-version": "%s",', ndk_toolchains_ver)
        settings_file:print('   "stdcpp-path": "%s",', _escape_path(stdcpp_path))
        settings_file:print('   "ndk-host": "%s",', ndk_host)
        settings_file:print('   "target-architecture": "%s",', target_arch)
        settings_file:print('   "qml-root-path": "%s",', _escape_path(os.projectdir()))
        -- for 6.2.x
        local search_dirs = {}
        if qt.libexecdir_host then table.insert(search_dirs, qt.libexecdir_host) end
        if qt.libexecdir then table.insert(search_dirs, qt.libexecdir) end
        local qmlimportscanner = find_file("qmlimportscanner" .. (is_host("windows") and ".exe" or ""), search_dirs)
        if os.isexec(qmlimportscanner) then
            settings_file:print('   "qml-importscanner-binary": "%s",', _escape_path(qmlimportscanner))
        end
        -- for 6.3.x
        local search_dirs = {}
        if qt.bindir_host then table.insert(search_dirs, qt.bindir_host) end
        if qt.bindir then table.insert(search_dirs, qt.bindir) end
        if qt.libexecdir_host then table.insert(search_dirs, qt.libexecdir_host) end
        if qt.libexecdir then table.insert(search_dirs, qt.libexecdir) end
        local rcc = find_file("rcc" .. (is_host("windows") and ".exe" or ""), search_dirs)
        if os.isexec(rcc) then
            settings_file:print('   "rcc-binary": "%s",', _escape_path(rcc))
        end
        local platformplugin = find_file("libplugins_platforms_qtforandroid_" .. target_arch .. "*", path.join(qt.sdkdir, "plugins", "platforms"))
        if platformplugin then
            settings_file:print('   "deployment-dependencies": {"%s":"%s"},', target_arch, _escape_path(platformplugin))
        end

        local minsdkversion = target:values("qt.android.minsdkversion")
        if minsdkversion then
            settings_file:print('    "android-min-sdk-version": "%s",', tostring(minsdkversion))
        end
        local targetsdkversion = target:values("qt.android.targetsdkversion")
        if targetsdkversion then
            settings_file:print('    "android-target-sdk-version": "%s",', tostring(targetsdkversion))
        end
        settings_file:print('   "useLLVM": true,')
        if qt_sdkver and qt_sdkver:ge("5.14") then
            -- @see https://codereview.qt-project.org/c/qt-creator/qt-creator/+/287145
            local triples =
            {
                ["armv5te"]     = "arm-linux-androideabi"   -- deprecated
            ,   ["armv7-a"]     = "arm-linux-androideabi"   -- deprecated
            ,   ["armeabi"]     = "arm-linux-androideabi"   -- removed in ndk r17
            ,   ["armeabi-v7a"] = "arm-linux-androideabi"
            ,   ["arm64-v8a"]   = "aarch64-linux-android"
            ,   i386            = "i686-linux-android"      -- deprecated
            ,   x86             = "i686-linux-android"
            ,   x86_64          = "x86_64-linux-android"
            ,   mips            = "mips-linux-android"      -- removed in ndk r17
            ,   mips64          = "mips64-linux-android"    -- removed in ndk r17
            }
            settings_file:print('   "architectures": {"%s":"%s"},', target_arch, triples[target_arch])
            settings_file:print('   "application-binary": "%s"', target:basename())
        else
            settings_file:print('   "application-binary": "%s"', _escape_path(target:targetfile()))
        end
        settings_file:print('}')
        settings_file:close()
    end
    if option.get("verbose") and option.get("diagnosis") then
        io.cat(android_deployment_settings)
    end

    -- do deploy
    local argv = {"--input", android_deployment_settings,
                  "--output", android_buildir,
                  "--jdk", java_home,
                  "--gradle", "--no-gdbserver"}
    if option.get("verbose") and option.get("diagnosis") then
        table.insert(argv, "--verbose")
    end

    -- add user flags
    local user_flags = target:values("qt.deploy.flags") or {}
    if user_flags then
        argv = table.join(argv, user_flags)
    end

    os.vrunv(androiddeployqt, argv)

    -- output apk
    os.cp(path.join(android_buildir, "build", "outputs", "apk", "debug", "android-build-debug.apk"), target_apk)

    -- show apk output path
    vprint("the apk output path: %s", target_apk)

    -- update files and values to the dependent file
    dependinfo.files = {targetfile}
    depend.save(dependinfo, dependfile)
end

