import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

// Text file extensions for LF normalization
val textExtensions = setOf(".kt", ".java", ".properties", ".json", ".toml", ".xml", ".txt", ".MF")
fun isTextFile(name: String) = textExtensions.any { name.endsWith(it) } || name.startsWith("META-INF/services/")

fun normalizeContent(name: String, content: ByteArray): ByteArray {
    if (!isTextFile(name)) return content
    return content.toString(Charsets.UTF_8)
        .replace("\r\n", "\n").replace("\r", "\n")
        .toByteArray(Charsets.UTF_8)
}

fun writeStoredEntry(output: ZipOutputStream, name: String, content: ByteArray) {
    val normalized = normalizeContent(name, content)
    val entry = ZipEntry(name).apply {
        time = 0
        size = normalized.size.toLong()
        compressedSize = normalized.size.toLong()
        crc = CRC32().apply { update(normalized) }.value
        method = ZipEntry.STORED
    }
    output.putNextEntry(entry)
    output.write(normalized)
    output.closeEntry()
}

plugins {
    id("java")
}

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
    }
}

repositories {
    mavenCentral()
    maven {
        name = "hytale-release"
        url = uri("https://maven.hytale.com/release")
    }
    maven {
        name = "hytale-pre-release"
        url = uri("https://maven.hytale.com/pre-release")
    }
}

val hytaleServer: Configuration by configurations.creating

val patchedJar = rootDir.resolve("repo/applications/HytaleServerPatched.jar")

dependencies {
    hytaleServer("com.hypixel.hytale:Server:2026.01.28-87d03be09")
}

configurations {
    compileOnly.get().extendsFrom(hytaleServer)
}

tasks.withType<JavaCompile> {
    options.compilerArgs.addAll(listOf(
        "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"
    ))
}

sourceSets {
    main {
        java {
            // Patch files go in runtime/hytale-server/src/
            // Use the same package structure as the original JAR
            // e.g., src/com/hypixel/hytale/server/SomeClass.java
            srcDir("src")
        }
    }
}

// Task to create the patched JAR by overlaying compiled classes on top of original
tasks.register("patchJar") {
    group = "coldfusion"
    description = "Creates a patched HytaleServer JAR with compiled modifications"

    dependsOn(tasks.compileJava)

    inputs.files(hytaleServer)
    inputs.files(tasks.compileJava.get().outputs.files).optional()
    outputs.file(patchedJar)

    doLast {
        val compiledClassesDir = tasks.compileJava.get().destinationDirectory.get().asFile
        val originalJar = hytaleServer.singleFile

        // Collect all compiled class files with their relative paths
        val patchedFiles = mutableMapOf<String, ByteArray>()
        if (compiledClassesDir.exists()) {
            compiledClassesDir.walkTopDown()
                .filter { it.isFile }
                .forEach { file ->
                    val relativePath = file.relativeTo(compiledClassesDir).path.replace(File.separatorChar, '/')
                    patchedFiles[relativePath] = file.readBytes()
                }
        }

        logger.lifecycle("Patching JAR with ${patchedFiles.size} compiled files")

        // Collect all entries: patched files override original
        val allEntries = mutableMapOf<String, ByteArray?>() // null = directory

        ZipFile(originalJar).use { original ->
            original.entries().asSequence().forEach { entry ->
                allEntries[entry.name] = if (entry.isDirectory) null else original.getInputStream(entry).readBytes()
            }
        }

        // Override with patched files
        patchedFiles.forEach { (path, content) ->
            logger.lifecycle("  Patching: $path")
            allEntries[path] = content
        }

        // Write sorted entries with STORED compression and LF normalization
        patchedJar.outputStream().buffered().let { ZipOutputStream(it) }.use { output ->
            output.setMethod(ZipOutputStream.STORED)

            allEntries.keys.sorted().forEach { name ->
                val content = allEntries[name]
                if (content == null) {
                    // Directory
                    val entry = ZipEntry(name).apply {
                        time = 0
                        size = 0
                        compressedSize = 0
                        crc = 0
                        method = ZipEntry.STORED
                    }
                    output.putNextEntry(entry)
                    output.closeEntry()
                } else {
                    writeStoredEntry(output, name, content)
                }
            }
        }

        logger.lifecycle("Created patched JAR: $patchedJar")
    }
}