buildscript {
	repositories {
		maven {
			name = "Fabric Repository"
			url = 'https://maven.fabricmc.net'
		}
		mavenCentral()
		mavenLocal()
	}
	dependencies {
		classpath "cuchaz:enigma-cli:${project.enigma_version}"
	}
}

plugins {
	id 'java' // for packages, javadoc
	id 'maven-publish'
	id "com.diffplug.spotless" version "7.2.1"
	id 'net.fabricmc.filament'
}

def minecraft_version = "25w41a"

filament {
	minecraftVersion = minecraft_version
}

def ENV = System.getenv()
// Fetch build number from Github Actions
def build_number = ENV.BUILD_NUMBER ?: "local"

def yarnVersion = "${minecraft_version}+build.$build_number"
version = yarnVersion

if (ENV.BRANCH_NAME) {
	def branch = ENV.BRANCH_NAME.substring(ENV.BRANCH_NAME.lastIndexOf('/') + 1)
	if (minecraft_version != branch) {
		throw new IllegalStateException("Branch name (${branch}) does not match the mc version (${minecraft_version})")
	}
}

repositories {
	mavenCentral {
		content {
			// Force LWJGL from Mojang's repo, as they have custom patched versions since 1.21-pre1
			excludeGroupByRegex("org.lwjgl")
		}
	}
	maven {
		name = "Fabric Repository"
		url = 'https://maven.fabricmc.net'
	}
	maven {
		name = "Mojang"
		url = 'https://libraries.minecraft.net/'
	}
	mavenLocal()
}

configurations {
	asm
	enigmaRuntime {
		extendsFrom asm
	}
	javadocClasspath
	decompileClasspath
}

def unpickMetaFile = file("unpick-definitions/unpick.json")

dependencies {
	enigmaRuntime "cuchaz:enigma-swing:${project.enigma_version}"
	enigmaRuntime "net.fabricmc:cfr:${project.cfr_version}"
	enigmaRuntime "org.vineflower:vineflower:${project.vineflower_version}"
	enigmaRuntime "net.fabricmc.unpick:unpick:${project.unpick_version}"
	enigmaRuntime "net.fabricmc.unpick:unpick-format-utils:${project.unpick_version}"
	enigmaRuntime("net.fabricmc:fabric-loom:${project.loom_version}") {
		transitive = false
	}
	enigmaRuntime "net.fabricmc:tiny-remapper:${project.tiny_remapper_version}"
	enigmaRuntime gradleApi()

	javadocClasspath "net.fabricmc:fabric-loader:${project.fabric_loader_version}"
	javadocClasspath "org.jetbrains:annotations:${project.jetbrains_annotations_version}"
	javadocClasspath "com.google.code.findbugs:jsr305:3.0.2" // for some other jsr annotations
	decompileClasspath "net.fabricmc:cfr:${project.cfr_version}"
	decompileClasspath "org.vineflower:vineflower:${project.vineflower_version}"
	asm "org.ow2.asm:asm:${project.asm_version}"
	asm "org.ow2.asm:asm-tree:${project.asm_version}"
	asm "org.ow2.asm:asm-commons:${project.asm_version}"
	asm "org.ow2.asm:asm-util:${project.asm_version}"
}

def yarnGroup = "yarn"
def buildMappingGroup = "mapping build"
def mapJarGroup = "jar mapping"
def mappingsDir = file("mappings")
def tempDir = file("build/temp/yarn")
def cacheFilesMinecraft = new File(tempDir, "minecraft")
def libs = new File("build/libs/")

def minecraftLibraries = configurations.minecraftLibraries

