/*
 * Copyright 2014-2025 Real Logic Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
plugins {
    id 'java-library'
    alias(libs.plugins.bnd).apply(false)
    alias(libs.plugins.shadow).apply(false)
    alias(libs.plugins.versions)
}

defaultTasks 'clean', 'build'

static def rawBuildJavaVersion()
{
    return System.getenv('BUILD_JAVA_VERSION') ?: JavaVersion.current().getMajorVersion()
}
boolean isEarlyAccessJavaVersion = rawBuildJavaVersion().endsWith("-ea")

static def getBuildJavaVersion() {
    def buildJavaVersion = rawBuildJavaVersion()
    if (buildJavaVersion.indexOf('.') > 0) {
        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('.'))
    }
    if (buildJavaVersion.indexOf('-') > 0) {
        buildJavaVersion = buildJavaVersion.substring(0, buildJavaVersion.indexOf('-'))
    }
    Integer.parseInt(buildJavaVersion)
}
int buildJavaVersion = getBuildJavaVersion()

def toolchainLauncher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(buildJavaVersion)
}

def toolchainCompiler = javaToolchains.compilerFor {
    languageVersion = JavaLanguageVersion.of(buildJavaVersion)
}

def toolchainJavadocTool = javaToolchains.javadocToolFor {
    languageVersion = JavaLanguageVersion.of(buildJavaVersion)
}

def agronaGroup = 'org.agrona'
def agronaVersion = file('version.txt').text.trim()

def getConfigProperty(final String projectPropertyName, final String envVarName) {
    String value = project.findProperty(projectPropertyName)
    if (!value) {
        value = System.getenv(envVarName)
        if (!value) {
            return null
        }
    }

    value = value.trim()

    return value ? value : null
}

ext {
    isReleaseVersion = !agronaVersion.endsWith('-SNAPSHOT')

    sonatypeCentralPortalReleasesRepoUrl = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/'
    sonatypeCentralPortalSnapshotsRepoUrl = 'https://central.sonatype.com/repository/maven-snapshots/'
    sonatypeCentralPortalUsername = getConfigProperty('sonatypeCentralPortalUsername', 'SONATYPE_CENTRAL_PORTAL_USERNAME')
    sonatypeCentralPortalPassword = getConfigProperty('sonatypeCentralPortalPassword', 'SONATYPE_CENTRAL_PORTAL_PASSWORD')

    signingKey = getConfigProperty('signingKey', 'SIGNING_GPG_SECRET_KEY')         // NOTE: ASCII armored secret key
    signingPassword = getConfigProperty('signingPassword', 'SIGNING_GPG_PASSWORD') // NOTE: Plain text
}

def projectPom = {
    name = 'agrona'
    // optionally artifactId can be defined here
    description = 'High performance primitives and utility library.'
    url = 'https://github.com/aeron-io/agrona'

    scm {
        connection = 'scm:git:https://github.com/aeron-io/agrona.git'
        developerConnection = 'scm:git:https://github.com/aeron-io/agrona.git'
        url = 'https://github.com/aeron-io/agrona.git'
    }

    licenses {
        license {
            name = 'The Apache License, Version 2.0'
            url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
        }
    }

    developers {
        developer {
            id = 'tmontgomery'
            name = 'Todd L. Montgomery'
            email = 'tmont@nard.net'
            url = 'https://github.com/tmontgomery'
        }
        developer {
            id = 'mjpt777'
            name = 'Martin Thompson'
            email = 'mjpt777@gmail.com'
            url = 'https://github.com/mjpt777'
        }
        developer {
            id = 'RichardWarburton'
            name = 'Richard Warburton'
            email = 'richard.warburton@gmail.com'
            url = 'https://github.com/RichardWarburton'
        }
        developer {
            id = 'nitsanw'
            name = 'Nitsan Wakart'
            email = 'nitsanw@yahoo.com'
            url = 'https://github.com/nitsanw'
        }
        developer {
            id = 'mikeb01'
            name = 'Mike Barker'
            email = 'mikeb01@gmail.com'
            url = 'https://github.com/mikeb01'
        }
        developer {
            id = 'vyazelenko'
            name = 'Dmytro Vyazelenko'
            email = 'vyazelenko@protonmail.com'
            url = 'https://github.com/vyazelenko'
        }
    }
}

allprojects {
    repositories {
        mavenLocal()
        mavenCentral()
    }

    configurations.configureEach {
        resolutionStrategy {
            failOnVersionConflict()

            force libs.byteBuddy,
                  libs.byteBuddy.agent,
                  // patching conflicting Checkstyle dependencies
                  libs.commons.codec,
                  libs.commons.lang3,
                  libs.httpcore,
                  libs.plexus.utils
        }
    }

    tasks.withType(JavaExec).configureEach {
        javaLauncher.set(toolchainLauncher)
    }
}

jar.enabled = false

subprojects {
    apply plugin: 'java-library'
    apply plugin: 'checkstyle'

    group = agronaGroup
    version = agronaVersion

    checkstyle.toolVersion = libs.versions.checkstyle.get()

    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(buildJavaVersion)
        }
        sourceCompatibility = 17
    }

    tasks.withType(Sign).configureEach {
        onlyIf {
            isReleaseVersion && gradle.taskGraph.hasTask(tasks.publish)
        }
    }

    tasks.withType(Jar).configureEach {
        enabled = true
        includeEmptyDirs = false
    }

    tasks.withType(JavaCompile).configureEach {
        options.compilerArgs.add('--release')
        options.compilerArgs.add(java.sourceCompatibility.majorVersion)
        options.compilerArgs.addAll(['-Xlint:all', '-Werror']) // Enable all warnings and treat them as errors
        options.encoding = 'UTF-8'
        options.deprecation = true
    }

    test {
        jvmArgs('--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED')
        jvmArgs('--add-opens', 'java.base/java.util.zip=ALL-UNNAMED')

        if (buildJavaVersion >= 21) {
            jvmArgs('-XX:+EnableDynamicAgentLoading')
        }

        useJUnitPlatform()

        testLogging {
            for (def level : LogLevel.values())
            {
                def testLogging = get(level)
                testLogging.exceptionFormat = 'full'
                testLogging.events = ["FAILED", "STANDARD_OUT", "STANDARD_ERROR"]
            }
        }

        systemProperties(
            'agrona.disable.bounds.checks': 'false',
            'agrona.strict.alignment.checks': 'true',
            'net.bytebuddy.experimental': 'true')

        javaLauncher.set(toolchainLauncher)
    }

    javadoc {
        title = '<h1>Agrona</h1>'
        options.bottom = '<i>Copyright &#169; 2014-2025 Real Logic Limited. All Rights Reserved.</i>'
        options.encoding = 'UTF-8'
        options.docEncoding = 'UTF-8'
        options.charSet = 'UTF-8'
        if (isEarlyAccessJavaVersion) {
            options.links("https://download.java.net/java/early_access/jdk${buildJavaVersion}/docs/api/")
        }
        else {
            options.links("https://docs.oracle.com/en/java/javase/${buildJavaVersion}/docs/api/")
        }

        options.addStringOption('-release', java.sourceCompatibility.majorVersion)
        options.addBooleanOption('Werror', true)
        options.addBooleanOption('Xdoclint:all', true)

        javadocTool.set(toolchainJavadocTool)
    }
}

project(':agrona') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'
    apply plugin: 'net.bytebuddy.byte-buddy-gradle-plugin'

    dependencies {
        testImplementation files("${layout.buildDirectory.get()}/classes/java/generated")
        testImplementation platform(libs.junit.bom)
        testImplementation "org.junit.jupiter:junit-jupiter-params"
        testImplementation libs.hamcrest
        testImplementation libs.mockito
        testImplementation libs.guava.testlib
        testImplementation libs.junit4 // Compatibility with JUnit 4
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
        testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }

    def mockitoAgent = configurations.create('mockitoAgent') { transitive = false }
    mockitoAgent.withDependencies {it.add(libs.mockito.get()) }

    tasks {
        test {
            jvmArgs("-javaagent:${mockitoAgent.asPath}")
        }
    }

    def generatedDir = file("${layout.buildDirectory.get()}/generated-src")
    sourceSets {
        generated {
            java.srcDir generatedDir
            compileClasspath += sourceSets.main.runtimeClasspath
        }
    }

    tasks.register('generatePrimitiveSpecialisations', JavaExec) {
        mainClass.set('org.agrona.generation.SpecialisationGenerator')
        classpath = sourceSets.main.runtimeClasspath
        outputs.dir generatedDir
    }

    tasks.register('generateUnsafeApi', org.agrona.build.UnsafeApiSourceGenerator) {
        outputDirectory = sourceSets.named('main').get().allJava.getSourceDirectories().singleFile
    }

    compileGeneratedJava.dependsOn('generatePrimitiveSpecialisations')
    compileTestJava.dependsOn compileGeneratedJava
    javadoc.dependsOn('generatePrimitiveSpecialisations')

    byteBuddy {
        transformation {
            plugin = org.agrona.build.UnsafeApiBytecodeGenerator.class
        }
    }

    jar {
        from sourceSets.generated.output

        manifest.attributes(
            'Automatic-Module-Name': 'org.agrona',
            'Implementation-Title': 'Agrona',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': agronaVersion
        )
    }

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

    javadoc {
        source += sourceSets.generated.allJava
        exclude 'org/agrona/UnsafeApi.java'
    }

    java {
        withSourcesJar()
        withJavadocJar()
    }

    publishing {
        publications {
            agrona(MavenPublication) {
                from components.java
                pom(projectPom)
            }
        }

        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (null != signingKey) {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.agrona
    }
}

project(':agrona-agent') {
    apply plugin: 'maven-publish'
    apply plugin: 'signing'
    apply plugin: 'com.gradleup.shadow'

    dependencies {
        implementation project(':agrona')
        implementation libs.byteBuddy
        compileOnly libs.findbugs.annotations
        testCompileOnly libs.findbugs.annotations
        testImplementation platform(libs.junit.bom)
        testImplementation 'org.junit.jupiter:junit-jupiter-params'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
        testImplementation libs.byteBuddy.agent
    }

    shadowJar {
        archiveClassifier.set('')

        manifest.attributes(
            'Automatic-Module-Name': 'org.agrona.agent',
            'Implementation-Title': 'Agrona',
            'Implementation-Vendor': 'Adaptive Financial Consulting Limited',
            'Implementation-Version': agronaVersion,
            'Agent-Class': 'org.agrona.agent.BufferAlignmentAgent',
            'Premain-Class': 'org.agrona.agent.BufferAlignmentAgent',
            'Can-Redefine-Classes': 'true',
            'Can-Retransform-Classes': 'true'
        )

        relocate 'net.bytebuddy', 'org.agrona.shadow.net.bytebuddy'
    }

    jar.finalizedBy shadowJar

    tasks.register('sourcesJar', Jar) {
        dependsOn ':agrona:generatePrimitiveSpecialisations'
        archiveClassifier.set('sources')
        from sourceSets.main.allSource
        from project(':agrona').sourceSets.main.allSource
        from project(':agrona').sourceSets.generated.allSource
    }

    javadoc {
        source += sourceSets.main.allJava
        source += project(':agrona').sourceSets.main.allJava
        source += project(':agrona').sourceSets.generated.allJava
    }
    javadoc.dependsOn ':agrona:generatePrimitiveSpecialisations'

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

    publishing {
        publications {
            agronaAgent(MavenPublication) {
                artifact shadowJar
                artifact sourcesJar
                artifact javadocJar
                pom(projectPom)
            }
        }
        repositories {
            maven {
                url = !isReleaseVersion ? sonatypeCentralPortalSnapshotsRepoUrl : sonatypeCentralPortalReleasesRepoUrl
                credentials {
                    username = sonatypeCentralPortalUsername
                    password = sonatypeCentralPortalPassword
                }
            }
        }
    }

    signing {
        if (null != signingKey)
        {
            useInMemoryPgpKeys(signingKey, signingPassword)
        }
        sign publishing.publications.agronaAgent
    }
}

project(':agrona-benchmarks') {
    apply plugin: 'com.gradleup.shadow'

    dependencies {
        implementation libs.jmh.core
        annotationProcessor libs.jmh.generator.annprocess
        implementation project(':agrona')
    }

    shadowJar {
        archiveFileName = 'benchmarks.jar'
        archiveClassifier.set('benchmarks')
        manifest.attributes('Main-Class': 'org.openjdk.jmh.Main')
    }

    jar.finalizedBy shadowJar
}

project(':agrona-concurrency-tests') {
    apply plugin: 'com.gradleup.shadow'

    dependencies {
        annotationProcessor libs.jcstress.core
        implementation libs.jcstress.core
        implementation project(':agrona')
    }

    shadowJar {
        archiveFileName = 'concurrency-tests.jar'
        archiveClassifier.set('concurrency-tests')
        manifest.attributes('Main-Class': 'org.openjdk.jcstress.Main')
    }

    jar.finalizedBy shadowJar

    tasks.register('concurrencyTests', JavaExec) {
        dependsOn shadowJar
        classpath = files(tasks.shadowJar)
        workingDir = layout.buildDirectory.get()
        args = ['-jvmArgs', '--add-opens java.base/jdk.internal.misc=ALL-UNNAMED -Dagrona.disable.bounds.checks=true -Dagrona.strict.alignment.checks=true -XX:+UseParallelGC', '-r', 'reports/jcstress']
        standardOutput = new NullOutputStream()
        errorOutput = new NullOutputStream()
    }

    def jcstressResults = project.hasProperty('jcstress-results') ? project.getProperty('jcstress-results') : ''

    tasks.register('parseResultsFile', JavaExec) {
        dependsOn shadowJar
        group = 'verification'
        classpath = files(tasks.shadowJar)
        workingDir = layout.buildDirectory.get()
        args = ['-p', jcstressResults, '-r', 'reports/jcstress']
    }
}

tasks.register('testReport', TestReport) {
    destinationDirectory = file("${layout.buildDirectory.get()}/reports/allTests")
    // Include the results from the `test` task in all subprojects
    testResults.setFrom(subprojects*.test)
}

def isNonStable = { String version ->
    def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
    def regex = /^[0-9,.v-]+(-r|-jre)?$/
    return !stableKeyword && !(version ==~ regex)
}

tasks.named("dependencyUpdates").configure {
    // Reject all non stable versions
    rejectVersionIf {
        isNonStable(it.candidate.version)
    }
}

tasks.register('copyTestLogs', Copy) {
    from '.'
    include '**/*.hprof'
    include '**/*.log'
    include '**/build/reports/tests/**'
    include '**/build/test-results/**/*'
    include '**/jcstress-results-*.gz'
    include 'LICENSE'
    exclude 'build'
    into 'build/test_logs'

    includeEmptyDirs = false
}

tasks.register('tarTestLogs', Tar) {
    dependsOn tasks.named('copyTestLogs')
    archiveBaseName.set('test_logs')
    from 'build/test_logs'
    compression = Compression.BZIP2
}

tasks.register('uploadArtifactsToCentralPortal', org.agrona.build.SonatypeCentralPortalUploadRepositoryTask) {
    portalUsername.set(sonatypeCentralPortalUsername)
    portalPassword.set(sonatypeCentralPortalPassword)
    groupId.set(agronaGroup)
    snapshotRelease.set(!isReleaseVersion)
}

wrapper {
    distributionType = 'ALL'
}

class NullOutputStream extends OutputStream
{
    void write(final int bytes) throws IOException
    {
    }

    void write(final byte[] bytes) throws IOException
    {
    }

    void write(final byte[] bytes, final int offset, final int length) throws IOException
    {
    }
}
