/*
 * Copyright 2015-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'
    id 'checkstyle'
    alias(libs.plugins.versions)
    alias(libs.plugins.shadow).apply(false)
    alias(libs.plugins.protobuf).apply(false)
}


defaultTasks 'clean', 'build'

static int getBuildJavaVersion() {
    def buildJavaVersion = System.getenv('BUILD_JAVA_VERSION') ?: JavaVersion.current().getMajorVersion()
    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 benchmarksVersion = file('version.txt').text.trim()

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

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

allprojects {
    group = 'io.aeron.benchmarks'
    version = benchmarksVersion

    repositories {
        mavenLocal()
        mavenCentral()
    }

    configurations.configureEach {
        resolutionStrategy {
            failOnVersionConflict()

            force libs.agrona,
                  libs.slf4j.api,
                  libs.scala.library,
                  libs.scala.reflect,
                  libs.errorprone,
                  libs.checker,
                  libs.commons.io,
                  libs.commons.logging,
                  // patching conflicting Checkstyle dependencies
                  libs.commons.codec,
                  libs.commons.lang3,
                  libs.httpcore,
                  libs.plexus.utils
        }
    }
}

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

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

    apply plugin: 'maven-publish'


    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
                artifactId = project.name
            }
        }
    }

    dependencies {
        testImplementation platform("org.junit:junit-bom:${libs.versions.junit.get()}")
        testImplementation "org.junit.jupiter:junit-jupiter-params"
        testImplementation libs.mockito
        testImplementation libs.hamcrest
        testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
        testRuntimeOnly "org.junit.platform:junit-platform-launcher"
    }

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

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

    tasks.withType(JavaCompile).configureEach {
        javaCompiler.set(toolchainCompiler)
        options.compilerArgs.add('-XDignore.symbol.file') // Suppress warnings about using Unsafe
        options.encoding = 'UTF-8'
        options.deprecation = true
    }

    tasks.withType(Test).configureEach {
        useJUnitPlatform()
        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')
        }

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

        javaLauncher.set(toolchainLauncher)
    }
}

project(':benchmarks-api') {
    dependencies {
        api libs.agrona
        api libs.hdrHistogram
    }
}

project(':benchmarks-aeron') {
    dependencies {
        api project(':benchmarks-api')
        annotationProcessor libs.jmh.generator.annprocess
        implementation libs.jmh.core
        implementation libs.aeron.cluster
        implementation libs.disruptor
        runtimeOnly libs.aeron.samples
    }
}

project(':benchmarks-grpc') {
    apply plugin: 'com.google.protobuf'

    dependencies {
        api project(':benchmarks-api')
        implementation libs.grpc.protobuf
        implementation libs.grpc.stub
        implementation libs.grpc.netty.shaded
        compileOnly libs.tomcat.annotations.api
    }

    protobuf {
        protoc { artifact = "com.google.protobuf:protoc:${libs.versions.protoc.get()}" }
        plugins {
            grpc { artifact = "io.grpc:protoc-gen-grpc-java:${libs.versions.grpc.get()}" }
        }
        generateProtoTasks {
            all()*.plugins { grpc {} }
        }
    }

    sourceSets {
        main {
            proto {
                srcDir 'source/main/proto'
                srcDir 'build/generated/source/proto/main/java'
                srcDir 'build/generated/source/proto/main/grpc'
            }
        }
    }

    tasks.processResources.dependsOn('generateProto')
}

project(':benchmarks-kafka') {
    dependencies {
        api project(':benchmarks-api')
        implementation libs.kafka.clients
        runtimeOnly libs.kafka
        runtimeOnly libs.slf4j.log4j12
        testImplementation libs.kafka
        testImplementation libs.kafka.server.common
        testImplementation libs.kafka.metadata
        testRuntimeOnly libs.slf4j.log4j12
    }
}

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

    dependencies {
        implementation project(':benchmarks-aeron')
        implementation project(':benchmarks-grpc')
        implementation project(':benchmarks-kafka')
    }

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

    jar.finalizedBy shadowJar
}

static def benchmarkJvmArgs() {
    return '-Dagrona.disable.bounds.checks=true -XX:+UseParallelGC --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED'
}

tasks.register('runJavaIpcBenchmarks', Exec) {
    dependsOn ':benchmarks-all:shadowJar'
    commandLine toolchainLauncher.get().executablePath,
    '-jar', 'benchmarks-all/build/libs/benchmarks.jar',
    '-jvmArgs', benchmarkJvmArgs(),
    '-wi', '3', '-w', '1s', '-i', '5', '-r', '1s', '-tu', 'ns', '-f', '5'
}

tasks.register('runAeronJavaIpcBenchmarks', Exec) {
    dependsOn ':benchmarks-all:shadowJar'
    commandLine toolchainLauncher.get().executablePath,
    '-jar', 'benchmarks-all/build/libs/benchmarks.jar',
    '-jvmArgs', benchmarkJvmArgs(),
    'Aeron',
    '-wi', '3', '-w', '1s', '-i', '5', '-r', '1s', '-tu', 'ns', '-f', '5'
}

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

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

FileTree benchmarksFileTree = fileTree('.') {
    include 'benchmarks-all/build/libs/benchmarks.jar'
    include 'scripts/**'
    include 'certificates/**'
}

FileTree cDriverTree = (project.hasProperty('aeron.cdriver.package')) ?
        tarTree(resources.gzip(project.property('aeron.cdriver.package'))) :
        files().asFileTree

FileTree cDriverDpdkTree = (project.hasProperty('aeron.cdriver.dpdk.package')) ?
        tarTree(resources.gzip(project.property('aeron.cdriver.dpdk.package'))) :
        files().asFileTree

FileTree cDriverEfviTree = (project.hasProperty('aeron.cdriver.efvi.package')) ?
        tarTree(resources.gzip(project.property('aeron.cdriver.efvi.package'))) :
        files().asFileTree

FileTree cDriverAtsTree = (project.hasProperty('aeron.cdriver.ats.package')) ?
        tarTree(resources.gzip(project.property('aeron.cdriver.ats.package'))) :
        files().asFileTree

tasks.register('deployTar', Tar) {
    dependsOn ':benchmarks-all:shadowJar'

    preserveFileTimestamps = true
    reproducibleFileOrder = false
    useFileSystemPermissions()

    from(benchmarksFileTree)
    from(cDriverTree) {
        include "*/**"
        eachFile { fcd ->
            fcd.relativePath = new RelativePath(true, (String[])fcd.relativePath.segments.drop(1))
        }
        includeEmptyDirs = false
    }
    from(cDriverDpdkTree)
    from(cDriverAtsTree)
    from(cDriverEfviTree)
    archiveFileName = 'benchmarks.tar'
}

tasks.register('copyTestLogs', Copy) {
    from '.'
    include '**/build/test-output/**'
    include '**/*.log'
    include '**/*.tlog'
    include '**/build/reports/tests/**'
    include '**/build/test-results/*/TEST*.xml'
    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
}

wrapper {
    distributionType = 'ALL'
}
