//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                : a p p / b u i l d . g r a d l e                               //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
//
// This "app" sub-project handles Audiveris application.
//
//--------------------------------------------------------------------------------------------------

plugins {
    id 'application'

    // Needed to retrieve all dependencies for flatpak
    id 'io.github.jwharm.flatpak-gradle-generator' version '1.5.0'
}

//------------
// Properties
//------------

def app = project(':app')

// Central location for application version
project.version = '5.7.1'
println "version: ${project.version}"

// Central location for the specific versions of tesseract dependencies
ext.jcppVersion     = '1.5.12'
ext.leptVersion     = '1.85.0'
ext.tessVersion     = '5.5.1'

// Name of host OS
ext.hostOSName   = System.getProperty('os.name').toLowerCase()\
                     .startsWith('mac os x') ? 'macosx' :\
                     System.getProperty('os.name').split(' ')[0].toLowerCase()

// Version of host OS
ext.hostOSVersion = System.getProperty('os.version')
println "hostOSVersion: ${ext.hostOSVersion}"

// Architecture of host OS
// This mapping is required to adapt values of os.name and os.arch
// to the conventions used by Javacpp's dependencies
ext.hostOSArch   = ["i386":"x86", "i486":"x86", "i586":"x86", "i686":"x86", "x86":"x86",
                    "amd64":"x86_64", "x86-64":"x86_64", "x86_64":"x86_64", 
                    "arm":"armhf",
                    "aarch64":"arm64"]\
                   [System.getProperty('os.arch').toLowerCase()]

// Full host OS identification as "name-arch"
ext.hostOS       = "${ext.hostOSName}-${ext.hostOSArch}"
println "hostOS: ${ext.hostOS}"

// Architecture of target OS
if (project.hasProperty('targetOS')) {
    println "property targetOS: " + project.property('targetOS')
    ext.targetOS = project.property('targetOS')
} else {
    ext.targetOS = ext.hostOS
}
println "targetOS: ${ext.targetOS}"

// Handling of Java version
ext.theMinJavaVersion = project.property('theMinJavaVersion')
println "theMinJavaVersion: $theMinJavaVersion"

java {
    sourceCompatibility = JavaLanguageVersion.of(theMinJavaVersion)
}

// No longer needed, but kept for potential future use (like batch integration test)
ext.tessdataTag     = project.property('theTessdataTag')
println "theTessdataTag: $tessdataTag"

ext.programName     = rootProject.name
ext.programId       = rootProject.name.toLowerCase()
ext.programVersion  = "$project.version"
ext.companyName     = "$programName Ltd."
ext.companyId       = "${programName}Ltd"

// Name of Java main class. 
// The caller may have set the property on the gradle command line via a -PmainClass=foobar
if (project.hasProperty('mainClass')) {
    println "property mainClass: " + project.property('mainClass')
    ext.mainClass = project.property('mainClass')
} else {
    ext.mainClass = ext.programName
}

//--------------
// Dependencies
//--------------

repositories {
    if (project.hasProperty("isFlatpak") && project.getProperty("isFlatpak") == "true") {
        println "repositories. isFlatpak set"
        maven { // Only the offline repository
            name = 'Offline repository for flatpak'
            url = '../dependencies'                // TODO: check this!
        }
    } else {
        mavenCentral()

        maven { // Required to obtain non-free JAI
            name = 'JBoss repository'
            url = 'https://repository.jboss.org/maven2'
        }

        mavenLocal()

        ///flatDir(dirs: 'dev/externals') // for libraries not in any other repository
    }
}