import cuchaz.enigma.command.CheckMappingsCommand
import net.fabricmc.filament.task.DownloadTask
import net.fabricmc.filament.task.MapJarTask
import net.fabricmc.filament.task.annotations.CheckAnnotationsTask
import net.fabricmc.filament.task.annotations.RemapAnnotationsTask
import net.fabricmc.filament.task.unpick.CheckUnpickDefinitionsTask
import net.fabricmc.filament.task.unpick.UnpickJarTask
import net.fabricmc.filament.task.MappingPoetTask
import net.fabricmc.filament.task.base.WithFileInput
import net.fabricmc.filament.task.base.WithFileOutput
import net.fabricmc.filament.task.enigma.MapSpecializedMethodsTask
import net.fabricmc.filament.task.mappingio.CompleteMappingsTask
import net.fabricmc.filament.task.mappingio.ConvertMappingsTask
import net.fabricmc.filament.task.mappingio.FormatMappingsTask
import net.fabricmc.filament.task.mappingio.MergeMappingsTask
import net.fabricmc.filament.task.mappingio.CheckMappingsTask
import net.fabricmc.filament.task.mappingio.CheckMergedMappingsTask
import net.fabricmc.filament.nameproposal.MappingNameCompleter
import net.fabricmc.filament.nameproposal.NameProposalConfig
import net.fabricmc.mappingio.format.MappingFormat
import org.gradle.work.DisableCachingByDefault
import com.diffplug.spotless.LineEnding
import groovy.xml.XmlSlurper

import java.util.zip.GZIPOutputStream

tasks.register('downloadIntermediary', DownloadTask) {
	group = buildMappingGroup
	url = "https://github.com/FabricMC/intermediary/raw/master/mappings/${minecraft_version}.tiny"
	output = new File(cacheFilesMinecraft, "${minecraft_version}-intermediary.tiny")
}

tasks.register('mapIntermediaryJar', MapJarTask) {
	dependsOn downloadIntermediary, mergeMinecraftJars
	group = mapJarGroup
	output = layout.buildDirectory.file("${minecraft_version}-intermediary.jar")
	input = mergeMinecraftJars.output
	mappings = downloadIntermediary.output
	from = 'official'
	to = 'intermediary'
}

tasks.register('mapServerIntermediaryJar', MapJarTask) {
	dependsOn downloadIntermediary, extractBundledServer
	group = mapJarGroup
	output = layout.buildDirectory.file("${minecraft_version}-server-intermediary.jar")
	input = extractBundledServer.output
	mappings = downloadIntermediary.output
	from = 'official'
	to = 'intermediary'
}

tasks.register('yarn', EnigmaTask) {
	dependsOn mapIntermediaryJar
	group = yarnGroup
	jar = mapIntermediaryJar.output
	libraries.from minecraftLibraries
	mappings = mappingsDir
	unpickDefinitions = file('unpick-definitions')
	annotations = file('annotations.json')
}

tasks.register('yarnCommon', EnigmaTask) {
	group = yarnGroup
	jar = mapServerIntermediaryJar.output
	libraries.from minecraftLibraries
	mappings = mappingsDir
	unpickDefinitions = file('unpick-definitions')
	annotations = file('annotations.json')
}

def checkPackages = tasks.register('checkPackages') {
	group = buildMappingGroup
	inputs.dir mappingsDir
	inputs.file mapIntermediaryJar.output

	def intermediaryJarPath = mapIntermediaryJar.outputFile.absolutePath

	doLast {
		String[] args = [
			intermediaryJarPath,
			mappingsDir.getAbsolutePath()
		]

		try {
			new CheckMappingsCommand().run(args)
		} catch (IllegalStateException ignored) {
			// just print, don't fail the task
		}
	}
}

tasks.register('mapSpecializedMethods', MapSpecializedMethodsTask) {
	intermediaryJarFile = mapIntermediaryJar.output
	mappings = mappingsDir
	output = new File(tempDir, "yarn-specialized-mappings-v2.tiny")

	inputMappingsFormat = "enigma"
	outputMappingsFormat = "tinyv2:intermediary:named"
}

tasks.register('completeMappings', CompleteMappingsTask) {
	input = mapSpecializedMethods.output
	output = new File(tempDir, "yarn-mappings-v2.tiny")
	outputFormat = MappingFormat.TINY_2_FILE
}

tasks.register('convertToV1', ConvertMappingsTask) {
	input = mapSpecializedMethods.output
	output = new File(tempDir, "yarn-mappings.tiny")
	outputFormat = MappingFormat.TINY_FILE
}

tasks.register('mergeTiny', MergeMappingsTask) {
	group = buildMappingGroup
	output = new File(tempDir, "mappings.tiny")
	mappingInputs.from downloadIntermediary.output
	mappingInputs.from convertToV1.output
	outputFormat = MappingFormat.TINY_FILE
}

