import de.undercouch.gradle.tasks.download.Download
import de.undercouch.gradle.tasks.download.Verify
import groovy.io.FileType
import groovy.json.JsonSlurper
import groovy.xml.XmlUtil
import org.apache.tools.ant.taskdefs.condition.Os

import java.util.regex.Matcher
import java.util.regex.Pattern

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
    }
}

plugins {
    id 'de.undercouch.download' version '3.3.0'
    id "cz.malohlava" version "1.0.3"
    id "io.github.http-builder-ng.http-plugin" version "0.1.1"
}

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

ext {
    RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
    VERSION_CODE = project.hasProperty('VERSION_CODE') ? project.getProperty('VERSION_CODE') : '0'
    RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
    STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0'
    EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
    appVersionCode = Integer.valueOf(VERSION_CODE ?: 1)
    appVersionName = RELEASE_NUMBER ?: "1.0"
}

def appDir = new File(projectDir, 'apps/interface')
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
def baseUrl = 'https://build-deps.overte.org/dependencies/android/'
def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms")

task extractGvrBinaries() {
    doLast {
        def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries');
        zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-audio-1.101.0.aar')).visit { element ->
            def fileName = element.file.toString();
            if (fileName.endsWith('libgvr_audio.so') && fileName.contains('arm64-v8a')) {
                copy {  from element.file; into gvrLibFolder }
            }
        }
        zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-base-1.101.0.aar')).visit { element ->
            def fileName = element.file.toString();
            if (fileName.endsWith('libgvr.so') && fileName.contains('arm64-v8a')) {
                copy {  from element.file; into gvrLibFolder }
            }
        }
        fileTree(gvrLibFolder).visit { element ->
            if (element.file.toString().endsWith('.so')) {
                copy { from element.file; into jniFolder }
            }
        }
    }
}

def generateAssetsFileList = {
    def assetsPath = "${appDir}/src/main/assets/"
    def addedByAndroidDeployQtName = "--Added-by-androiddeployqt--/"

    def addedByAndroidDeployQtPath = assetsPath + addedByAndroidDeployQtName

    def addedByAndroidDeployQt = new File(addedByAndroidDeployQtPath)
    if (!addedByAndroidDeployQt.exists() && !addedByAndroidDeployQt.mkdirs()) {
        throw new GradleScriptException("Failed to create directory " + addedByAndroidDeployQtPath, null);
    }
    def outputFilename = "/qt_cache_pregenerated_file_list"
    def outputFile = new File(addedByAndroidDeployQtPath + outputFilename);
    Map<String, List<String>> directoryContents = new TreeMap<>();

    def dir = new File(assetsPath)
    dir.eachFileRecurse (FileType.ANY) { file ->

        def name = file.path.substring(assetsPath.length())
        int slashIndex = name.lastIndexOf('/')
        def pathName = slashIndex >= 0 ? name.substring(0, slashIndex) : "/"
        def fileName = slashIndex >= 0 ? name.substring(pathName.length() + 1) : name
        if (!fileName.isEmpty() && file.isDirectory() && !fileName.endsWith("/")) {
            fileName += "/"
        }

        if (!directoryContents.containsKey(pathName)) {
            directoryContents[pathName] = new ArrayList<String>()
        }
        if (!fileName.isEmpty()) {
            directoryContents[pathName].add(fileName);
        }
    }
    DataOutputStream fos = new DataOutputStream(new FileOutputStream(outputFile));
    for (Map.Entry<String, List<String>> e: directoryContents.entrySet()) {
        def entryList = e.getValue()
        fos.writeInt(e.key.length()*2); // 2 bytes per char
        fos.writeChars(e.key);
        fos.writeInt(entryList.size());
        for (String entry: entryList) {
            fos.writeInt(entry.length()*2);
            fos.writeChars(entry);
        }
    }
    fos.close();
}


// Copy required Qt main libraries and required plugins based on the predefined list here
// FIXME eventually we would like to use the readelf functionality to automatically detect dependencies
// from our built applications and use that during the full build process.  However doing so would mean
// hooking existing Android build tasks since the output from the qtBundle logic adds JNI libs, asset
// files and resources files and potentially modifies the AndroidManifest.xml
task qtBundle {
    doLast {
        parseQtDependencies(QT5_DEPS)
        def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/")
        //def qmlImportFolder = new File("${projectDir}/app/src/main/cpp")
        scanQmlImports(qmlImportFolder)
        generateLibsXml()
        generateAssetsFileList()
   }
}

task setupDependencies() {
    // migrated to python
}

task cleanDependencies(type: Delete) {
}

