import qbs.File
import qbs.FileInfo
import qbs.ModUtils
import qbs.TextFile
import qbs.Utilities
import qbs.Process

Module {
    version: @version@
    property bool useMinistro: false
    property string qmlRootDir: product.sourceDirectory
    property stringList extraPrefixDirs
    property stringList deploymentDependencies // qmake: ANDROID_DEPLOYMENT_DEPENDENCIES
    property stringList extraPlugins // qmake: ANDROID_EXTRA_PLUGINS
    property stringList extraLibs // qmake: ANDROID_EXTRA_LIBS
    property bool verboseAndroidDeployQt: false

    property string _androidDeployQtFilePath: FileInfo.joinPaths(_qtInstallDir, "bin",
                                                                 "androiddeployqt")
    property string _qtInstallDir
    property bool _enableSdkSupport: product.type && product.type.contains("android.apk")
                                     && !consoleApplication
    property bool _enableNdkSupport: !product.aggregate || product.multiplexConfigurationId
    property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android")
    property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out")

    property bool _multiAbi: Utilities.versionCompare(version, "5.14") >= 0

    Depends { name: "Android.sdk"; condition: _enableSdkSupport }
    Depends { name: "Android.ndk"; condition: _enableNdkSupport }
    Depends { name: "java"; condition: _enableSdkSupport }
    Depends { name: "cpp" }

    Properties {
        condition: _enableNdkSupport && qbs.toolchain.contains("clang")
        Android.ndk.appStl: "c++_shared"
    }
    Properties {
        condition: _enableNdkSupport && !qbs.toolchain.contains("clang")
        Android.ndk.appStl: "gnustl_shared"
    }
    Properties {
        condition: _enableSdkSupport
        Android.sdk.customManifestProcessing: true
        java._tagJniHeaders: false // prevent rule cycle
    }
    Properties {
        condition: _enableNdkSupport && (Android.ndk.abi === "armeabi-v7a" || Android.ndk.abi === "x86")
        cpp.defines: "ANDROID_HAS_WSTRING"
    }
    Properties {
        condition: _enableSdkSupport
        Android.sdk._archInName: _multiAbi
        Android.sdk._bundledInAssets: _multiAbi
    }

    Rule {
        condition: _enableSdkSupport
        multiplex: true
        property stringList inputTags: "android.nativelibrary"
        inputsFromDependencies: inputTags
        inputs: product.aggregate ? [] : inputTags
        Artifact {
            filePath: "androiddeployqt.json"
            fileTags: "qt_androiddeployqt_input"
        }
        prepare: {
            var cmd = new JavaScriptCommand();
            cmd.description = "creating " + output.fileName;
            cmd.sourceCode = function() {
                var theBinary;
                var nativeLibs = inputs["android.nativelibrary"];
                var architectures = [];
                var triples = [];
                var hostArch;
                var targetArchitecture;
                if (nativeLibs.length === 1) {
                    theBinary = nativeLibs[0];
                    hostArch = theBinary.Android.ndk.hostArch;
                    targetArchitecture = theBinary.Android.ndk.abi;
                    if (product.Qt.android_support._multiAbi) {
                        architectures.push(theBinary.Android.ndk.abi);
                        triples.push(theBinary.cpp.toolchainTriple);
                    }
                } else {
                    for (i = 0; i < nativeLibs.length; ++i) {
                        var candidate = nativeLibs[i];
                        if (product.Qt.android_support._multiAbi) {
                           if (candidate.product.name === product.name) {
                               architectures.push(candidate.Android.ndk.abi);
                               triples.push(candidate.cpp.toolchainTriple);
                               hostArch = candidate.Android.ndk.hostArch;
                               targetArchitecture = candidate.Android.ndk.abi;
                               theBinary = candidate;
                           }
                        } else {
                            if (!candidate.fileName.contains(candidate.product.targetName))
                                continue;
                            if (!theBinary) {
                                theBinary = candidate;
                                hostArch = theBinary.Android.ndk.hostArch;
                                targetArchitecture = theBinary.Android.ndk.abi;
                                continue;
                            }
                            if (theBinary.product.name === product.name
                                    && candidate.product.name !== product.name) {
                                continue; // We already have a better match.
                            }
                            if (candidate.product.name === product.name
                                    && theBinary.product.name !== product.name) {
                                theBinary = candidate; // The new candidate is a better match.
                                hostArch = theBinary.Android.ndk.hostArch;
                                targetArchitecture = theBinary.Android.ndk.abi;
                                continue;
                            }

                            throw "Qt applications for Android support only one native binary "
                                    + "per package.\n"
                                    + "In particular, you cannot build a Qt app for more than "
                                    + "one architecture at the same time.";
                        }
                    }
                }
                var f = new TextFile(output.filePath, TextFile.WriteOnly);
                f.writeLine("{");
                f.writeLine('"description": "This file was generated by qbs to be read by '
                            + 'androiddeployqt and should not be modified by hand.",');
                f.writeLine('"qt": "' + product.Qt.android_support._qtInstallDir + '",');
                f.writeLine('"sdk": "' + product.Android.sdk.sdkDir + '",');
                f.writeLine('"sdkBuildToolsRevision": "' + product.Android.sdk.buildToolsVersion
                            + '",');
                f.writeLine('"ndk": "' + product.Android.sdk.ndkDir + '",');
                f.writeLine('"toolchain-prefix": "llvm",');
                f.writeLine('"tool-prefix": "llvm",');
                f.writeLine('"useLLVM": true,');
                f.writeLine('"ndk-host": "' + hostArch + '",');
                if (!product.Qt.android_support._multiAbi) {
                    f.writeLine('"target-architecture": "' + targetArchitecture + '",');
                }
                else {
                    var line = '"architectures": {';
                    for (var i in architectures) {
                        line = line + '"' + architectures[i] + '":"' + triples[i] + '"';
                        if (i < architectures.length-1)
                            line = line + ',';
                    }
                    line = line + "},";
                    f.writeLine(line);
                }
                f.writeLine('"qml-root-path": "' + product.Qt.android_support.qmlRootDir + '",');
                var deploymentDeps = product.Qt.android_support.deploymentDependencies;
                if (deploymentDeps && deploymentDeps.length > 0)
                    f.writeLine('"deployment-dependencies": "' + deploymentDeps.join() + '",');
                var extraPlugins = product.Qt.android_support.extraPlugins;
                if (extraPlugins && extraPlugins.length > 0)
                    f.writeLine('"android-extra-plugins": "' + extraPlugins.join() + '",');
                var extraLibs = product.Qt.android_support.extraLibs;
                if (extraLibs && extraLibs.length > 0) {
                    for (var i = 0; i < extraLibs.length; ++i) {
                        if (!FileInfo.isAbsolutePath(extraLibs[i])) {
                            extraLibs[i] = FileInfo.joinPaths(product.sourceDirectory, extraLibs[i]);
                        }
                    }
                    f.writeLine('"android-extra-libs": "' + extraLibs.join() + '",');
                }
                var prefixDirs = product.Qt.android_support.extraPrefixDirs;
                if (prefixDirs && prefixDirs.length > 0)
                    f.writeLine('"extraPrefixDirs": ' + JSON.stringify(prefixDirs) + ',');
                if (Array.isArray(product.qmlImportPaths) && product.qmlImportPaths.length > 0)
                    f.writeLine('"qml-import-paths": "' + product.qmlImportPaths.join(',') + '",');

                // QBS-1429
                if (!product.Qt.android_support._multiAbi) {
                    f.writeLine('"stdcpp-path": "' + (product.cpp.sharedStlFilePath
                            ? product.cpp.sharedStlFilePath : product.cpp.staticStlFilePath)
                            + '",');
                    f.writeLine('"application-binary": "' + theBinary.filePath + '"');
                } else {
                    f.writeLine('"stdcpp-path": "' + product.Android.sdk.ndkDir +
                                '/toolchains/llvm/prebuilt/' + hostArch + '/sysroot/usr/lib/",');
                    f.writeLine('"application-binary": "' + theBinary.product.name + '"');
                }
                f.writeLine("}");
                f.close();
            };
            return cmd;
        }
    }

    // We use the manifest template from the Qt installation if and only if the project
    // does not provide a manifest file.
    Rule {
        condition: _enableSdkSupport
        multiplex: true
        requiresInputs: false
        inputs: "android.manifest"
        excludedInputs: "qt.android_manifest"
        outputFileTags: ["android.manifest", "qt.android_manifest"]
        outputArtifacts: {
            if (inputs["android.manifest"])
                return [];
            return [{
                filePath: "qt_manifest/AndroidManifest.xml",
                fileTags: ["android.manifest", "qt.android_manifest"]
            }];
        }
        prepare: {
            var cmd = new JavaScriptCommand();
            cmd.description = "copying Qt Android manifest template";
            cmd.sourceCode = function() {
                File.copy(FileInfo.joinPaths(product.Qt.android_support._templatesBaseDir,
                          "templates", "AndroidManifest.xml"), output.filePath);
            };
            return cmd;
        }
    }

    Rule {
        condition: _enableSdkSupport
        multiplex: true
        property stringList defaultInputs: ["qt_androiddeployqt_input",
                                            "android.manifest_processed"]
        property stringList allInputs: ["qt_androiddeployqt_input", "android.manifest_processed",
                                        "android.nativelibrary"]
        inputsFromDependencies: "android.nativelibrary"
        inputs: product.aggregate ? defaultInputs : allInputs
        outputFileTags: [
            "android.manifest_final", "android.resources", "android.assets", "bundled_jar",
            "android.deployqt_list",
        ]
        outputArtifacts: {
            var artifacts = [
                {
                    filePath: "AndroidManifest.xml",
                    fileTags: "android.manifest_final"
                },
                {
                    filePath: product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml",
                    fileTags: "android.resources"
                },
                {
                    filePath: product.Qt.android_support._deployQtOutDir
                              + "/res/values/strings.xml",
                    fileTags: "android.resources"
                },
                {
                    filePath: product.Qt.android_support._deployQtOutDir + "/assets/.dummy",
                    fileTags: "android.assets"
                },
                {
                    filePath: "deployqt.list",
                    fileTags: "android.deployqt_list"
                },

            ];
            if (!product.Qt.android_support.useMinistro) {
                artifacts.push({
                    filePath: FileInfo.joinPaths(product.java.classFilesDir, "QtAndroid.jar"),
                    fileTags: ["bundled_jar"]
                });
            }
            return artifacts;
        }
        prepare: {
            var copyCmd = new JavaScriptCommand();
            copyCmd.description = "copying Qt resource templates";
            copyCmd.sourceCode = function() {
                File.copy(inputs["android.manifest_processed"][0].filePath,
                          product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml");
                File.copy(product.Qt.android_support._templatesBaseDir + "/java/res",
                          product.Qt.android_support._deployQtOutDir + "/res");
                File.copy(product.Qt.android_support._templatesBaseDir
                          + "/templates/res/values/libs.xml",
                          product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml");
                if (!product.Qt.android_support._multiAbi) {
                    try {
                        File.remove(FileInfo.path(outputs["android.assets"][0].filePath));
                    } catch (e) {
                    }
                }
                else {
                    for (var i in inputs["android.nativelibrary"]) {
                        var input = inputs["android.nativelibrary"][i];
                        File.copy(input.filePath,
                                  FileInfo.joinPaths(product.Qt.android_support._deployQtOutDir,
                                                     "libs",
                                                     input.Android.ndk.abi,
                                                     input.fileName));
                    }
                }
            };
            var androidDeployQtArgs = [
                "--output", product.Qt.android_support._deployQtOutDir,
                "--input", inputs["qt_androiddeployqt_input"][0].filePath, "--aux-mode",
                "--deployment", product.Qt.android_support.useMinistro ? "ministro" : "bundled",
                "--android-platform", product.Android.sdk.platform,
            ];
            if (product.Qt.android_support.verboseAndroidDeployQt)
                androidDeployQtArgs.push("--verbose");
            var androidDeployQtCmd = new Command(
                        product.Qt.android_support._androidDeployQtFilePath, androidDeployQtArgs);
            androidDeployQtCmd.description = "running androiddeployqt";

            // We do not want androiddeployqt to write directly into our APK base dir, so
            // we ran it on an isolated directory and now we move stuff over.
            // We remember the files for which we do that, so if the next invocation
            // of androiddeployqt creates fewer files, the other ones are removed from
            // the APK base dir.
            var moveCmd = new JavaScriptCommand();
            moveCmd.description = "processing androiddeployqt outout";
            moveCmd.sourceCode = function() {
                File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml",
                          outputs["android.manifest_final"][0].filePath);
                var libsDir = product.Qt.android_support._deployQtOutDir + "/libs";
                var libDir = product.Android.sdk.apkContentsDir + "/lib";
                var listFilePath = outputs["android.deployqt_list"][0].filePath;
                var oldLibs = [];
                try {
                    var listFile = new TextFile(listFilePath, TextFile.ReadOnly);
                    var listFileLine = listFile.readLine();
                    while (listFileLine) {
                        oldLibs.push(listFileLine);
                        listFileLine = listFile.readLine();
                    }
                    listFile.close();
                } catch (e) {
                }
                listFile = new TextFile(listFilePath, TextFile.WriteOnly);
                var newLibs = [];
                var moveLibFiles = function(prefix) {
                    var fullSrcPrefix = FileInfo.joinPaths(libsDir, prefix);
                    var files = File.directoryEntries(fullSrcPrefix, File.Files);
                    for (var i = 0; i < files.length; ++i) {
                        var file = files[i];
                        var srcFilePath = FileInfo.joinPaths(fullSrcPrefix, file);
                        var targetFilePath;
                        if (file.endsWith(".jar"))
                            targetFilePath = FileInfo.joinPaths(product.java.classFilesDir, file);
                        else
                            targetFilePath = FileInfo.joinPaths(libDir, prefix, file);
                        File.move(srcFilePath, targetFilePath);
                        listFile.writeLine(targetFilePath);
                        newLibs.push(targetFilePath);
                    }
                    var dirs = File.directoryEntries(fullSrcPrefix,
                                                     File.Dirs | File.NoDotAndDotDot);
                    for (i = 0; i < dirs.length; ++i)
                        moveLibFiles(FileInfo.joinPaths(prefix, dirs[i]));
                };
                moveLibFiles("");
                listFile.close();
                for (i = 0; i < oldLibs.length; ++i) {
                    if (!newLibs.contains(oldLibs[i]))
                        File.remove(oldLibs[i]);
                }
            };

            // androiddeployqt doesn't strip the deployed libraries anymore so it has to done here
            var stripLibsCmd = new JavaScriptCommand();
            stripLibsCmd.description = "Stripping unneeded symbols from deployed qt libraries";
            stripLibsCmd.sourceCode = function() {
                var stripArgs = ["--strip-all"];
                var architectures = [];
                for (var i in inputs["android.nativelibrary"])
                    architectures.push(inputs["android.nativelibrary"][i].Android.ndk.abi);
                for (var i in architectures) {
                    var abiDirPath = FileInfo.joinPaths(product.Android.sdk.apkContentsDir,
                                                        "lib", architectures[i]);
                    var files = File.directoryEntries(abiDirPath, File.Files);
                    for (var i = 0; i < files.length; ++i) {
                        var filePath = FileInfo.joinPaths(abiDirPath, files[i]);
                        if (FileInfo.suffix(filePath) == "so") {
                            stripArgs.push(filePath);
                        }
                    }
                }
                var process = new Process();
                process.exec(product.cpp.stripPath, stripArgs, false);
            }

            return [copyCmd, androidDeployQtCmd, moveCmd, stripLibsCmd];
        }
    }

    Group {
        condition: Qt.android_support._enableSdkSupport
        name: "helper sources from qt"
        prefix: Qt.android_support._templatesBaseDir + "/java/"
        Android.sdk.aidlSearchPaths: prefix + "src"
        files: [
            "**/*.java",
            "**/*.aidl",
        ]
    }

    validate: {
        if (Utilities.versionCompare(version, "5.12") < 0)
            throw ModUtils.ModuleError("Cannot use Qt " + version + " with Android. "
                + "Version 5.12 or later is required.");
    }
}
