// Adapted from https://raw.githubusercontent.com/facebook/react-native/d16ff3bd8b92fa84a9007bf5ebedd8153e4c089d/react.gradle

import java.nio.file.Paths;

def config = project.extensions.findByName("react") ?: [:]
def bundleAssetName = config.bundleAssetName ? config.bundleAssetName.get() : "index.android.bundle"

// because elvis operator
def elvisFile(thing) {
    return thing ? file(thing) : null;
}

void runBefore(String dependentTaskName, Task task) {
    Task dependentTask = tasks.findByPath(dependentTaskName);
    if (dependentTask != null) {
        dependentTask.dependsOn task
    }
}

/**
 * Finds the path of the installed npm package with the given name using Node's
 * module resolution algorithm, which searches "node_modules" directories up to
 * the file system root. This handles various cases, including:
 *
 *   - Working in the open-source RN repo:
 *       Gradle: /path/to/react-native/ReactAndroid
 *       Node module: /path/to/react-native/node_modules/[package]
 *
 *   - Installing RN as a dependency of an app and searching for hoisted
 *     dependencies:
 *       Gradle: /path/to/app/node_modules/react-native/ReactAndroid
 *       Node module: /path/to/app/node_modules/[package]
 *
 *   - Working in a larger repo (e.g., Facebook) that contains RN:
 *       Gradle: /path/to/repo/path/to/react-native/ReactAndroid
 *       Node module: /path/to/repo/node_modules/[package]
 *
 * The search begins at the given base directory (a File object). The returned
 * path is a string.
 */
static def findNodeModulePath(baseDir, packageName) {
    def basePath = baseDir.toPath().normalize()
    // Node's module resolution algorithm searches up to the root directory,
    // after which the base path will be null
    while (basePath) {
        def candidatePath = Paths.get(basePath.toString(), "node_modules", packageName)
        if (candidatePath.toFile().exists()) {
            return candidatePath.toString()
        }
        basePath = basePath.getParent()
    }
    return null
}

android.buildTypes.each { buildType ->
    // to prevent incorrect long value restoration from strings.xml we need to wrap it with double quotes
    // https://github.com/microsoft/cordova-plugin-code-push/issues/264
    buildType.resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
}

gradle.projectsEvaluated {
    def debuggableVariants = config.debuggableVariants ? config.debuggableVariants.get() : ['debug']

    android.applicationVariants.all { variant ->
        // No code push for debuggable variants
        if (debuggableVariants.contains(variant.name)) {
            return;
        }

        def nodeModulesPath;
        if (project.hasProperty('nodeModulesPath')) {
            nodeModulesPath = "${project.nodeModulesPath}/@appzung/react-native-code-push"
        } else {
            nodeModulesPath = findNodeModulePath(projectDir, "@appzung/react-native-code-push")
        }

        def targetName = variant.name.capitalize()
        def targetPath = variant.dirName

        def jsBundleDir;
        def resourcesDir;
        def jsBundleFile;

        // Additional node commandline arguments
        def nodeExecutableAndArgs = config.nodeExecutableAndArgs ? config.nodeExecutableAndArgs.get(): ["node"]
        def extraPackagerArgs = config.extraPackagerArgs ? config.extraPackagerArgs.get() : []

        // Make this task run right after the bundle task
        def generateBundledResourcesHash;

        def reactBundleTask = tasks.findByName("createBundle${targetName}JsAndAssets")
        if (reactBundleTask) {
            jsBundleDir = reactBundleTask.property('jsBundleDir').asFile.get()
            resourcesDir = reactBundleTask.property('resourcesDir').asFile.get()

            // mitigates Resource and asset merger: Duplicate resources error
            project.delete(files("${jsBundleDir}"))

            jsBundleDir.mkdirs()
            resourcesDir.mkdirs()

            jsBundleFile = file("$jsBundleDir/$bundleAssetName")

            generateBundledResourcesHash = tasks.create(
                    name: "generateBundledResourcesHash${targetName}",
                    type: Exec) {
                commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/generateBundledResourcesHash.js", resourcesDir, jsBundleFile, jsBundleDir)

                enabled !debuggableVariants.contains(variant.name) ?: targetName.toLowerCase().contains("release")
            }

            runBefore("merge${targetName}Resources", generateBundledResourcesHash)
            runBefore("merge${targetName}Assets", generateBundledResourcesHash)
        } else {
            def jsBundleDirConfigName = "jsBundleDir${targetName}"
            jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ? elvisFile(config."$jsBundleDirConfigName").get():
                    file("$buildDir/intermediates/assets/${targetPath}")

            def resourcesDirConfigName = "resourcesDir${targetName}"
            resourcesDir = elvisFile(config."${resourcesDirConfigName}") ? elvisFile(config."${resourcesDirConfigName}").get():
                    file("$buildDir/intermediates/res/merged/${targetPath}")

            // In case version of 'Android Plugin for Gradle'' is lower than 1.3.0
            // '$buildDir' has slightly different structure - 'merged' folder
            // does not exists so '${targetPath}' folder contains directly in 'res' folder.
            if (!resourcesDir.exists() && file("$buildDir/intermediates/res/${targetPath}").exists()) {
                resourcesDir = file("$buildDir/intermediates/res/${targetPath}")
            }

            jsBundleFile = file("$jsBundleDir/$bundleAssetName")

            def resourcesMapTempFileName = "CodePushResourcesMap-" + java.util.UUID.randomUUID().toString().substring(0,8) + ".json"

            generateBundledResourcesHash = tasks.create(
                    name: "generateBundledResourcesHash${targetName}",
                    type: Exec) {
                commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/generateBundledResourcesHash.js", resourcesDir, jsBundleFile, jsBundleDir, resourcesMapTempFileName)
            }

            // Make this task run right before the bundle task
            def recordFilesBeforeBundleCommand = tasks.create(
                    name: "recordFilesBeforeBundleCommand${targetName}",
                    type: Exec) {
                commandLine (*nodeExecutableAndArgs, "${nodeModulesPath}/scripts/recordFilesBeforeBundleCommand.js", resourcesDir, resourcesMapTempFileName)
            }

            recordFilesBeforeBundleCommand.dependsOn("merge${targetName}Resources")
            recordFilesBeforeBundleCommand.dependsOn("merge${targetName}Assets")
            runBefore("bundle${targetName}JsAndAssets", recordFilesBeforeBundleCommand)

            // We need to generate and record the resources map, but we use it to generate the bundle hash
            generateBundledResourcesHash.dependsOn("recordFilesBeforeBundleCommand${targetName}")
        }

        generateBundledResourcesHash.dependsOn("createBundle${targetName}JsAndAssets")

        runBefore("processArmeabi-v7a${targetName}Resources", generateBundledResourcesHash)
        runBefore("processX86${targetName}Resources", generateBundledResourcesHash)
        runBefore("processUniversal${targetName}Resources", generateBundledResourcesHash)
        runBefore("process${targetName}Resources", generateBundledResourcesHash)
    }
}
