/*
 Usage:
 ./gradlew build - builds code and creates a single jar in build/libs/frostwire.jar
 ./gradlew clean - cleans the build.
 ./gradlew tasks - shows available tasks.
 ./gradlew run - runs the app

 Make sure ${ANT_HOME} is properly set for these tasks to work
 ./gradlew gettextExtract - extract all the strings from the sourcecode into frostwire.pot
 ./gradlew gettextBundle  - create messages.jar with the extracted strings.
*/
plugins {
    id 'java'
    id 'application'
    id 'me.champeau.jmh' version '0.7.2'
}

group = 'com.frostwire'
version = '1.0-SNAPSHOT'

import org.gradle.internal.os.OperatingSystem

java {
    sourceCompatibility = JavaVersion.VERSION_19
    targetCompatibility = JavaVersion.VERSION_19
}

compileJava {
    options.encoding = '8859_1'
    options.fork = true
    options.compilerArgs += ['-Xlint:deprecation']
    options.compilerArgs += ['-Xlint:unchecked']
}

application {
    mainClass = 'com.limegroup.gnutella.gui.Main'
    applicationDefaultJvmArgs = ['-Djava.library.path=lib/native',
                                 '--add-opens=java.desktop/java.awt=ALL-UNNAMED',
                                 '--add-opens=java.desktop/javax.swing=ALL-UNNAMED',
                                 '--enable-native-access=ALL-UNNAMED',
'-XX:+PreserveFramePointer',
'-XX:ErrorFile=./hs_err_pid%p.log',
'-XX:+CreateCoredumpOnCrash',
                                 '-Xms64m',
                                 '-Xmx512m',
                                 //'-Xss2m', 2MB stack (debugging silent crashes: prolong crashes due to native stack overflows, attach gdb, etc.)
                                 //'-Xss768k', // 768k stack (development)
                                 '-Xss256k', // 256k stack (production)
                                 '-Xcheck:jni',
                                 '-Dcom.sun.management.jmxremote.port=9595',
                                 '-Dcom.sun.management.jmxremote.ssl=false',
                                 '-Dcom.sun.management.jmxremote.authenticate=false',
                                 '-Djava.rmi.server.hostname=127.0.0.1',
                                 '-agentlib:jdwp=transport=dt_socket,address=9696,server=y,suspend=n',
                                 '-Dfw.debug.assertNoEdtIO=true', // only in development mode, see LibraryUtils for usage (Boolean.getBoolean("fw.debug.assertNoEdtIO"))
                                 '-Ddebug=1',
//'-XX:NativeMemoryTracking=detail',
    ]

    def flight_recorder = false

    if (flight_recorder) {
        applicationDefaultJvmArgs.add('-XX:StartFlightRecording=name=FW-Startup,' + 'settings=profile,' + // higher-detail preset
                'delay=1s,' + 'disk=true,' + 'dumponexit=true,' + 'maxage=10m,' +                       // keep last 10 minutes
                'maxsize=256m,' +                     // cap file size
                'filename=build/frostwire-startup.jfr')
        // To open the .jfr file with all the recorded data you need JDK Mission Control
        // $ brew install --cask jdk-mission-control
        // $ open -a "JDK Mission Control" build/frostwire-startup.jfr
    }

    if (OperatingSystem.current().isMacOsX()) {
        applicationDefaultJvmArgs += ['--add-exports=java.desktop/com.apple.laf=ALL-UNNAMED', '-Dsun.java2d.metal=true']
    }
}

repositories {
    mavenCentral()
    maven { url = 'https://dl.frostwire.com/maven' }
}