dependencies {
    implementation(
        [group: 'args4j', name: 'args4j', version: '2.33'],
        [group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'],
        [group: 'com.github.jai-imageio', name: 'jai-imageio-core', version: '1.4.0'],
        [group: 'com.github.jai-imageio', name: 'jai-imageio-jpeg2000', version: '1.4.0'],
        [group: 'com.itextpdf', name: 'itextpdf', version: '5.5.13.2'],
        [group: 'com.jgoodies', name: 'jgoodies-forms', version: '1.9.0'],
        [group: 'com.jgoodies', name: 'jgoodies-looks', version: '2.7.0'],
        [group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0.1'],
        [group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.1'],
        [group: 'gov.nist.math', name: 'jama', version: '1.0.3'],
        [group: 'javax.media', name: 'jai-core', version: '1.1.3'],
        [group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'],
        [group: 'net.imagej', name: 'ij', version: '1.53j'],
        [group: 'net.jcip', name: 'jcip-annotations', version: '1.0'],
        [group: 'org.apache.directory.studio', name: 'org.apache.commons.io', version: '2.4'],
        [group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'],
        [group: 'org.apache.pdfbox', name: 'pdfbox', version: '3.0.4'],
        [group: 'org.apache.pdfbox', name: 'pdfbox-io', version: '3.0.4'],
        [group: 'org.audiveris', name: 'proxymusic', version: '4.0.3'],
        [group: 'org.bushe', name: 'eventbus', version: '1.4'],
        [group: 'org.bytedeco', name: 'javacpp', version: jcppVersion],
        [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}"],
        [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}"],
        [group: 'org.jdesktop.bsaf', name: 'bsaf', version: '1.9.2'],
        [group: 'org.jfree', name: 'jfreechart', version: '1.5.3'],
        [group: 'org.jgrapht', name: 'jgrapht-core', version: '1.5.1'],
        [group: 'org.kohsuke', name: 'github-api', version: '1.301'],
        [group: 'org.reflections', name: 'reflections', version: '0.10.2'],
        [group: 'org.slf4j', name: 'slf4j-api', version: '1.7.35']
    )

    runtimeOnly(
        [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "${app.ext.targetOS}"],
        [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "${app.ext.targetOS}"]
    )

    testImplementation(
        [group: 'junit', name: 'junit', version: '4.13.2'],
        [group: 'org.jgrapht', name: 'jgrapht-ext', version: '1.5.1']
    )
}

// Trick for Linux to include 'arm64' architecture in addition to 'x86_64'
if (targetOS == "linux-x86_64") {
    dependencies {
        runtimeOnly(
            [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "linux-arm64"],
            [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "linux-arm64"]
        )
    }
}

//------------
// SourceSets
//------------

sourceSets {
    main {
        java {
            srcDir 'src/main/java'
            srcDir 'build/generated-src'
        }
        resources {            
            srcDir 'src/main/java'  
            include '**/*.properties'   // For BSAF use of properties files at runtime
            include '**/icon-*.png'     // For Audiveris specific icons in resources
        }
        resources {            
            srcDir 'dev/icons'          // Crystal icons needed at runtime (in build/resources/main)
            include '**/*.png'
        }
    }

    test {
        java {
            srcDir 'src/test/java'
        }
    }
}

//-------
// Tasks
//-------

application {
    mainClass = "$app.ext.mainClass"
    
    // 1/ Needed to circumvent the limitations brought by Java 9 and above (the JigSaw cuts)

    // 2/ WARNING: A restricted method in java.lang.System has been called
    // WARNING: java.lang.System::load has been called by org.bytedeco.javacpp.Loader in an unnamed module
    // (file:/C:/Users/herve/.gradle/caches/modules-2/files-2.1/org.bytedeco/javacpp/1.5.12/d37e3e9c7f3e3a9e492c245e62fd358611889e04/javacpp-1.5.12.jar)
    // WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
    // WARNING: Restricted methods will be blocked in a future release unless native access is enabled
    applicationDefaultJvmArgs = ["--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED",
                                 "--enable-native-access=ALL-UNNAMED"]
}

// Perhaps no longer needed?
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

// Useful for turning on deprecation warnings
// Just uncomment the appropriate option
allprojects {
    tasks.withType(JavaCompile).tap {
        configureEach {
            options.compilerArgs << "-Xlint:deprecation"
            options.compilerArgs << "-Xlint:unchecked"
        }
    }
}

// Run default configuration.
// Heap size (and other stuff) can be modified via a 'jvmLineArgs' on gradle command line
tasks.run {
    minHeapSize = '512m'
    maxHeapSize = '1g'

    // Retrieve JVM arguments from jvmLineArgs property if any
    if (project.hasProperty("jvmLineArgs")) {
        println "run. jvmLineArgs: ${jvmLineArgs}"
        jvmArgs jvmLineArgs.split(',')
    }

    // Retrieve CLI arguments from cmdLineArgs property if any
    if (project.hasProperty("cmdLineArgs")) {
        println "run. cmdLineArgs: ${cmdLineArgs}"
        args cmdLineArgs.split(',')
    }
}

// Generate application.jar
jar {
    // override default output archive name
    archiveFileName = "${programId}.jar"

    exclude ("**/doc-files/**")

    destinationDirectory = file('build/jar')

    // copy resources into the destination jar
    from(file('res')) {
        into 'res'
    }

    manifest {
        attributes 'Built-By': app.ext.companyName
        attributes 'Specification-Title': app.ext.programName
        attributes 'Specification-Vendor': app.ext.companyName
        attributes 'Specification-Version': project.version
        attributes 'Implementation-Version': project.version
        attributes 'Main-Class': app.ext.mainClass
    }
}

// Retrieve the full hash for the latest commit from Git
tasks.register('getCommit', Exec) {
    description = "Retrieves full hash of git latest commit"
    outputs.file("build/commit.txt")
    commandLine = "git rev-parse HEAD".split(' ')
    standardOutput = new ByteArrayOutputStream()

    doLast {
        def commit = standardOutput.toString().replaceAll('\n', '')
        println "commit: ${commit}"
        app.ext.commit = commit

        def commitFile = file("build/commit.txt")
        commitFile.text = commit
    }
}

// Retrieve the short hash for the latest commit from Git
tasks.register('getCommitShort', Exec) {
    description = "Retrieves short hash of git latest commit"
    outputs.file("build/commitShort.txt")

    commandLine = "git rev-parse --short HEAD".split(' ')
    standardOutput = new ByteArrayOutputStream()

    doLast {
        def commitShort = standardOutput.toString().replaceAll('\n', '')
        println "commitShort: ${commitShort}"

        def commitShortFile = file("build/commitShort.txt")
        commitShortFile.text = commitShort
    }
}

// Generate a 'ProgramId.java' source file, based on Git data
tasks.register('generateProgramId') {
    group = "build"
    description = "Generates the ProgramId source"
    dependsOn 'getCommit'
    dependsOn 'getCommitShort'

    inputs.file("build/commit.txt")
    inputs.file("build/commitShort.txt")

    def outputDir = file("build/generated-src/org/audiveris/omr")
    def className = "ProgramId"
    def gSrc = new File(outputDir, "${className}.java")
    outputs.file(gSrc)

    doLast {
        outputDir.exists() || outputDir.mkdirs()
        gSrc.write("package org.audiveris.omr;\n\n")
        gSrc.append("/**\n * Class {@code $className} provides full program identification.\n")
        gSrc.append(" * This code has been automatically generated by Gradle.\n */\n")
        gSrc.append("public abstract class $className {")

        app.ext.programBuild = file('build/commitShort.txt').text.trim()
        ["company_name", "company_id", "program_name", "program_id", "program_version", "program_build", "tessdata_tag"].each { str ->
            def strParts = str.split("_")
            def propName = strParts[0] + strParts[1].capitalize()
            gSrc.append("\n    /** Precise ${strParts[0]} ${strParts[1]}: {@value} */")
            gSrc.append("\n    public static final String ${str.toUpperCase(Locale.ROOT)} = \"${app.ext."${propName}"}\";\n")
        }

        gSrc.append("}\n")
    }
}
compileJava.dependsOn('generateProgramId')

// Customization of start scripts to force a check of Java version at run time
tasks.withType(CreateStartScripts).configureEach {
    applicationName = rootProject.name
    doFirst {
        // Copy our own templates while updating THE_MIN_JAVA_VERSION token
        copy {
            into layout.buildDirectory
            filter { line -> line.replaceAll('THE_MIN_JAVA_VERSION', theMinJavaVersion) }
            from 'dev/scripts/custom-unixStartScript.txt'
            from 'dev/scripts/custom-windowsStartScript.txt'
        }
    }

    // Make the script generators use our templates
    unixStartScriptGenerator.template = resources.text.fromFile(layout.buildDirectory.file("custom-unixStartScript.txt"))
    windowsStartScriptGenerator.template = resources.text.fromFile(layout.buildDirectory.file("custom-windowsStartScript.txt"))
}

// Tell gradle to log standard output and error streams when running tests
test {
    testLogging.showStandardStreams = true
}

// Defining 'debug' task allows to set its arguments later   
tasks.register('debug', JavaExec) {
    dependsOn('classes')
    mainClass = "$app.ext.mainClass"
    classpath = sourceSets.main.runtimeClasspath
    debug true
}

//---------------
// Documentation
//---------------

javadoc {
    title = "${app.ext.programName}-${project.version} API"
    options.overview('src/main/java/overview.html')

    // Copy images from src to javadoc hierarchy
    doLast {
        copy {
            from 'src/main/java'
            include '**/*.png'
            into layout.buildDirectory.file("docs/javadoc")
        }
    }
}

// To avoid too many warnings in building javadoc
if (JavaVersion.current().isJava8Compatible()) {
    allprojects {
        tasks.withType(Javadoc).tap {
            configureEach {
                options.addStringOption('Xdoclint:none', '-quiet')
            }
        }
    }
}

//---------
// Flatpak
//---------

// Configure the flatpakGradleGenerator plugin task
def depsFile = file('build/dependencies.json')
tasks.flatpakGradleGenerator {
    description = "Collects all dependencies for Flatpak"
    outputFile = depsFile
    downloadDirectory = 'dependencies'

    // outputs.files = depsFile
    doFirst { println "app. Generating dependencies" }
    doLast { println "app. Dependencies generated" }
}

configurations {
    sharedConfiguration {
        canBeResolved = false
    }
}

artifacts {
    sharedConfiguration(depsFile)
}

//-----------
// Utilities
//-----------

// Ability to include private tasks (such as save)
fileTree("$projectDir/private").include('*.gradle').each { file ->
     apply from: file
}

// Utility task to print out head templates
tasks.register('printTemplates', JavaExec) {
    description = "Prints out head templates with various sizes"
    dependsOn('classes')
    mainClass = "org.audiveris.omr.image.TemplateFactory"
    classpath = sourceSets.main.runtimeClasspath

    if (project.hasProperty("cmdLineArgs")) {
        println "printTemplates. cmdLineArgs: "+ project.property('cmdLineArgs')
        args(project.property('cmdLineArgs').split())
    }
}
