import org.gradle.api.internal.java.DefaultJavaPlatformExtension
import org.gradle.api.plugins.internal.DefaultJavaPluginExtension
import org.gradle.api.publish.maven.internal.publisher.MavenRemotePublisher
import org.gradle.language.nativeplatform.internal.Dimensions

buildscript {
/****************************************************************************
 * Establish Visual Studio configuration environment for Windows native builds
 * NOTE: vsconfig.gradle path is relative to each GPL project module
 ****************************************************************************/
    apply from: "../../vsconfig.gradle"
    apply from: "../../chooseBackend.gradle"

    ext {

        host_cores = Runtime.getRuntime().availableProcessors()

        buildHelper = "-mingw"
        javacppPlatform = osdetector.classifier

        println "Building on ${host_cores} CPU cores."
        println "JavaCPP target plattform is ${javacppPlatform}"


        if (project.hasProperty("CAVIS_AVX_EXTENSION")) {
            avxExtension = project.getProperty("CAVIS_AVX_EXTENSION").toLowerCase()
            println "Bulding with ${avxExtension}"
        } else {
            avxExtension = "avx2"
            logger.quiet("No AVX CPU extension selected (avx2|avx512). Building with default 'avx2'")
        }

        javacppPlatformExtension = "-${avxExtension}".toString()

        getBuildPlatform = { String chip, Task tsk ->
            def pf =""
            if(chip.equals("cuda")) {
                pf = osdetector.classifier
            } else {
                if(osdetector.os.equals("windows")) {
                    pf = "${osdetector.classifier}-mingw"
                } else {
                    pf = "${osdetector.classifier}"
                }
            }
            logger.info("Setting properties for task '{}' to '{}'", tsk.getName(), pf)
            return pf
        }
    } // End of ext block


    dependencies {
        classpath platform(project(":cavis-common-platform"))
        classpath group: "org.bytedeco", name: "openblas"
        classpath group: "org.bytedeco", name: "openblas", classifier: "${javacppPlatform}"
        classpath group: "org.bytedeco", name:"mkl"
        classpath group: "org.bytedeco", name:"mkl", classifier: "${javacppPlatform}"
        classpath group: "org.bytedeco", name: "javacpp"
        classpath group: "org.bytedeco", name: "javacpp", classifier: "${javacppPlatform}"
    }


}


plugins {
    id 'java-library'
    id 'org.bytedeco.gradle-javacpp-build' version "1.5.9" //version "1.5.10-SNAPSHOT"
    id 'maven-publish'
    id 'signing'
}

chipList.each {String thisChip ->
    sourceSets.register(thisChip) {
        java {
            srcDirs = ["${projectDir}/src/main/java/"]
            include "org/nd4j/nativeblas/${thisChip}/Nd4j${thisChip.capitalize()}Helper.java"
            include "org/nd4j/nativeblas/${thisChip}/Nd4j${thisChip.capitalize()}Presets.java"
        }
        java.destinationDirectory.set(file("${projectDir}/build/classes/java/main/"))
    }
    sourceSets.register("${thisChip}Generated") {
        java {
            srcDirs = ["${buildDir}/generated/sources/javacpp/${thisChip}/${javacppPlatform}${javacppPlatformExtension}/"]
            include "org/nd4j/nativeblas/Nd4j${thisChip.capitalize()}.java"
        }
        java.destinationDirectory.set(file("${projectDir}/build/classes/java/main/"))
    }
}

/*
sourceSets {
    main {
        java {
            srcDirs = new HashSet<>();
            include 'org/nd4j/nativeblas/Dummy.java'
        }
    }
}
*/


// This block registers the cpu and cuda features and creates
// i. e. the {chip}Implementation
java {
    chipList.each {thisChip ->
        registerFeature("${thisChip}Support") {
            usingSourceSet(sourceSets.findByName("${thisChip}"))
            capability(project.group, "cavis-native-lib-${thisChip}-support", project.version)
            //withJavadocJar()
            //withSourcesJar()
        }}}