dependencies {
    // @Nullable cross-os annotation
    implementation 'org.jetbrains:annotations:26.0.2'

    implementation 'com.google.re2j:re2j:1.8'
    implementation 'com.google.code.gson:gson:2.13.2'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.googlecode.gettext-commons:gettext-commons:0.9.8'
    implementation 'org.xerial:sqlite-jdbc:3.50.3.0'

    def jlibtorrent_version = '2.0.12.5'
    def os = OperatingSystem.current()
    def arch = System.getProperty('os.arch').toLowerCase()
    def is_arm64 = arch == "aarch64" || arch == "arm64"

    def use_maven_jlibtorrent = true

    if (use_maven_jlibtorrent) {
        // jlibtorrent java wrappers
        implementation "com.frostwire:jlibtorrent:${jlibtorrent_version}"

        // jlibtorrent native libraries
        // if we are on windows, we need to use the windows native libraries
        if (os.isWindows()) {
            implementation "com.frostwire:jlibtorrent-windows:${jlibtorrent_version}"
        } else if (os.isLinux()) {
            implementation "com.frostwire:jlibtorrent-linux:${jlibtorrent_version}"
        } else if (os.isMacOsX()) {
            // if it's arm mac
            if (is_arm64) {
                implementation "com.frostwire:jlibtorrent-macosx-arm64:${jlibtorrent_version}"
            }
            // intel
            else {
                implementation "com.frostwire:jlibtorrent-macosx-x86_64:${jlibtorrent_version}"
            }
        }
    } else {
        // assume use has frostwire-jlibtorrent project checked out (dev mode) at the same level as the frostwire project
        // we are currently at frostwire/desktop
        def jlibtorrent_built_jars_path = '../../frostwire-jlibtorrent/build/libs'
        System.out.println("Using jlibtorrent jars from development environment at ${jlibtorrent_built_jars_path}")

        def main_jar = files("${jlibtorrent_built_jars_path}/jlibtorrent-${jlibtorrent_version}.jar")
        // print everything in the main_jar file collection
        // print the absolute path of the main jar
        main_jar.each { file ->
            System.out.println("Using jlibtorrent jar: ${file.name}")
            System.out.println("Absolute path: ${file.absolutePath}")
        }

        implementation files("${jlibtorrent_built_jars_path}/jlibtorrent-${jlibtorrent_version}.jar")

        if (os.isWindows()) {
            implementation files("${jlibtorrent_built_jars_path}/jlibtorrent-windows-${jlibtorrent_version}.jar")
        } else if (os.isLinux()) {
            implementation files("${jlibtorrent_built_jars_path}/jlibtorrent-linux-${jlibtorrent_version}.jar")
        } else if (os.isMacOsX()) {
            // if it's arm mac
            if (is_arm64) {
                implementation files("${jlibtorrent_built_jars_path}/jlibtorrent-macosx-arm64-${jlibtorrent_version}.jar")
            }
            // intel
            else {
                implementation files("${jlibtorrent_built_jars_path}/jlibtorrent-macosx-x86_64-${jlibtorrent_version}.jar")
            }
        }
    }

    implementation fileTree(dir: 'lib/jars', include: ['*.jar'])
    // FlatLaf for dark theme support
    implementation 'com.formdev:flatlaf:3.6'
    testImplementation platform('org.junit:junit-bom:5.9.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

sourceSets {
    main {
        java {
            srcDir '../common/src/main/java'

            // TODO: move 'main' sources to src/main/java
            srcDir 'src'
            // Exclude JMH benchmarks from main compilation
            exclude 'jmh/**'
        }

        resources {
            srcDir 'resources'
            include '**/*.properties'
            include '**/*.png'
            include '**/*.gif'
            include '**/*.jpg'
            include '**/*.html'
            include '**/*.js'
            include '**/*.sh'
            include '**/*.dat'
            include '**/*.icc'
            exclude '**/*.DS_Store'
        }
    }

    test {
        java {
            srcDir '../common/src/test/java'

            // TODO: move 'test' sources to src/test/java
            srcDir 'tests/java'
        }
    }

    // JMH benchmarks source set (managed by JMH plugin)
    jmh {
        java {
            srcDir 'src/jmh/java'
        }
    }
}

jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    archiveFileName = 'frostwire.jar'

    exclude('META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF', 'META-INF/*.md')

    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }

    manifest {
        attributes 'Main-Class': 'com.limegroup.gnutella.gui.Main'
    }
}

// gettext tasks

tasks.register('gettextInit') {
    description = 'Loads and define the ant gettext related tasks'

    doLast {
        ant.taskdef(name: 'gettextExtract', classname: 'org.xnap.commons.ant.gettext.GettextExtractKeysTask', classpath: 'lib/jars/gettext-ant-tasks-0.9.7.jar')
        ant.taskdef(name: 'gettextMerge', classname: 'org.xnap.commons.ant.gettext.GettextMergeKeysTask', classpath: 'lib/jars/gettext-ant-tasks-0.9.7.jar')
        ant.taskdef(name: 'gettextGenerateDefault', classname: 'org.xnap.commons.ant.gettext.GenerateDefaultBundleTask', classpath: 'lib/jars/gettext-ant-tasks-0.9.7.jar')
        ant.taskdef(name: 'gettextDist', classname: 'org.xnap.commons.ant.gettext.GettextDistTask', classpath: 'lib/jars/gettext-ant-tasks-0.9.7.jar')
    }
}

