import net.ltgt.gradle.errorprone.CheckSeverity
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

import java.text.SimpleDateFormat

plugins {
    id 'java'
    id 'java-library'
    id 'maven-publish'
    id 'antlr'
    id 'signing'
    id "com.gradleup.shadow" version "8.3.8"
    id "biz.aQute.bnd.builder" version "6.4.0"
    id "io.github.gradle-nexus.publish-plugin" version "2.0.0"
    id "groovy"
    id "me.champeau.jmh" version "0.7.3"
    id "net.ltgt.errorprone" version '4.3.0'
    //
    // Kotlin just for tests - not production code
    id 'org.jetbrains.kotlin.jvm' version '2.2.20'
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21) // build on 21 - release on 11
    }
}

kotlin {
    compilerOptions {
        apiVersion = KotlinVersion.KOTLIN_2_0
        languageVersion = KotlinVersion.KOTLIN_2_0
        jvmTarget = JvmTarget.JVM_11
        javaParameters = true
        freeCompilerArgs = [
                '-Xemit-jvm-type-annotations',
                '-Xjspecify-annotations=strict',
        ]
    }
}

def makeDevelopmentVersion(parts) {
    def version = String.join("-", parts)
    println "created development version: $version"
    return version
}

def getDevelopmentVersion() {
    def dateTime = new SimpleDateFormat('yyyy-MM-dd\'T\'HH-mm-ss').format(new Date())
    def gitCheckOutput = new StringBuilder()
    def gitCheckError = new StringBuilder()
    def gitCheck = ["git", "-C", projectDir.toString(), "rev-parse", "--is-inside-work-tree"].execute()
    gitCheck.waitForProcessOutput(gitCheckOutput, gitCheckError)
    def isGit = gitCheckOutput.toString().trim()
    if (isGit != "true") {
        return makeDevelopmentVersion(["0.0.0", dateTime, "no-git"])
    }

    // a default Github Action env variable set to 'true'
    def isCi = Boolean.parseBoolean(System.env.CI)
    if (isCi) {
        def gitHashOutput = new StringBuilder()
        def gitHashError = new StringBuilder()
        def gitShortHash = ["git", "-C", projectDir.toString(), "rev-parse", "--short", "HEAD"].execute()
        gitShortHash.waitForProcessOutput(gitHashOutput, gitHashError)
        def gitHash = gitHashOutput.toString().trim()
        if (gitHash.isEmpty()) {
            println "git hash is empty: error: ${gitHashError.toString()}"
            throw new IllegalStateException("git hash could not be determined")
        }

        return makeDevelopmentVersion(["0.0.0", dateTime, gitHash])
    }

    def gitRevParseOutput = new StringBuilder()
    def gitRevParseError = new StringBuilder()
    def gitRevParse = ["git", "-C", projectDir.toString(), "rev-parse", "--abbrev-ref", "HEAD"].execute()
    gitRevParse.waitForProcessOutput(gitRevParseOutput, gitRevParseError)
    def branchName = gitRevParseOutput.toString().trim()

    return makeDevelopmentVersion(["0.0.0", branchName, "SNAPSHOT"])
}

def reactiveStreamsVersion = '1.0.3'
def releaseVersion = System.env.RELEASE_VERSION
def antlrVersion = '4.13.2' // https://mvnrepository.com/artifact/org.antlr/antlr4-runtime
def guavaVersion = '32.1.2-jre'
version = releaseVersion ? releaseVersion : getDevelopmentVersion()
group = 'com.graphql-java'

gradle.buildFinished { buildResult ->
    println "*******************************"
    println "*"
    if (buildResult.failure != null) {
        println "* FAILURE - ${buildResult.failure}"
    } else {
        println "* SUCCESS"
    }
    println "* Version: $version"
    println "*"
    println "*******************************"
}

repositories {
    mavenCentral()
    mavenLocal()
}

jar {
    from "LICENSE.md"
    from "src/main/antlr/Graphql.g4"
    from "src/main/antlr/GraphqlOperation.g4"
    from "src/main/antlr/GraphqlSDL.g4"
    from "src/main/antlr/GraphqlCommon.g4"
    manifest {
        attributes('Automatic-Module-Name': 'com.graphqljava')
    }
}