dependencies {
    if(withCuda()) {
        cudaImplementation platform(project(':cavis-common-platform'))

        //cudaImplementation project(":cavis-dnn:cavis-dnn-api")
        //cudaImplementation project(":cavis-dnn:cavis-dnn-common")
        cudaImplementation project(":cavis-native:cavis-native-blas")
        //cudaImplementation project(":cavis-native:cavis-native-common")
        //cudaImplementation "commons-io:commons-io"
        //cudaImplementation "org.bytedeco:openblas"
        //cudaImplementation "org.bytedeco:openblas::${javacppPlatform}"
        //cudaImplementation "org.bytedeco:cuda"
        //cudaImplementation "org.bytedeco:cuda::${javacppPlatform}"
        //cudaImplementation "org.apache.logging.log4j:log4j-core:2.17.0"
        //cudaImplementation "com.google.guava:guava:14.0.1"
        //cudaImplementation "org.apache.commons:commons-lang3"
        //cudaImplementation "org.apache.commons:commons-math3"
        //cudaImplementation "com.google.flatbuffers:flatbuffers-java"
        //cudaImplementation 'javax.mail:javax.mail-api:1.6.2'
        cudaImplementation "org.bytedeco:javacpp"
        cudaImplementation "org.bytedeco:javacpp::${javacppPlatform}"
        cudaImplementation project(":cavis-native:cavis-native-cuda-presets")

        cudaGeneratedImplementation platform(project(':cavis-common-platform'))
        cudaGeneratedImplementation project(":cavis-native:cavis-native-blas")
        cudaGeneratedImplementation "org.bytedeco:javacpp"
        cudaGeneratedImplementation "org.bytedeco:javacpp::${javacppPlatform}"
        cudaGeneratedImplementation project(":cavis-native:cavis-native-cuda-presets")
    }

    if(withCpu()) {
        cpuImplementation platform(project(':cavis-common-platform'))
        //cpuImplementation project(":cavis-dnn:cavis-dnn-api")
        //cpuImplementation project(":cavis-dnn:cavis-dnn-common")
        //cpuImplementation project(":cavis-native:cavis-native-blas")
        //cpuImplementation project(":cavis-native:cavis-native-common")
        //cpuImplementation "commons-io:commons-io"
        //cpuImplementation "org.bytedeco:opencv"
        //cpuImplementation "org.bytedeco:opencv::${javacppPlatform}"
        //cpuImplementation "org.apache.logging.log4j:log4j-core:2.17.0"
        //cpuImplementation "com.google.guava:guava:14.0.1"
        //cpuImplementation "org.apache.commons:commons-lang3"
        //cpuImplementation "org.apache.commons:commons-math3"
        //cpuImplementation "com.google.flatbuffers:flatbuffers-java"
        //cpuImplementation 'javax.mail:javax.mail-api:1.6.2'
        //cpuImplementation "org.bytedeco:javacpp"
        //cpuImplementation "org.bytedeco:javacpp::${javacppPlatform}"
        cpuImplementation project(":cavis-native:cavis-native-cpu-presets")
    }
}


clean {
    doFirst {
        delete "${projectDir}/build"
        delete "${projectDir}/src/main/include/config.h"
        chipList.each {
            delete "${projectDir}/blasbuild/${it}"
        }
    }
}

task deepClean(type: Delete) {
    dependsOn clean
    doFirst {
        delete "$projectDir}/blasbuild"
    }
}


tasks.withType(org.bytedeco.gradle.javacpp.BuildTask).configureEach { org.bytedeco.gradle.javacpp.BuildTask it ->
    it.buildResource = ["/org/bytedeco/openblas/${javacppPlatform}/",
                        "/org/bytedeco/mkldnn/${javacppPlatform}/"]

    it.includeResource = ["/org/bytedeco/openblas/${javacppPlatform}/include/"]

    it.linkResource = ["/org/bytedeco/openblas/${javacppPlatform}/",
                       "/org/bytedeco/openblas/${javacppPlatform}/lib/"]
}


