buildscript {
    repositories { mavenCentral() }
    dependencies {
        // used at configuration-time for version info
        classpath 'org.eclipse.jgit:org.eclipse.jgit:5.7.+'
        classpath "javax.inject:javax.inject:1"
    }
}

plugins {
    id 'java'
    id 'antlr'
    id 'eclipse'
    id 'idea'
    id 'jacoco'
    id 'maven-publish'
    id 'com.github.kt3k.coveralls' version '2.12.2'
    id 'com.gradleup.shadow' version '9.2.2'
    id 'de.undercouch.download' version '5.6.0'
}


import de.undercouch.gradle.tasks.download.Download

import java.util.regex.Pattern

ext.mainClassName = "de.peeeq.wurstio.Main"

version = "1.9.0.0"

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
    }
}

def toolchainSvc = extensions.getByType(JavaToolchainService)
def j25Launcher = toolchainSvc.launcherFor(java.toolchain)

tasks.withType(JavaExec).configureEach {
    javaLauncher.set(j25Launcher)
    jvmArgs('-XX:+UnlockExperimentalVMOptions', '-XX:+UseCompactObjectHeaders')
}

tasks.withType(JavaCompile).configureEach { options.release = 25 }

jacoco {
    toolVersion = "0.8.13"
}

tasks.named("jacocoTestReport", JacocoReport) {
    dependsOn(tasks.named("test"))

    reports { xml.required.set(true) }

    def excluded = ['**/ast/**', '**/jassAst/**', '**/jassIm/**', '**/luaAst/**', '**/antlr/**']

    classDirectories.setFrom(
            files(classDirectories.files.collect { dir ->
                fileTree(dir: dir, exclude: excluded)
            })
    )
}

def genDir = "$projectDir/src-gen"

sourceSets {
    main {
        java.srcDir(genDir)
    }
}

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

configurations {
    // isolated classpath for the generator
    astgen
}

dependencies {
    implementation 'org.jetbrains:annotations:23.0.0'

    // Antlr
    antlr "org.antlr:antlr4:4.13.1"

    implementation "org.antlr:antlr4-runtime:4.13.1"

    // abstractsyntaxgen (available to IDE via compileOnly; used at runtime via astgen)
    compileOnly 'com.github.peterzeller:abstractsyntaxgen:623da1c60f'
    astgen 'com.github.peterzeller:abstractsyntaxgen:623da1c60f'


    // Tests
    testImplementation 'org.testng:testng:7.11.0'
    testImplementation 'org.hamcrest:hamcrest-all:1.3'

    // Libs
    implementation 'com.google.guava:guava:32.1.3-jre'
    implementation 'io.vavr:vavr:0.10.7'
    implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0'
    implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.2'
    implementation 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.1.0'
    implementation 'com.google.code.gson:gson:2.10.1'
    implementation 'commons-lang:commons-lang:2.6'
    implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
    implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
    implementation 'com.github.inwc3:jmpq3:e28f6999c0'
    implementation 'com.github.inwc3:wc3libs:ac41f780a5'
    implementation 'com.github.wurstscript:wurst-project-config:348fcd4ef5'
    implementation 'org.slf4j:slf4j-api:2.0.17'
    implementation 'ch.qos.logback:logback-classic:1.5.20'
    testImplementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
    testImplementation 'org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r'
    implementation 'it.unimi.dsi:fastutil:8.5.16'

    // Smallcheck
    testImplementation 'com.github.peterzeller:java-smallcheck:3f6a178ba7'
}

configurations {
    runtimeClasspath {
        exclude group: "org.antlr", module: "antlr4"
    }
}

configurations.configureEach {
    exclude group: "org.slf4j", module: "slf4j-log4j12"
    exclude group: "log4j", module: "log4j"
}

/** -------- AST generation (abstractsyntaxgen) -------- */

def parseqFiles = fileTree(dir: 'parserspec', include: '*.parseq')

def pkgPattern = Pattern.compile(/package\s+(\S+)\s*;/)

// resolve once at configuration time
def genDirFile = file(genDir)
def astgenCp = configurations.astgen
def pkgPatternLocal = pkgPattern

def perFileTasks = []

parseqFiles.files.each { File f ->
    // determine package at configuration time (same logic you had)
    String contents = f.getText('UTF-8')
    def m = pkgPatternLocal.matcher(contents)
    String pkg = m.find() ? m.group(1) : ""
    File targetDir = new File(genDirFile, pkg.replace('.', '/'))

    def t = tasks.register("genAst_${f.name.replaceAll(/[^A-Za-z0-9_]/, '_')}", JavaExec) {
        // incremental + cache correctness for the generator
        inputs.file(f)
        inputs.files(astgenCp).withPropertyName("astgenClasspath")
        outputs.dir(targetDir)

        classpath = astgenCp
        mainClass.set("asg.Main")
        args(f.absolutePath, targetDir.absolutePath)

        // optional: ensure target dir exists
        doFirst { targetDir.mkdirs() }
    }

    perFileTasks << t
}