// Disable the default jar task
jar {
	enabled = false
}

tasks.register('tinyJar', Jar) {
	dependsOn mergeTiny
	group = buildMappingGroup
	archiveFileName = "yarn-${yarnVersion}.jar"
	destinationDirectory.set(file("build/libs"))
	archiveClassifier = ""

	from(mergeTiny.output) {
		rename { "mappings/mappings.tiny" }
	}
	manifest {
		attributes("Minecraft-Version-Id": minecraft_version)
	}
}

tasks.register('compressTiny', FileInputOutput) {
	dependsOn tinyJar, mergeTiny
	group = buildMappingGroup

	input = mergeTiny.output
	output = new File(libs, "yarn-tiny-${yarnVersion}.gz")

	doLast {
		def buffer = new byte[1024]
		def fileOutputStream = new FileOutputStream(outputFile)
		def outputStream = new GZIPOutputStream(fileOutputStream)
		def fileInputStream = new FileInputStream(inputFile)

		def length
		while ((length = fileInputStream.read(buffer)) > 0) {
			outputStream.write(buffer, 0, length)
		}

		fileInputStream.close()
		outputStream.finish()
		outputStream.close()
	}
}

clean.doFirst {
	delete tempDir, cacheFilesMinecraft
}

sourceSets {
	packageDocs // package info files
}

tasks.build.dependsOn "compressTiny","tinyJar","v2UnmergedYarnJar", "v2MergedYarnJar", "javadocJar"

combineUnpickDefinitions {
	group = 'unpick'
	input = file('unpick-definitions')
	output = new File(tempDir, 'definitions.unpick')
}

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

tasks.withType(JavaCompile).configureEach {
	it.options.encoding = "UTF-8"
	it.options.release = 21
}

tasks.withType(AbstractArchiveTask).configureEach {
	it.preserveFileTimestamps = false
	it.reproducibleFileOrder = true
}

spotless {
	lineEndings = LineEnding.UNIX

	java {
		licenseHeaderFile(rootProject.file("HEADER"))
	}
}

tasks.register('insertNameProposalMappings', FileInputOutput) {
	dependsOn mapIntermediaryJar, downloadIntermediary
	group = buildMappingGroup
	input = completeMappings.output
	output = new File(tempDir, "unmerged-named-v2-with-enum.tiny")

	inputs.file mapIntermediaryJar.output
	inputs.file downloadIntermediary.output

	def intermediaryJarPath = mapIntermediaryJar.outputPath
	def intermediaryMappingsPath = downloadIntermediary.outputPath

	doLast {
		MappingNameCompleter.completeNames(
			intermediaryJarPath,
			inputPath,
			intermediaryMappingsPath,
			outputPath,
			NameProposalConfig.DEFAULT
		)
	}
}

remapUnpickDefinitionsIntermediary {
	group = 'unpick'
	output = new File(tempDir, 'intermediary-definitions.unpick')
	classpath.from(mapIntermediaryJar, minecraftLibraries)
	mappings = insertNameProposalMappings.output
}

tasks.register('unpickIntermediaryJar', UnpickJarTask) {
	group = 'unpick'
	input = mapIntermediaryJar.output
	output = layout.buildDirectory.file("${minecraft_version}-intermediary-unpicked.jar")
	unpickDefinition = remapUnpickDefinitionsIntermediary.output
	classpath.from minecraftLibraries
}

tasks.register('mergeV2', MergeMappingsTask) {
	group = buildMappingGroup
	output = new File(tempDir, "merged-v2.tiny")
	mappingInputs.from downloadIntermediary.output
	mappingInputs.from insertNameProposalMappings.output
	outputFormat = MappingFormat.TINY_2_FILE
}

