/*

    Content :

        1.  BUILD DEPENDENCIES
        2.  BUILD PLUGINS
        3.  UTILITY LAMBDAS FOR AUTOMATIC VERSION MIGRATION
        4.  VERSION MIGRATION
        5.  SETTING JAVA VERSION AND DEPENDENCY SRC
        6.  LOADING AND COMPILING DEPENDENCIES
        7.  CONFIGURING TEST EXECUTION
        8.  CONFIGURING TEST REPORTING
        9.  CONFIGURING MUTATION TESTING
        10. CUSTOM DEPLOYMENT TASKS
        11. JAR CREATION
        12. JDOC CREATION
        13. MAVEN DEPLOYMENT CONFIGURATION

*/
//______________________________________________________________________________________________________________________
//----------------------------------------------------------------------------------------------------------------------
// 1. BUILD DEPENDENCIES :

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
        gradlePluginPortal()
    }
}

//----------------------------------------------------------------------------------------------------------------------
// 2. BUILD PLUGINS :

plugins {
    id 'java-library'

    id 'java'
    id 'groovy'

    // Test reporting :
    id 'jacoco'

    // Mutation testing : (pitest)
    id 'info.solidsoft.pitest' version '1.5.1'

    // Kotlin tests:
    id 'org.jetbrains.kotlin.jvm' version '1.7.10'

    // Public deployment :
    id 'maven-publish'
    id 'signing'
    id('io.github.gradle-nexus.publish-plugin') version '1.1.0'

    // For static analysis and null checking:
    id "net.ltgt.errorprone" version "3.1.0"
}

// If current version >= 11 we can do static analysis:
boolean WE_CAN_DO_STATIC_ANALYSIS = JavaVersion.current().isJava11Compatible()
println "Static analysis enabled: $WE_CAN_DO_STATIC_ANALYSIS"


// This gives some more freedom for comments ( special characters... ).
compileJava { options.encoding = "UTF-8" } // Allows for '"' characters in comments for example...

String PROJECT = "neureka"
String VERSION = "1.0.1"
String GROUP_ID = "com.github.gleethos"
String BASENAME = "neureka"

boolean SIGN_ON_PUBLISH = false

//----------------------------------------------------------------------------------------------------------------------
// 3. UTILITY LAMBDAS FOR AUTOMATIC VERSION MIGRATION :

def change = { c -> new Object() {
            def between = { String a, String b -> new Object() {
                    def inFile = { String p, boolean unchanged = false ->
                        File file = new File( "$rootDir" + p )
                        if ( !file.exists() ) return
                        String[] parts = file.text.split( a )
                        if ( parts.length == 1 ) println( '-=>\n'+ a )
                        assert parts.length == 2
                        String[] tails = parts[1].split( b )
                        if ( tails.length == 1 ) println( '-=>\n'+ b )
                        assert tails.length >= 2
                        b = b.replace("\\", "")
                        a = a.replace("\\", "")
                        if ( tails.length > 2 ) {
                            tails[1] = (tails as List)[1..tails.length-1].join(b)
                        }
                        if( !unchanged ) file.write( parts[0] + a + c(tails[0]) + b + tails[1] )
                    }
                }
            }
        }
    }

//----------------------------------------------------------------------------------------------------------------------
// 4. VERSION MIGRATION : SETTING VERSION IN SRC AND PRODUCTION :

task setVersionAcrossProject() {
    change({VERSION}).between( "return \"", "\"" ).inFile("/src/main/resources/library_settings.groovy")
    change({VERSION}).between( "String _VERSION = \"", "\";" ).inFile("/src/main/java/neureka/Neureka.java")
    change({VERSION}).between( "Neureka.version\\(\\)==\"", "\"//version" ).inFile("/src/test/groovy/ThoroughGroovyTests.groovy")
    change({VERSION}).between( "Neureka.version\\(\\)==\"", "\"//version" ).inFile("/src/test/groovy/ut/neureka/Neureka_Spec.groovy")

    change({VERSION}).between( "VERSION = \"", "\" // " ).inFile("/src/test/resources/SpockConfig.Groovy")

    change({VERSION}).between( "raw/master/production/lib/neureka-", "\\.jar\\)" ).inFile("/README.md")
    change({VERSION}).between( "neureka/master/production/lib/neureka-", "\\.jar\\)" ).inFile("/README.md")
    change({VERSION}).between( "<version>", "</version>").inFile("/README.md")

    File readme = new File("$rootDir/README.md")
    readme.write( readme.text.replaceAll( "implementation 'com\\.github\\.gleethos:neureka:\\d(\\.\\d+){2}(-pre)*(-alpha)*", "implementation 'com.github.gleethos:neureka:"+VERSION ) )
    readme.write( readme.text.replaceAll( "implementation\\(\"com.github\\.gleethos:neureka:\\d(\\.\\d+){2}(-pre)*(-alpha)*", "implementation(\"com.github.gleethos:neureka:"+VERSION ) )
    readme.write( readme.text.replaceAll( "implementation 'com\\.github\\.Gleethos:neureka:v\\d(\\.\\d+){2}(-pre)*(-alpha)*", "implementation 'com.github.Gleethos:neureka:v"+VERSION ) )
    readme.write( readme.text.replaceAll( "@Grab\\('com\\.github\\.Gleethos:neureka:v\\d(\\.\\d+){2}(-pre)*(-alpha)*", "@Grab\\('com\\.github\\.Gleethos:neureka:v"+VERSION ) )
}