tasks.register("genAst") {
    inputs.files(parseqFiles)
    outputs.dir(genDirFile)
    dependsOn(perFileTasks)
}



/** -------- Version info file generation -------- */

tasks.register('versionInfoFile') {
    description "Generates a file CompileTimeInfo.java with version number etc."

    def repoDir = new File(rootProject.projectDir, '..')

    def dir = new File("$genDir/de/peeeq/wurstscript/")
    def out = new File(dir, 'CompileTimeInfo.java')
    outputs.file(out)

    // capture at configuration time (no Task.project usage later)
    def versionString = project.version.toString()

    // Inputs so the task reruns when metadata changes:
    // - project version changes
    inputs.property("projectVersion", versionString)
    // - HEAD changes (branch updates)
    inputs.file(new File(repoDir, ".git/HEAD")).withPropertyName("gitHeadRef")
    // - branch ref changes (only for branch-based HEAD; harmless if missing)
    inputs.files(fileTree(new File(repoDir, ".git/refs"))).withPropertyName("gitRefs")
    // - tags move / new tags (for git describe --tags)
    inputs.files(fileTree(new File(repoDir, ".git/refs/tags"))).withPropertyName("gitTags")
    // - working tree dirtiness affects "--dirty"
    inputs.file(new File(repoDir, ".git/index")).optional().withPropertyName("gitIndex")

    doLast {
        def rev8 = ["git", "rev-parse", "--short=8", "HEAD"].execute(null, repoDir).text.trim()
        def revLong = ["git", "rev-parse", "HEAD"].execute(null, repoDir).text.trim()
        def tag = ["git", "describe", "--tags", "--always"].execute(null, repoDir).text.trim()

        def wurstVersion = "${versionString}-${tag}"

        dir.mkdirs()
        out.text = """
package de.peeeq.wurstscript;

public class CompileTimeInfo {
    public static final String revision="${rev8}";
    public static final String revisionLong="${revLong}";
    public static final String version="${wurstVersion}";
}
"""
    }
}



/** -------- Aggregate generation + wiring into compile -------- */

tasks.register('gen') {
    description "Generates code from various input files"
    dependsOn 'genAst', 'versionInfoFile', 'generateGrammarSource'
}
tasks.named('compileJava') { it.dependsOn('gen') }

/** -------- Tests -------- */

test {
    useTestNG()

    jvmArgs(
            '-Xmx2g',                         // local: give it room to finish and dump
            '-XX:MaxMetaspaceSize=256m',
            '-XX:+HeapDumpOnOutOfMemoryError',
            '-XX:+UnlockExperimentalVMOptions', // needed for UseCompactObjectHeaders until it graduates
            '-XX:+UseCompactObjectHeaders',    // Java 24+: 8-byte headers (vs 16) — big win for AST-heavy workloads
    )
}

/** -------- Clean generated sources -------- */
tasks.named('clean') {
    doFirst { delete genDir }
}

/** -------- Download helpers -------- */

tasks.register('downloadZipFile', Download) {
    src 'https://github.com/wurstscript/wurstStdlib2/archive/master.zip'
    dest new File(buildDir, 'stdlib2.zip')
}

tasks.register('downloadAndUnzipFile', Copy) {
    dependsOn 'downloadZipFile'
    from zipTree(tasks.named('downloadZipFile').get().dest)
    into new File(buildDir, '/deps/')
}

/** -------- Shadow / packaging -------- */