tasks.register('v2UnmergedYarnJar', Jar) {
	dependsOn insertNameProposalMappings, combineUnpickDefinitions
	def mappings = insertNameProposalMappings.output
	group = "mapping build"
	archiveFileName = "yarn-${yarnVersion}-v2.jar"

	from(file(mappings)) {
		rename mappings.get().asFile.name, "mappings/mappings.tiny"
	}
	from(remapUnpickDefinitionsIntermediary.output) {
		rename remapUnpickDefinitionsIntermediary.output.get().asFile.name, "extras/definitions.unpick"
	}
	from(file(unpickMetaFile)) {
		expand version: project.version
		rename unpickMetaFile.name, "extras/unpick.json"
	}
	from(remapAnnotationsIntermediary.output) {
		rename remapAnnotationsIntermediary.output.get().asFile.name, "extras/annotations.json"
	}
	destinationDirectory.set(file("build/libs"))
	manifest {
		attributes("Minecraft-Version-Id": minecraft_version)
	}
}

tasks.register('v2MergedYarnJar', Jar) {
	dependsOn mergeV2
	def mappings = mergeV2.outputFile
	group = "mapping build"
	archiveFileName = "yarn-${yarnVersion}-mergedv2.jar"

	from(file(mappings)) {
		rename mappings.name, "mappings/mappings.tiny"
	}
	from(remapUnpickDefinitionsIntermediary.output) {
		rename remapUnpickDefinitionsIntermediary.output.get().asFile.name, "extras/definitions.unpick"
	}
	from(file(unpickMetaFile)) {
		expand version: project.version
		rename unpickMetaFile.name, "extras/unpick.json"
	}
	from(remapAnnotationsIntermediary.output) {
		rename remapAnnotationsIntermediary.output.get().asFile.name, "extras/annotations.json"
	}
	destinationDirectory.set(file("build/libs"))
	manifest {
		attributes("Minecraft-Version-Id": minecraft_version)
	}
}

tasks.register('mapNamedJar', MapJarTask) {
	dependsOn mergeV2, unpickIntermediaryJar
	group = mapJarGroup
	output = layout.buildDirectory.file("${minecraft_version}-named.jar")
	input = unpickIntermediaryJar.output
	mappings = mergeV2.output
	from = 'intermediary'
	to = 'named'
	classMappings = [
		"javax/annotation/Nullable"            : "org/jetbrains/annotations/Nullable",
		"javax/annotation/Nonnull"             : "org/jetbrains/annotations/NotNull",
		"javax/annotation/concurrent/Immutable": "org/jetbrains/annotations/Unmodifiable"
	]
}

// Test that we can remap back to intermediary.
tasks.register('remapIntermediaryJar', MapJarTask) {
	group = mapJarGroup
	output = layout.buildDirectory.file("${minecraft_version}-remapped.jar")
	input = mapNamedJar.output
	mappings = mergeV2.output
	from = 'named'
	to = 'intermediary'
}

check.dependsOn remapIntermediaryJar

tasks.register('mapNamedNoUnpickJar', MapJarTask) {
	dependsOn mergeV2
	group = mapJarGroup
	output = layout.buildDirectory.file("${minecraft_version}-named-no-unpick.jar")
	input = mapIntermediaryJar.output
	mappings = mergeV2.output
	from = 'intermediary'
	to = 'named'
}

tasks.register('checkUnpickDefinitions', CheckUnpickDefinitionsTask) {
	group = 'unpick'
	input = file('unpick-definitions')
	classpath.from(mapNamedNoUnpickJar.output, minecraftLibraries)
}

check.dependsOn checkUnpickDefinitions
unpickIntermediaryJar.mustRunAfter checkUnpickDefinitions // the check will give nicer error messages (not in intermediary)

tasks.register('remapAnnotationsIntermediary', RemapAnnotationsTask) {
	group = 'annotations'
	input = file('annotations.json')
	classpath.from(mapNamedNoUnpickJar)
	mappings = insertNameProposalMappings.output
	sourceNamespace = 'named'
	targetNamespace = 'intermediary'
	output = new File(tempDir, 'annotations-intermediary.json')
}

tasks.register('checkAnnotations', CheckAnnotationsTask) {
	group = 'annotations'
	input = file('annotations.json')
	targetJars.from(mapNamedNoUnpickJar)
	classpath.from(minecraftLibraries)
}

check.dependsOn checkAnnotations

def mcLibsDir = file('build/tmp/mclibs')

// Task to copy all the mc libs into a single directory.
tasks.register('syncDependencies', Sync) {
	from minecraftLibraries
	into mcLibsDir
}