// SETTING VERSION AND GROUP-ID FOR BUILDING :

group GROUP_ID
archivesBaseName = BASENAME
version VERSION

//----------------------------------------------------------------------------------------------------------------------
// 5. SETTING JAVA VERSION AND DEPENDENCY SRC:

sourceCompatibility = 8
targetCompatibility = 8

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
}

//----------------------------------------------------------------------------------------------------------------------
// 6. LOADING AND COMPILING DEPENDENCIES :

// Note: Visit the java-library gradle plugin documentation for more information about api vs implementation.
dependencies {
    if ( WE_CAN_DO_STATIC_ANALYSIS ) {
        errorprone "com.uber.nullaway:nullaway:0.10.25"
        compileOnly "com.google.code.findbugs:jsr305:3.0.2"
        errorprone "com.google.errorprone:error_prone_core:2.27.0"
    }
    else
        errorprone "com.google.errorprone:error_prone_core:2.9.+"


    // LIBRARY DEPENDENCIES:

    implementation group: 'org.jspecify', name: 'jspecify', version: '1.0.0' // Annotations for null checking
    implementation 'org.jocl:jocl:2.0.5' // OpenCL
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '[1+, )' // Logging

    // TESTING SUITE:

    testImplementation 'org.slf4j:slf4j-api:2.0.12' // Logging in tests
    testImplementation 'org.slf4j:slf4j-simple:2.0.12'

    // Groovy, Spock (+ sl4j, + Spock-Reports)
    testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
    testImplementation 'com.athaydes:spock-reports:2.5.1-groovy-4.0'
    testRuntimeOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.12.10' // allows mocking of classes (in addition to interfaces)
    testRuntimeOnly group: 'org.objenesis', name: 'objenesis', version: '3.2'      // allows mocking of classes without default constructor (together with ByteBuddy or CGLIB)

    // Kotlin tests:
    testImplementation 'org.jetbrains.kotlin:kotlin-script-runtime:1.7.10' // first the runtime so that the tests compile
    testImplementation 'org.jetbrains.kotlin:kotlin-test' // Then some test dependencies to do assertions...
    testImplementation 'org.assertj:assertj-core:3.23.1' // Same story here!
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
}

compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}


//----------------------------------------------------------------------------------------------------------------------
// 7. CONFIGURING TEST EXECUTION :

test {
    //filter { includeTestsMatching "*" } // Here you can specify a filter for the tests.

    // Spock-Reports configuration can be found in the test resources: src/test/resources/SpockConfig.Groovy

    useJUnitPlatform() // ...otherwise spock tests will not be received by gradle...
    testLogging {
        exceptionFormat = 'full' // So we see spock power assert messages!
    }

    // Memory :
    minHeapSize = "1g"
    maxHeapSize = "5g"

    // Number of threads used for testing :
    maxParallelForks = 1//Math.floor(1+(Runtime.runtime.availableProcessors().intdiv(2) ?: 1)/2)
}

if ( WE_CAN_DO_STATIC_ANALYSIS ) {
    tasks.withType(JavaCompile) {
        // remove the if condition if you want to run NullAway on test code
        if (!name.toLowerCase().contains("test")) {
            options.errorprone {
                check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
                option("NullAway:AnnotatedPackages", "")
                // We disable the NonCanonicalType because non-canonical type declarations are used for convenience
                check("NonCanonicalType", net.ltgt.gradle.errorprone.CheckSeverity.OFF)
            }
        } else {
            options.errorprone {
                check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.WARN)
                option("NullAway:AnnotatedPackages", "")
            }
        }
    }
}


//----------------------------------------------------------------------------------------------------------------------
// 8. CONFIGURING TEST REPORTING :

jacoco {
    toolVersion = "0.8.11"
    reportsDirectory.set(file("docs/coverage"))
}

jacocoTestReport {
    reports {
        xml.required.set(true)
        csv.required.set(false)
    }
}
check.dependsOn jacocoTestReport

//----------------------------------------------------------------------------------------------------------------------
// 9. CONFIGURING MUTATION TESTING :