dependencies {
    api 'com.graphql-java:java-dataloader:5.0.3'
    api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion
    api "org.jspecify:jspecify:1.0.0"

    implementation 'org.antlr:antlr4-runtime:' + antlrVersion
    implementation 'com.google.guava:guava:' + guavaVersion

    testImplementation group: 'junit', name: 'junit', version: '4.13.2'
    testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
    testImplementation 'net.bytebuddy:byte-buddy:1.17.7'
    testImplementation 'org.objenesis:objenesis:3.4'
    testImplementation 'org.apache.groovy:groovy:4.0.28"'
    testImplementation 'org.apache.groovy:groovy-json:4.0.28'
    testImplementation 'com.google.code.gson:gson:2.13.2'
    testImplementation 'org.eclipse.jetty:jetty-server:11.0.26'
    testImplementation 'com.fasterxml.jackson.core:jackson-databind:2.20.0'
    testImplementation 'org.awaitility:awaitility-groovy:4.3.0'
    testImplementation 'com.github.javafaker:javafaker:1.0.2'

    testImplementation 'org.reactivestreams:reactive-streams-tck:' + reactiveStreamsVersion
    testImplementation "io.reactivex.rxjava2:rxjava:2.2.21"
    testImplementation "io.projectreactor:reactor-core:3.7.11"

    testImplementation 'org.testng:testng:7.11.0' // use for reactive streams test inheritance
    testImplementation "com.tngtech.archunit:archunit-junit5:1.4.1"
    testImplementation 'org.openjdk.jmh:jmh-core:1.37' // required for ArchUnit to check JMH tests

    antlr 'org.antlr:antlr4:' + antlrVersion

    // this is needed for the idea jmh plugin to work correctly
    jmh 'org.openjdk.jmh:jmh-core:1.37'
    jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.37'

    // comment this in if you want to run JMH benchmarks from idea
//    jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'

    errorprone 'com.uber.nullaway:nullaway:0.12.10'
    errorprone 'com.google.errorprone:error_prone_core:2.42.0'

    // just tests - no Kotlin otherwise
    testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}

shadowJar {
    minimize()
    archiveClassifier.set('')
    configurations = [project.configurations.compileClasspath]
    relocate('com.google.common', 'graphql.com.google.common') {
        include 'com.google.common.collect.*'
        include 'com.google.common.base.*'
        include 'com.google.common.math.*'
        include 'com.google.common.primitives.*'
    }
    relocate('org.antlr.v4.runtime', 'graphql.org.antlr.v4.runtime')
    dependencies {
        include(dependency('com.google.guava:guava:' + guavaVersion))
        include(dependency('org.antlr:antlr4-runtime:' + antlrVersion))
    }
    from "LICENSE.md"
    from "src/main/antlr/Graphql.g4"
    from "src/main/antlr/GraphqlOperation.g4"
    from "src/main/antlr/GraphqlSDL.g4"
    from "src/main/antlr/GraphqlCommon.g4"
    manifest {
        attributes('Automatic-Module-Name': 'com.graphqljava')
    }
    //Apply biz.aQute.bnd.builder plugin logic to shadowJar as in BndBuilderPlugin 
    convention.plugins.bundle = new aQute.bnd.gradle.BundleTaskConvention(it)
    doLast {
        //Call bnd after the ShadowJar was built to update the MANIFEST.MF
        buildBundle()
    }

    //Configure bnd for shadowJar
    // -exportcontents: graphql.*  Adds all packages of graphql and below to the exported packages list
    // -removeheaders:  Private-Package Removes the MANIFEST.MF header Private-Package, which contains all the internal packages and 
    //                                  also the repackaged packages like guava, which would be wrong after repackaging.
    // Import-Package:  Changes the imported packages header, to exclude guava and dependencies from the import list (! excludes packages)
    //                  Guava was repackaged and included inside the jar, so we need to remove it.
    //                  ANTLR was shaded, so we need to remove it.
    //                  sun.misc is a JRE internal-only class that is not directly used by graphql-java. It was causing problems in libraries using graphql-java.
    //                  The last ,* copies all the existing imports from the other dependencies, which is required.
    bnd('''
-exportcontents: graphql.*
-removeheaders: Private-Package
Import-Package: !android.os.*,!com.google.*,!org.checkerframework.*,!graphql.com.google.*,!org.antlr.*,!graphql.org.antlr.*,!sun.misc.*,org.jspecify.annotations;resolution:=optional,*
''')
}


task extractWithoutGuava(type: Copy) {
    from({ zipTree({ "build/libs/graphql-java-${project.version}.jar" }) }) {
        exclude('/com/**')
    }
    into layout.buildDirectory.dir("extract")
}

extractWithoutGuava.dependsOn jar

task buildNewJar(type: Jar) {
    from layout.buildDirectory.dir("extract")
    archiveFileName = "graphql-java-tmp.jar"
    destinationDirectory = file("${project.buildDir}/libs")
    manifest {
        from file("build/extract/META-INF/MANIFEST.MF")
    }
    doLast {
        delete("build/libs/graphql-java-${project.version}.jar")
        file("build/libs/graphql-java-tmp.jar").renameTo(file("build/libs/graphql-java-${project.version}.jar"))
    }
}

buildNewJar.dependsOn extractWithoutGuava

shadowJar.finalizedBy extractWithoutGuava, buildNewJar


task testng(type: Test) {
    useTestNG()
}
check.dependsOn testng

compileJava {
    options.compilerArgs += ["-parameters"]
    source file("build/generated-src"), sourceSets.main.java
}

tasks.withType(GroovyCompile) {
    // Options when compiling Java using the Groovy plugin.
    // (Groovy itself defaults to UTF-8 for Groovy code)
    options.encoding = 'UTF-8'
    sourceCompatibility = '11'
    targetCompatibility = '11'
    groovyOptions.forkOptions.memoryMaximumSize = "4g"
}