def fakeSourceDir = file(".gradle/temp/fakeSource")
tasks.register('genFakeSource', MappingPoetTask) {
	group = "javadoc generation"
	mappings = mergeV2.output
	minecraftJar = mapNamedJar.output
	libraries.from(minecraftLibraries)
	output = fakeSourceDir
}

def decompileOutput = layout.buildDirectory.file("namedSrc")

tasks.register("deleteDecompilerOutput", Delete) {
	delete decompileOutput
}

tasks.register('decompileCFR', JavaExec) {
	dependsOn deleteDecompilerOutput
	dependsOn mapNamedJar
	mainClass = "org.benf.cfr.reader.Main"
	classpath.from configurations.decompileClasspath
	classpath.from minecraftLibraries
	args mapNamedJar.outputFile.getAbsolutePath(), "--outputdir", decompileOutput.get().asFile.absolutePath
}

tasks.register('decompileVineflower', JavaExec) {
	dependsOn deleteDecompilerOutput
	dependsOn mapNamedJar
	mainClass = "org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler"
	classpath.from configurations.decompileClasspath
	minecraftLibraries.files.forEach { file ->
		args "--add-external=${file.absolutePath}"
	}
	args "--folder", mapNamedJar.outputFile.getAbsolutePath(), decompileOutput.get().asFile.absolutePath
	jvmArgs "-Xmx4G"
}

tasks.register("formatMappings", FormatMappingsTask) {
	input = file("mappings")
}

javadoc {
	dependsOn genFakeSource
	group = "javadoc generation"

	failOnError = false
	maxMemory = '2G'

	// verbose = true // enable to debug
	options {
		// verbose() // enable to debug
		source = "21"
		encoding = 'UTF-8'
		charSet = 'UTF-8'
		memberLevel = JavadocMemberLevel.PRIVATE
		splitIndex true
		tags(
				'apiNote:a:API Note:',
				'implSpec:a:Implementation Requirements:',
				'implNote:a:Implementation Note:'
		)
		taglets "net.fabricmc.filament.mappingpoet.jd.MappingTaglet"
		// taglet path, header, extra stylesheet settings deferred
		it.use()

		addBooleanOption "-allow-script-in-comments", true
		addBooleanOption "-ignore-source-errors", true
		links(
				'https://guava.dev/releases/32.1.2-jre/api/docs/',
				'https://www.javadoc.io/doc/com.google.code.gson/gson/2.10.1/',
				'https://logging.apache.org/log4j/2.x/javadoc/log4j-api/',
				'https://www.slf4j.org/apidocs/',
				"https://javadoc.io/doc/org.jetbrains/annotations/${project.jetbrains_annotations_version}/",
				'https://javadoc.lwjgl.org/',
				'https://fastutil.di.unimi.it/docs/',
				'https://javadoc.scijava.org/JOML/',
				'https://netty.io/4.1/api/',
				'https://www.oshi.ooo/oshi-core-java11/apidocs/',
				'https://java-native-access.github.io/jna/5.13.0/javadoc/',
				'https://unicode-org.github.io/icu-docs/apidoc/released/icu4j/',
				'https://jopt-simple.github.io/jopt-simple/apidocs/',
				'https://solutions.weblite.ca/java-objective-c-bridge/docs/',
				'https://commons.apache.org/proper/commons-logging/apidocs/',
				'https://commons.apache.org/proper/commons-lang/javadocs/api-release/',
				'https://commons.apache.org/proper/commons-io/apidocs/',
				'https://commons.apache.org/proper/commons-codec/archives/1.15/apidocs/',
				'https://commons.apache.org/proper/commons-compress/apidocs/',
				'https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/',
				"https://maven.fabricmc.net/docs/fabric-loader-${project.fabric_loader_version}/",
				"https://docs.oracle.com/en/java/javase/21/docs/api/"
		)
		// https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#additional-options-provided-by-the-standard-doclet
		addBooleanOption 'Xdoclint:html', true
		addBooleanOption 'Xdoclint:syntax', true
		addBooleanOption 'Xdoclint:reference', true
		addBooleanOption 'Xdoclint:accessibility', true
	}
	source fileTree(fakeSourceDir) + sourceSets.packageDocs.allJava
	classpath = configurations.javadocClasspath.plus minecraftLibraries

	def filamentCodeSource = MappingPoetTask.class.getProtectionDomain().getCodeSource()
	def filamentJarFile = new File(filamentCodeSource.getLocation().getFile())

	options {
		tagletPath = [filamentJarFile]
		header file("gradle/javadoc/header.txt").text.trim() // cannot include line breaks
		addFileOption "-add-stylesheet", file("gradle/javadoc/forms.css")
	}
}