// Disable the standard javacpp generated tasks and use own
// versions below. This allows to build for each variant

[javacppBuildParser, javacppBuildCommand, javacppCompileJava, javacppBuildCompiler].each {
    it.enabled false
}

chipList.each { String thisChip ->

    // 1)
    //Run the C++ compile first
    tasks.register("javacpp${thisChip.capitalize()}SupportBuildCommand", org.bytedeco.gradle.javacpp.BuildTask) {org.bytedeco.gradle.javacpp.BuildTask it ->
        properties = getBuildPlatform( thisChip, it )

        includePath = ["${projectDir}/src/main/cpp/blas/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/src/main/include/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/flatbuffers-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/cpu_features-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/mkldnn-src/include"]
        linkPath = ["${projectDir}/blasbuild/${thisChip}/${avxExtension}/output"]
        //No idea why this is here, but it looks like even for the javacppBuildCommand task,
        //there is a javacpp Loader actively determining platform etc.
        classOrPackageNames = ["org.nd4j.nativeblas.${thisChip}.Nd4j${thisChip.capitalize()}Presets"]
        //workingDirectory = projectDir
        //if the classpath is not set here, the javacpp classloader starts to look around
        //everywhere and causes java.io.IOExceptions:  because files is being used by another process
        //logger.quiet("Using compile classpath from configuration named '{}'", sourceSets.named(thisChip).get().getCompileClasspathConfigurationName())

        classPath = sourceSets.named(thisChip).get().compileClasspath.collect()

        /* Get VCVARS in case we want to build CUDA
        * MinGW64 g++ on MSYS is used otherwise */
        if (thisChip.equals('cuda') && osdetector.os.startsWith("win")
        && project.hasProperty("skip-native")
                && !project.getProperty("skip-native").equals("true")
                && !VISUAL_STUDIO_INSTALL_DIR.isEmpty()) {
            def proc = ["cmd.exe", "/c", "${VISUAL_STUDIO_VCVARS_CMD} > nul && set"].execute()
            it.environmentVariables = it.environmentVariables ?: [:]
            def lines = proc.text.split("\\r?\\n")
            for (def line in lines) {
                if (line.contains("=")) {
                    def parts = line.split("=")
                    it.environmentVariables.put(parts[0], parts[1])
                    logger.quiet("Added variable to environment: {} = {}", parts[0], parts[1])
                }
            }
        }
        workingDirectory projectDir
        if (thisChip.equals('cuda') && osdetector.os.startsWith("windows")) { //cuDNN requires CUDA
            it.buildCommand = ['sh', 'buildnativeoperations.sh',
                            '-V',
                            '--build-type', 'release',
                            '--chip', thisChip,
                            '--plattform', 'x86_64',
                            '--chip-extension', avxExtension,
                            '-j', "${host_cores}",
                            // '--helper', 'mkldnn',
                            '--helper', 'cudnn']
        } else if (thisChip.equals('cuda') && osdetector.os.startsWith("linux")) { //cuDNN requires CUDA
            it.buildCommand = ['bash', 'buildnativeoperations.sh',
                            '-V',
                            '--build-type', 'release',
                            '--chip', thisChip,
                            '--plattform', 'x86_64',
                            '--chip-extension', avxExtension,
                            '-j', "${host_cores}",
                            // '--helper', 'mkldnn',
                            '--helper', 'cudnn']
        } else {
            it.buildCommand = ['bash', 'buildnativeoperations.sh',
                            '-V',
                            '--build-type', 'release',
                            '--chip', thisChip,
                            '--plattform', 'x86_64',
                            '--chip-extension', avxExtension,
                            '-j', "${host_cores}",
                            '--helper', 'mkldnn']
        }
    }

    //Run the parser on the InfoMap in Nd4j$ChipPresets and listed header files in @Platform
    //Generates Nd4jCpu.java and/ or Nd4jCuda.java Java JNI code
    tasks.register("javacpp${thisChip.capitalize()}SupportBuildParser", org.bytedeco.gradle.javacpp.BuildTask) {


        includePath = ["${projectDir}/src/main/cpp/blas/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/src/main/include/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/flatbuffers-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/cpu_features-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/mkldnn-src/include"]

        classOrPackageNames = ["org.nd4j.nativeblas.${thisChip}.Nd4j${thisChip.capitalize()}Presets"]
        classPath = sourceSets.named(thisChip).get().compileClasspath.collect()
        outputDirectory file("${buildDir}/generated/sources/javacpp/${thisChip}/${javacppPlatform}${javacppPlatformExtension}/")
    }


    // Generates jnijavacpp.cpp and jniNativeLibrary.cpp, compiles and links it
    tasks.register("javacpp${thisChip.capitalize()}SupportBuildCompiler", org.bytedeco.gradle.javacpp.BuildTask) {org.bytedeco.gradle.javacpp.BuildTask it ->
        if (project.hasProperty("skip-native") && project.getProperty("skip-native").equals("true")) {
            enabled = false
        }

        linkPath = ["${projectDir}/blasbuild/${thisChip}/${avxExtension}/output"]
        includePath = ["${projectDir}/src/main/cpp/blas/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/src/main/include/",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/flatbuffers-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/cpu_features-src/include",
                       "${projectDir}/blasbuild/${thisChip}/${avxExtension}/mkldnn-src/include"]

        properties = getBuildPlatform( thisChip, it )

        if(thisChip.equals('cuda') && osdetector.os.startsWith("win") && project.hasProperty("skip-native")
                && !project.getProperty("skip-native").equals("true") && !VISUAL_STUDIO_INSTALL_DIR.isEmpty()) {
            def proc = ["cmd.exe", "/c", "${VISUAL_STUDIO_VCVARS_CMD} > nul && where.exe cl.exe"].execute()
            def outp = proc.text
            def cl = "\"" + outp.replace("\\", "\\\\").trim() + "\""
            def currentCompiler = ""
            doFirst{
                currentCompiler = System.getProperty("org.bytedeco.javacpp.platform.compiler")
                System.setProperty("org.bytedeco.javacpp.platform.compiler", cl)
                System.setProperty("platform.compiler.cpp11", cl)
                logger.quiet("Task ${name} overrides compiler '${currentCompiler}' with '${cl}'.")

            }
            doLast {
                //restore compiler
                System.setProperty("org.bytedeco.javacpp.platform.compiler", currentCompiler ?: "")
            }//System.setProperty("org.bytedeco.javacpp.platform.compiler", cl)
            //System.setProperty("org.bytedeco.javacpp.platform.compiler.cpp11", cl)

            proc = ["cmd.exe", "/c", "${VISUAL_STUDIO_VCVARS_CMD} > nul && set"].execute()
            environmentVariables = environmentVariables ?: [:]
            def lines = proc.text.split("\\r?\\n")
            for (def line in lines) {
                if (line.contains("=")) {
                    def parts = line.split("=")
                    environmentVariables.put(parts[0], parts[1])
                }
            }

        } else {
            //System.setProperty("org.bytedeco.javacpp.platform.compiler", "g++")
        }


        buildPath = ["${buildDir}/generated/sources/javacpp/${thisChip}/${javacppPlatform}${javacppPlatformExtension}/"]
        copyLibs = true
        deleteJniFiles(false)
        //outputName = "jnind4j${thisChip}"
        outputDirectory = file("${buildDir}/generated/sources/javacpp/${thisChip}/${javacppPlatform}${javacppPlatformExtension}/")
        classOrPackageNames= ["org.nd4j.nativeblas.Nd4j${thisChip.capitalize()}"]

        configDirectory = file("${buildDir}/classes/java/${thisChip}Support/META-INF/native-image/${javacppPlatform}")
        classPath = sourceSets.named("${thisChip}Generated").get().compileClasspath.collect()
        classPath += "${buildDir}/classes/java/main/"
    }

    // Create Jar with classifier
    tasks.named("${thisChip}Jar").configure { Jar thisTask ->
        dependsOn "javacpp${thisChip.capitalize()}SupportBuildCompiler"
        dependsOn "javacpp${thisChip.capitalize()}SupportBuildCommand"


        def spec = copySpec {

           from(tasks.named("javacpp${thisChip.capitalize()}SupportBuildCompiler").get()) {
               exclude { f ->
                   def exclude = f.file.isDirectory()
                   if(exclude) {
                       logger.info("${thisTask.name}: excluding '${f}'")
                   } else {
                       logger.info("${thisTask.name}: including '${f}'")
                   }
                   return exclude
               }
               into "${javacppPlatform}/" //path within jar, we need it in a platform, that javacpp Loader understands
           }
            from(sourceSets.named(thisChip).get().getOutput()) {
                into "${javacppPlatform}/" //path within jar, we need it in a platform, that javacpp Loader understands
            }
            from(sourceSets.named("${thisChip}Generated").get().getOutput()) {
                into "${javacppPlatform}/" //path within jar, we need it in a platform, that javacpp Loader understands
            }
            duplicatesStrategy DuplicatesStrategy.EXCLUDE
        }

        thisTask.with spec
        thisTask.archiveClassifier = "${javacppPlatform}${javacppPlatformExtension}-${thisChip}"
    }
}