// This take forever to complete, strong test filtering is needed!
pitest {
    junit5PluginVersion = '0.12' // This is important, otherwise pitest will not run.
    //jvmArgs = ['-Xmx1024m', '-ea']
    useClasspathFile = true     // useful with bigger projects on Windows
    testSourceSets = [sourceSets.test]
    mainSourceSets = [sourceSets.main]
    targetClasses = ["neureka*"]  // by default "${project.group}.*" //:= com.github.gleethos.*
    targetTests = ["ut.*", "it.*", "st.*"]
    pitestVersion = '1.5.1' // not needed when a default PIT version should be used
    threads = (int) Math.floor(1+(Runtime.runtime.availableProcessors().intdiv(2) ?: 1)/2)
    outputFormats = ['XML', 'HTML']
    timestampedReports = false
}


//----------------------------------------------------------------------------------------------------------------------
// 10. CUSTOM DEPLOYMENT TASKS :

tasks.register('releaseJDocs', Copy) {
    dependsOn 'javadoc'
    from "$rootDir/build/docs/javadoc"
    into "$rootDir/docs/jdocs"
}

// 1:
tasks.register('cleanForDeployment', Delete) {
    delete "$rootDir/build"
    if (new File("$rootDir/docs/jdocs").listFiles() != null)
        delete new File("$rootDir/docs/jdocs").listFiles()
    if (new File("$rootDir/docs/spock/reports").listFiles() != null)
        delete new File("$rootDir/docs/spock/reports").listFiles()
    if (new File("$rootDir/docs/spock/aggregated").listFiles() != null)
        delete new File("$rootDir/docs/spock/aggregated").listFiles()
}
// 2:
tasks.register('deploy') {

    dependsOn 'cleanForDeployment'

    dependsOn 'check'
    dependsOn 'jar'
    dependsOn 'releaseJDocs'

}
// 3:
//     execute dependsOn 'publishToSonatype' then
//     go to https://oss.sonatype.org/#stagingRepositories
//     close and release the repository


//----------------------------------------------------------------------------------------------------------------------
// 11. JAR CREATION :

jar {
    copy {
        from "$rootDir/build/libs/neureka-"+VERSION+".jar"
        into "$rootDir/production/lib"
    }
}

tasks.register('sourcesJar', Jar) {
    archiveClassifier.set("sources")
    from sourceSets.main.allSource
}

tasks.register('javadocJar', Jar) {
    archiveClassifier.set("javadoc")
    from javadoc
}

//----------------------------------------------------------------------------------------------------------------------
// 12. JDOC CREATION:

tasks.register('createJDocs', Javadoc) {
    destinationDir = new File("$projectDir/docs/jdocs")
    if (JavaVersion.current().isJava9Compatible()) {
        options.addBooleanOption('html5', true)
    }
}

//----------------------------------------------------------------------------------------------------------------------
// 13. MAVEN DEPLOYMENT CONFIGURATION: (! WIP !)

artifacts {
    archives javadocJar, sourcesJar
}

java {
    withJavadocJar()
    withSourcesJar()
}

ext.genOutputDir = file("$buildDir/generated-resources")

tasks.register('generateVersionTxt') {
    ext.outputFile = file("$genOutputDir/version.txt")
    outputs.file(outputFile)
    doLast {
        outputFile.text = """GroupId: ${project.group}
Name: ${project.name}
Version: $version
Build-time: ${java.time.LocalDateTime.now()}
"""
    }
}

sourceSets.main.output.dir genOutputDir, builtBy: generateVersionTxt


signing {
    required { SIGN_ON_PUBLISH && gradle.taskGraph.hasTask("publish") }
    //Key-ID: 372447547E38B0A909776E4DD830DAE574A07986
    sign configurations.archives
    sign publishing.publications
}

nexusPublishing {
    packageGroup = GROUP_ID
    repositories { sonatype() }
    transitionCheckOptions {
        // closing checks take some time so increase the retries
        // total time is maxRetries(180) * delayBetween(10s) = 30m
        maxRetries.set(180)
    }
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from(components.java)
            pom {
                name = PROJECT
                description = 'A platform independent tensor library written in Java.'
                url = 'https://gleethos.github.io/'+PROJECT+'/index.html'
                organization {
                    name = 'com.github.gleethos'
                    url = 'https://github.com/Gleethos'
                }
                issueManagement {
                    system = 'Github'
                    url = 'https://github.com/Gleethos/'+PROJECT+'/issues'
                }
                licenses {
                    license {
                        name = 'MIT License'
                        url = "https://raw.githubusercontent.com/Gleethos/"+PROJECT+"/master/LICENSE"
                    }
                }
                developers {
                    developer {
                        id = "Gleethos"
                        name = "Daniel Nepp"
                        email = "Gleethos@gmx.at"
                    }
                }
                scm {
                    url = 'https://github.com/Gleethos/'+PROJECT+''
                    connection = 'scm:git:git://github.com/Gleethos/'+PROJECT+'.git'
                    developerConnection = 'scm:git:ssh://git@github.com:Gleethos/'+PROJECT+'.git'
                }
            }
        }
    }
}