tasks.register('gettextExtract') {
    description = 'Extracts message keys from the source code'

    doLast {
        println 'gettext extract...'
        ant.gettextExtract(keysFile: 'frostwire.pot',
                poDirectory: 'lib/messagebundles',
                keywords: '-kgetStringResource -kgetFormattedStringResource -ktrc -ktr -ktrn:1,2 -ktrl') {
            fileset(dir: "src") {
                include(name: '**/*.java')
            }
        }

        println 'gettext merge...'
        ant.gettextMerge(keysFile: "frostwire.pot", poDirectory: "lib/messagebundles")
    }
}

tasks.register('gettextBundle') {
    description = 'Rebuilds messages and bundles them into messages.jar'

    doLast {
        // clean up
        println 'gettext bundle...'
        println 'cleaning up...'
        delete 'lib/messagebundles/org'

        // generate default bundle for complete messages
        println 'gettext generate default'
        ant.gettextGenerateDefault(targetBundle: 'org.limewire.i18n.Messages',
                outputDirectory: 'lib/messagebundles',
                potfile: 'lib/messagebundles/frostwire.pot')

        // generate complete bundles
        println 'gettext dist (complete bundles)'
        ant.gettextDist(targetBundle: 'org.limewire.i18n.Messages',
                poDirectory: 'lib/messagebundles',
                outputDirectory: 'lib/messagebundles',
                percentage: '35')

        // generate the empty en.po file without percentage
        println 'gettext dist (empty en.po file without percentage)'
        ant.gettextDist(targetBundle: 'org.limewire.i18n.Messages',
                outputDirectory: 'lib/messagebundles') {
            fileset(dir: 'lib/messagebundles') {
                include(name: 'en.po')
            }
        }

        // jar them
        println 'jar it...'
        ant.jar(destfile: 'lib/jars/messages.jar',
                basedir: 'lib/messagebundles',
                includes: "org/**")

        // clean up
        println 'cleaning up...'
        delete 'lib/messagebundles/org'
    }
}

gettextExtract.dependsOn gettextInit
gettextBundle.dependsOn gettextInit


// TENTATIVE: WE MIGHT HAVE TO MAKE JSIG ALWAYS BE LOADED FOR MACOSX AND LINUX
// which means adding DYLD_INSERT_LIBRARIES AND LD_PRELOAD in the Mac and Linux launchers
// Arvid Norberg from libtorrent saysa:
// yes, libtorrent needs to handle certain signals during disk I/O operations.
// When using memory mapped files, disk writes are made by copying bytes into memory regions.
// There is no error code return path for memcopy, so the only way the kernel can signal errors (such as EPERM or ENOSPC)
// is via signals.
// When libtorrent is copying data into memory mapped files, it installs a temporary signal handle for the thread,
// and uninstalls it immediately afterwards.
// I believe the JVM also installs signal handlers for managing its garbage collection, so it sounds plausible
// that there's some clash.
tasks.register('runWithJSig', JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    mainClass = application.mainClass
    jvmArgs = application.applicationDefaultJvmArgs

    if (OperatingSystem.current().isMacOsX()) {
        def jsigPath = new File(System.getenv('JAVA_HOME'), 'lib/libjsig.dylib').absolutePath
        environment 'DYLD_INSERT_LIBRARIES', jsigPath
    } else if (OperatingSystem.current().isLinux()) {
        def jsigPath = new File(System.getenv('JAVA_HOME'), 'lib/libjsig.so').absolutePath
        environment 'LD_PRELOAD', jsigPath
    }
}

test {
    failFast = false
    enabled = gradle.startParameter.taskNames.contains("test")
    useJUnitPlatform()

    maxParallelForks = Runtime.getRuntime().availableProcessors()

    filter {
        //include specific method in any of the tests        
        includeTestsMatching "com.frostwire.util.*"
        includeTestsMatching "com.frostwire.mp4.*"
        includeTestsMatching "com.frostwire.platform.*"
        includeTestsMatching "com.frostwire.search.*"
        includeTestsMatching "com.frostwire.util.*"
        includeTestsMatching "com.frostwire.tests.*"        
    }

    testLogging {
        events "passed", "failed", "standardError"
        showExceptions = true
        showCauses = true
        showStackTraces = false
        exceptionFormat = "full"
    }
}

// JMH microbenchmark configuration
jmh {
    warmupIterations = 3
    iterations = 5
    fork = 2
    jvmArgs = ['-Xmx2g']
    resultFormat = 'JSON'
    resultsFile = project.file("${project.buildDir}/reports/jmh/results.json")
    // includes parameter can be overridden via: -Pjmh.includes=LoggerBench
}