tasks.register('javadocJar', Jar) {
	dependsOn javadoc
	group = "javadoc generation"

	from javadoc.destinationDir
	from file("gradle/javadoc/copy_on_click.js")

	archiveVersion.set yarnVersion
	archiveClassifier = 'javadoc'
}

generatePackageInfoMappings {
	inputJar = mapIntermediaryJar.output
	packageName = "net/minecraft/unused/packageinfo/"
	outputDir = file("mappings/net/minecraft/unused/packageinfo")
}

javadocLint {
	group = "javadoc generation"
	mappingDirectory = file("mappings")
}

def checkMappings = tasks.register("checkMappings", CheckMappingsTask) {
	input = mappingsDir
}

def checkMergedMappings = tasks.register("checkMergedMappings", CheckMergedMappingsTask) {
	input = mergeV2.output
}

check.dependsOn checkPackages
check.dependsOn checkMappings
check.dependsOn checkMergedMappings
check.dependsOn javadocLint

publishing {
	publications {
		register("maven", MavenPublication) {
			groupId = 'net.fabricmc'
			artifactId = "yarn"
			version = yarnVersion

			artifact(compressTiny.output) {
				classifier = "tiny"
				builtBy compressTiny
			}
			artifact(tinyJar)
			artifact(v2UnmergedYarnJar) {
				classifier = "v2"
			}
			artifact(v2MergedYarnJar) {
				classifier = "mergedv2"
			}
			artifact javadocJar
		}

	}
	repositories {
		if (ENV.MAVEN_URL) {
			maven {
				url = ENV.MAVEN_URL
				credentials {
					username = ENV.MAVEN_USERNAME
					password = ENV.MAVEN_PASSWORD
				}
			}
		}
	}
}

// A task to ensure that the version being released has not already been released.
tasks.register('checkVersion') {
	doFirst {
		def xml = new URL("https://maven.fabricmc.net/net/fabricmc/yarn/maven-metadata.xml").text
		def metadata = new XmlSlurper().parseText(xml)
		def versions = metadata.versioning.versions.version*.text();
		if (versions.contains(version)) {
			throw new RuntimeException("${version} has already been released!")
		}
	}
}

publish.mustRunAfter checkVersion


abstract class FileInputOutput extends DefaultTask implements WithFileInput, WithFileOutput {
}

@DisableCachingByDefault
abstract class EnigmaTask extends JavaExec {
	@InputFile
	abstract RegularFileProperty getJar()

	@Input
	abstract Property<File> getMappings()

	@InputFiles
	abstract ConfigurableFileCollection getLibraries()

	@InputDirectory
	abstract DirectoryProperty getUnpickDefinitions()

	@InputFile
	abstract RegularFileProperty getAnnotations()

	EnigmaTask() {
		def filamentCodeSource = MappingPoetTask.class.getProtectionDomain().getCodeSource()
		def filamentJarFile = new File(filamentCodeSource.getLocation().getFile())
		def runtimeClasspath = project.files()
		runtimeClasspath.from(project.configurations.enigmaRuntime)
		runtimeClasspath.from(filamentJarFile)

		classpath = runtimeClasspath
		mainClass.set('cuchaz.enigma.gui.Main')
		jvmArgs "-Xmx2048m"
	}

	@TaskAction
	void exec() {
		args '-jar'
		args jar.get().asFile.absolutePath
		args '-mappings'
		args mappings.get().absolutePath
		args '-profile'
		args 'enigma_profile.json'

		for (def library in getLibraries().files) {
			args '-library', library.absolutePath
		}

		systemProperties 'unpick.directory': getUnpickDefinitions().get().asFile.absolutePath
		systemProperties 'annotations.file': getAnnotations().get().asFile.absolutePath

		super.exec()
	}
}