shadowJar {
    archiveBaseName.set('wurstscript')
    archiveClassifier.set('')
    archiveVersion.set('')
    manifest {
        attributes 'Main-Class': mainClassName
    }
    exclude 'OSGI-INF/**'
    exclude '**/DefaultListfile.txt'

    // exclude fastutil type-specialized packages (keep objects + refs)
    exclude 'it/unimi/dsi/fastutil/doubles/**'
    exclude 'it/unimi/dsi/fastutil/floats/**'
    exclude 'it/unimi/dsi/fastutil/longs/**'
    exclude 'it/unimi/dsi/fastutil/shorts/**'
    exclude 'it/unimi/dsi/fastutil/chars/**'
    exclude 'it/unimi/dsi/fastutil/bytes/**'
    exclude 'it/unimi/dsi/fastutil/booleans/**'
    exclude 'it/unimi/dsi/fastutil/io/**'

    // Within the kept objects/ints packages the compiler only uses the open-hash map/set, linked-open variants,
    // array list and FIFO queue for Object2Object, Object2Int, Reference2Object, Reference2Boolean and Int2Object.
    // Drop the rest (~4.6 MB): sorted (tree) maps, custom-hash maps, and the cross-primitive value families whose
    // value-type packages are already excluded above. If you start using another fastutil collection, remove the
    // matching exclude here.
    exclude 'it/unimi/dsi/fastutil/**/*AVLTreeMap*'
    exclude 'it/unimi/dsi/fastutil/**/*RBTreeMap*'
    exclude 'it/unimi/dsi/fastutil/**/*OpenCustomHashMap*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Double*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Float*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Long*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Short*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Byte*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Char*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Boolean*'
    exclude 'it/unimi/dsi/fastutil/objects/Object2Reference*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Double*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Float*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Long*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Short*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Byte*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Char*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Int*'
    exclude 'it/unimi/dsi/fastutil/objects/Reference2Reference*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Double*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Float*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Long*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Short*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Byte*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Char*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Boolean*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Int*'
    exclude 'it/unimi/dsi/fastutil/ints/Int2Reference*'

    // sqlite-jdbc bundles ~13.5 MB of native libs for 24 platforms. The compiler needs Java (64-bit only), so keep
    // only the desktop platforms it actually runs on (Windows/macOS/Linux on x86_64 + aarch64, incl. Apple Silicon)
    // and drop the rest (~10 MB saved). Keep this list in sync if sqlite-jdbc adds platforms.
    exclude 'org/sqlite/native/FreeBSD/**'
    exclude 'org/sqlite/native/Linux-Android/**'
    exclude 'org/sqlite/native/Linux-Musl/**'
    exclude 'org/sqlite/native/Linux/ppc64/**'
    exclude 'org/sqlite/native/Linux/riscv64/**'
    exclude 'org/sqlite/native/Linux/arm/**'
    exclude 'org/sqlite/native/Linux/armv6/**'
    exclude 'org/sqlite/native/Linux/armv7/**'
    exclude 'org/sqlite/native/Linux/x86/**'
    exclude 'org/sqlite/native/Windows/armv7/**'
    exclude 'org/sqlite/native/Windows/x86/**'
}

def fatJar = shadowJar.archiveFile.map { it.asFile }
def wurstUserDir = "${System.properties['user.home']}/.wurst"
def wurstCompilerDir = "${wurstUserDir}/wurst-compiler"

tasks.register('delete_legacy_userdir_compiler_jar', Delete) {
    delete "${wurstUserDir}/wurstscript.jar"
}

tasks.register('make_for_userdir', Copy) {
    dependsOn 'shadowJar', 'delete_legacy_userdir_compiler_jar'
    from fatJar
    into wurstCompilerDir
}

tasks.register('make_for_userdir_wurst_compiler') {
    dependsOn 'make_for_userdir'
}

tasks.register('make_for_wurstpack', Copy) {
    dependsOn 'shadowJar'
    from fatJar
    into '../Wurstpack/wurstscript/'
}

tasks.register('create_zip_wurstpack_complete', Zip) {
    dependsOn 'make_for_wurstpack'
    from '../Wurstpack'
    archiveFileName.set('wurstpack_complete.zip')
}

tasks.register('create_zip_wurstpack_compiler', Zip) {
    dependsOn 'make_for_wurstpack'
    from '../Wurstpack/wurstscript/'
    archiveFileName.set('wurstpack_compiler.zip')
}

tasks.register('create_zips') {
    dependsOn 'shadowJar', 'create_zip_wurstpack_complete', 'create_zip_wurstpack_compiler'

    doLast {
        ExecOperations execOps = project.services.get(ExecOperations)

        mkdir("../downloads/")

        copy {
            from fatJar.get()
            into '../downloads/'
        }
        copy {
            from '../Wurstpack'
            into '../downloads/Wurstpack/'
        }
        copy {
            from '../WurstSetup/build/libs/WurstSetup.jar'
            into '../downloads/'
        }

        mkdir("../Checksums/bin")
        execOps.javaexec {
            classpath = sourceSets.main.runtimeClasspath
            mainClass.set('de.peeeq.wurstio.Checksums')
            args("../downloads/Wurstpack/", "../downloads/wurstpack.md5")
        }
    }
}

// TODO add a modern documentation generator replacement for the removed legacy hotdoc pipeline.
tasks.named("coveralls") {
    notCompatibleWithConfigurationCache("coveralls plugin task uses Project at execution time")
}

/** -------- Apply deployment settings -------- */
apply from: 'deploy.gradle'