def runBreakpadDumpSyms = { buildType ->
    gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS

    def objDir = new File("${appDir}/build/intermediates/cmake/${buildType}/obj/arm64-v8a")
    def stripDebugSymbol = "${appDir}/build/intermediates/transforms/stripDebugSymbol/${buildType}/0/lib/arm64-v8a/"
    def outputDir = new File(breakpadDumpSymsDir, buildType)
    if (!outputDir.exists()) {
        outputDir.mkdirs()
    }

    objDir.eachFileRecurse (FileType.FILES) { file ->
        if (file.name.endsWith('.so')) {
            def output = file.name + ".sym"
            def cmdArgs = [
                    file.toString(),
                    stripDebugSymbol
            ]
            def result = exec {
                workingDir HIFI_ANDROID_PRECOMPILED + '/breakpad/bin'
                commandLine './dump_syms'
                args cmdArgs
                ignoreExitValue true
                standardOutput = new BufferedOutputStream(new FileOutputStream(new File(outputDir, output)))
            }
        }
    }
}

task runBreakpadDumpSymsDebug() {
    doLast {
        runBreakpadDumpSyms("debug");
    }
}

task runBreakpadDumpSymsRelease() {
    doLast {
        runBreakpadDumpSyms("release");
    }
}

task zipDumpSymsDebug(type: Zip, dependsOn: runBreakpadDumpSymsDebug) {
    from (new File(breakpadDumpSymsDir, "debug").absolutePath)
    archiveName "symbols-${RELEASE_NUMBER}-debug.zip"
    destinationDir(new File("${appDir}/build/tmp/"))
}

task zipDumpSymsRelease(type: Zip, dependsOn: runBreakpadDumpSymsRelease) {
    from (new File(breakpadDumpSymsDir, "release").absolutePath)
    archiveName "symbols-${RELEASE_NUMBER}-release.zip"
    destinationDir(new File("${appDir}/build/tmp/"))
}

task uploadBreakpadDumpSymsDebug(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsDebug) {
    onlyIf {
        System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")
    }
    config {
        request.uri = System.getenv("CMAKE_BACKTRACE_URL")
    }
    post {
        request.uri.path = '/post'
        request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")]
        request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-debug.zip").bytes
        request.contentType = 'application/octet-stream'
        response.success {
            println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-debug.zip uploaded")
        }
    }
}

task uploadBreakpadDumpSymsRelease(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsRelease) {
    onlyIf {
        System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")
    }
    config {
        request.uri = System.getenv("CMAKE_BACKTRACE_URL")
    }
    post {
        request.uri.path = '/post'
        request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")]
        request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-release.zip").bytes
        request.contentType = 'application/octet-stream'
        response.success {
            println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-release.zip uploaded")
        }
    }
}

// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
// See the comment on the qtBundle task above
/*
// FIXME derive the path from the gradle environment
def toolchain = [
    version: '4.9',
    prefix: 'aarch64-linux-android',
    // FIXME derive from the host OS
    ndkHost: 'windows-x86_64',
]

def findDependentLibrary = { String name ->
    def libFolders = [
        new File(qmlRoot, 'lib'),
        new File("${HIFI_ANDROID_PRECOMPILED}/tbb/lib/release"),
        new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/Release"),
        new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/"),
        new File("${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/libraries"),
    ]

}

def readElfBinary = new File(android.ndkDirectory, "/toolchains/${toolchain.prefix}-${toolchain.version}/prebuilt/${toolchain.ndkHost}/bin/${toolchain.prefix}-readelf${EXEC_SUFFIX}")

def getDependencies = { File elfBinary ->
    Set<File> result = []
    Queue<File> pending = new LinkedList<>()
    pending.add(elfBinary)
    Set<File> scanned = []

    Pattern p = ~/.*\(NEEDED\).*Shared library: \[(.*\.so)\]/
    while (!pending.isEmpty()) {
        File current = pending.remove()
        if (scanned.contains(current)) {
            continue
        }
        scanned.add(current)
        def command = "${readElfBinary} -d -W ${current.absolutePath}"
        captureOutput(command).split('[\r\n]').each { line ->
            Matcher m = p.matcher(line)
            if (!m.matches()) {
                return
            }
            def libName = m.group(1)
            def file = new File(qmlRoot, "lib/${libName}")
            if (file.exists()) {
                result.add(file)
                pending.add(file)
            }
        }
    }
    return result
}

task testElf (dependsOn: 'externalNativeBuildDebug') {
    doLast {
        def appLibraries = new HashSet<File>()
        def qtDependencies = new HashSet<File>()
        externalNativeBuildDebug.nativeBuildConfigurationsJsons.each { File file ->
            def json = new JsonSlurper().parse(file)
            json.libraries.each { node ->
                def outputFile = new File(node.value.output)
                if (outputFile.canonicalPath.startsWith(projectDir.canonicalPath)) {
                    appLibraries.add(outputFile)
                }
            }
        }

        appLibraries.each { File file ->
            println getDependencies(file)
        }
    }
}
*/