tasks.withType(JavaCompile) {
    options.release = 11
    options.errorprone {
        disableAllChecks = true
        check("NullAway", CheckSeverity.ERROR)
        //
        // end state has us with this config turned on - eg all classes
        //
        //option("NullAway:AnnotatedPackages", "graphql")
        option("NullAway:CustomContractAnnotations", "graphql.Contract")
        option("NullAway:OnlyNullMarked", "true")
        option("NullAway:JSpecifyMode", "true")
    }
    // Include to disable NullAway on test code
    if (name.toLowerCase().contains("test")) {
        options.errorprone {
            disable("NullAway")
        }
    }
}

generateGrammarSource {
    includes = ['Graphql.g4']
    maxHeapSize = "64m"
    arguments += ["-visitor"]
    outputDirectory = file("${project.buildDir}/generated-src/antlr/main/graphql/parser/antlr")
}
generateGrammarSource.inputs
        .dir('src/main/antlr')
        .withPropertyName('sourceDir')
        .withPathSensitivity(PathSensitivity.RELATIVE)


task sourcesJar(type: Jar) {
    dependsOn classes
    archiveClassifier = 'sources'
    from sourceSets.main.allSource
}

task javadocJar(type: Jar, dependsOn: javadoc) {
    archiveClassifier = 'javadoc'
    from javadoc.destinationDir
}

javadoc {
    options.encoding = 'UTF-8'
}

artifacts {
    archives sourcesJar
    archives javadocJar
}

List<TestDescriptor> failedTests = []

tasks.withType(Test) {
    useJUnitPlatform()
    testLogging {
        events "FAILED", "SKIPPED"
        exceptionFormat = "FULL"
    }

    // Required for JMH ArchUnit tests
    classpath += sourceSets.jmh.output
    dependsOn "jmhClasses"

    afterTest { TestDescriptor descriptor, TestResult result ->
        if (result.getFailedTestCount() > 0) {
            failedTests.add(descriptor)
        }
    }
}

tasks.register('testWithJava17', Test) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
tasks.register('testWithJava11', Test) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(11)
    }
}
test.dependsOn testWithJava17
test.dependsOn testWithJava11


/*
 * The gradle.buildFinished callback is deprecated BUT there does not seem to be a decent alternative in gradle 7
 * So progress over perfection here
 *
 * See https://github.com/gradle/gradle/issues/20151
 */
gradle.buildFinished {
    if (!failedTests.isEmpty()) {
        println "\n\n"
        println "============================"
        println "These are the test failures"
        println "============================"
        for (td in failedTests) {
            println "${td.getClassName()}.${td.getDisplayName()}"
        }
        println "============================"
    }
}


allprojects {
    tasks.withType(Javadoc) {
        exclude('**/antlr/**')
    }
}

publishing {

    publications {

        graphqlJava(MavenPublication) {
            version version
            from components.java

            artifact sourcesJar {
                archiveClassifier = "sources"
            }
            artifact javadocJar {
                archiveClassifier = "javadoc"
            }
            pom.withXml {
                // Removing antlr4 below (introduced in `1ac98bf`) addresses an issue with
                // the Gradle ANTLR plugin. `1ac98bf` can be reverted and this comment removed once
                // that issue is fixed and Gradle upgraded. See https://goo.gl/L92KiF and https://goo.gl/FY0PVR.
                //
                // Removing antlr4-runtime and guava because the classes we want to use are "shaded" into the jar itself
                // via the shadowJar task
                def pomNode = asNode()
                pomNode.dependencies.'*'.findAll() {
                    it.artifactId.text() == 'antlr4' || it.artifactId.text() == 'antlr4-runtime' || it.artifactId.text() == 'guava'
                }.each() {
                    it.parent().remove(it)
                }
                pomNode.children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    name 'graphql-java'
                    description 'GraphqL Java'
                    url "https://github.com/graphql-java/graphql-java"
                    scm {
                        url "https://github.com/graphql-java/graphql-java"
                        connection "https://github.com/graphql-java/graphql-java"
                        developerConnection "https://github.com/graphql-java/graphql-java"
                    }
                    licenses {
                        license {
                            name 'MIT'
                            url 'https://github.com/graphql-java/graphql-java/blob/master/LICENSE.md'
                            distribution 'repo'
                        }
                    }
                    developers {
                        developer {
                            id 'andimarek'
                            name 'Andreas Marek'
                        }
                    }
                }
            }
        }
    }
}

nexusPublishing {
    repositories {
        sonatype {
            username = System.env.MAVEN_CENTRAL_USER_NEW
            password = System.env.MAVEN_CENTRAL_PASSWORD_NEW
            // https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
            nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
            // GraphQL Java does not publish snapshots, but adding this URL for completeness
            snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
        }
    }
}

signing {
    required { !project.hasProperty('publishToMavenLocal') }
    def signingKey = System.env.MAVEN_CENTRAL_PGP_KEY
    useInMemoryPgpKeys(signingKey, "")
    sign publishing.publications
}


// all publish tasks depend on the build task
tasks.withType(PublishToMavenRepository) {
    dependsOn build
}

// Only publish Maven POM, disable default Gradle modules file
tasks.withType(GenerateModuleMetadata) {
    enabled = false
}