//Before we can compile the whole java part, we
//need to generate the Nd4jXXX.java files first
tasks.named("compileJava").configure {enabled false}

chipList.each { String thisChip ->
    tasks.named("javacpp${thisChip.capitalize()}SupportBuildCompiler").configure {
        dependsOn "javacpp${thisChip.capitalize()}SupportBuildParser"
    }

    tasks.named("javacpp${thisChip.capitalize()}SupportBuildParser").configure {
        dependsOn "javacpp${thisChip.capitalize()}SupportBuildCommand"
    }

    tasks.named("javacpp${thisChip.capitalize()}SupportBuildCommand").configure {
        dependsOn   "process${thisChip.capitalize()}Resources",
                    "compile${thisChip.capitalize()}Java",
                    "compile${thisChip.capitalize()}GeneratedJava"
    }

    tasks.named("${thisChip}Jar").configure {
        dependsOn "javacpp${thisChip.capitalize()}SupportBuildCompiler"
    }
}

tasks.withType(JavaCompile).configureEach {
    // options.setCompilerArgs(Arrays.asList("-Xlint:unchecked"))
}

tasks.withType(Javadoc).configureEach {
    options.addStringOption('Xdoclint:none', '-quiet')
}

javadoc {
    dependsOn "javacppPomProperties"
    failOnError = false
    //options.links = ['http://bytedeco.org/javacpp/apidocs']
    options.addStringOption('Xdoclint:none', '-quiet')
    //options.JFlags = ["-Xdoclint:none"]
}


    tasks.getByName("generatePomFileForMavenJavaPublication") {
        enabled = true
    }
    tasks.getByName("publishMavenJavaPublicationToLocalRemoteRepository") {
        enabled = true
    }

artifacts {
    //implementation(jar)

    chipList.each { String thisChip ->
        implementation tasks.getByName("${thisChip}Jar")
    }


}

/*

if( osdetector.os.startsWith("windows")) {

    FileCollection collection = layout.files { file("build/libs/").listFiles() }

    //collection.collect { relativePath(it) }.sort().each { println it }

    publishing {
        publications {
            mavenJava(MavenPublication) {
                artifact jar
                collection.collect {File fi ->
                    if( fi.name.contains('linux-x86_64-avx2-cpu')) {
                        logger.quiet("Adding artifact ${fi.name} to publication.")
                        artifact source: fi, classifier: 'linux-x86_64-avx2-cpu', extension: 'jar'
                    }
                }

            }
        }
    }
}
*/

signing {
    useGpgCmd()
    if (!version.endsWith('SNAPSHOT')) {
        sign publishing.publications.mavenJava
        //sign publishing.publications.mavenJavacppPlatform
    }
}