buildscript {
	repositories {
		jcenter()
		maven {
			name "Modmuss50 Repository"
			url 'https://maven.modmuss50.me'
		}
	}
	dependencies {
		classpath "net.fabricmc:weave:0.3.1.17+"
		classpath "net.fabricmc:stitch:0.1.2.49+"
		classpath "commons-io:commons-io:1.4"
		classpath "com.google.guava:guava:19.0"
		classpath 'de.undercouch:gradle-download-task:3.4.3'
		classpath 'net.fabricmc:tiny-remapper:+'
	}
}

plugins {
	id 'de.undercouch.download' version '3.4.3'
	id 'maven'
	id 'maven-publish'
}

def minecraft_version = "1.14.1 Pre-Release 2"

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

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


repositories {
	mavenCentral()
	maven {
		name "Modmuss50 Repository"
		url 'https://maven.modmuss50.me'
	}
}

configurations {
	enigmaRuntime {
		resolutionStrategy {
			cacheDynamicVersionsFor 0, "seconds"
			cacheChangingModulesFor 0, "seconds"
		}
	}
}

dependencies {
	enigmaRuntime "net.fabricmc:stitch:0.1.2.49+"
	enigmaRuntime "cuchaz:enigma:0.13.1.+:all"
}

def mappingsDir = file("mappings")
def cacheFilesMinecraft = file(".gradle/minecraft")
def tempDir = file(".gradle/temp")
def mergedFile = file("${minecraft_version}-merged.jar")
def intermediaryJar = file("${minecraft_version}-intermediary.jar")
def namedJar = file("${minecraft_version}-named.jar")
def versionFile = new File(cacheFilesMinecraft, "${minecraft_version}.json")
def clientJar = new File(cacheFilesMinecraft, "${minecraft_version}-client.jar")
def serverJar = new File(cacheFilesMinecraft, "${minecraft_version}-server.jar")
def libraries = new File(cacheFilesMinecraft, "${minecraft_version}-libraries")
def libs = new File("build/libs/")

import groovy.json.JsonSlurper
import org.apache.commons.io.FileUtils
import com.google.common.hash.Hashing
import com.google.common.io.Files
import net.fabricmc.stitch.commands.CommandMergeTiny
import net.fabricmc.stitch.commands.CommandProposeFieldNames
import net.fabricmc.stitch.commands.CommandRewriteIntermediary
import net.fabricmc.stitch.merge.JarMerger
import net.fabricmc.tinyremapper.OutputConsumerPath
import net.fabricmc.tinyremapper.TinyRemapper
import net.fabricmc.tinyremapper.TinyUtils
import net.fabricmc.weave.CommandFindMappingErrors
import net.fabricmc.weave.CommandTinyify
import groovy.io.FileType
import java.util.zip.GZIPOutputStream

boolean validateChecksum(File file, String checksum) {
	if (file != null) {
		def hash = Files.hash(file, Hashing.sha1())
		def builder = new StringBuilder()
		hash.asBytes().each {
			builder.append(Integer.toString((it & 0xFF) + 0x100, 16).substring(1))
		}
		return builder.toString().equals(checksum)
	}
	return false
}

task downloadVersionsManifest {
	//inputs.property "mc_ver", minecraft_version
	inputs.property "currenttime", new Date()
	def manifestFile = new File(cacheFilesMinecraft, "version_manifest.json")
	outputs.file(manifestFile)
	doLast {
		logger.lifecycle(":downloading minecraft versions manifest")
		FileUtils.copyURLToFile(new URL("https://launchermeta.mojang.com/mc/game/version_manifest.json"), manifestFile)
	}
}

def getManifestVersion(File manifestFile, String minecraft_version) {
	def manifest = manifestFile.exists() ? new JsonSlurper().parseText(FileUtils.readFileToString(manifestFile)) : null
	return manifest != null ? manifest.versions.stream().filter({it.id.equals(minecraft_version)}).findFirst() : java.util.Optional.empty()
}

task downloadWantedVersionManifest(dependsOn: downloadVersionsManifest) {
	def manifestFile = downloadVersionsManifest.outputs.files.singleFile
	def manifestVersion = getManifestVersion(manifestFile, minecraft_version)

	//have to grab the release time as there's a current timestamp on each element?!
	inputs.property "releaseTime", manifestVersion.isPresent() ? manifestVersion.get().releaseTime : -1

	outputs.file versionFile

	doLast {
		manifestVersion = getManifestVersion(manifestFile, minecraft_version) //nb need to re-read here in case it didn't exist before
		if (manifestVersion.isPresent() || versionFile.exists()) {

			if (manifestVersion.isPresent()) {
				FileUtils.copyURLToFile(new URL(manifestVersion.get().url), versionFile)
			}
		} else {
			throw new RuntimeException("No version data for Minecraft version ${minecraft_version}")
		}
	}

}

task downloadMcJars(dependsOn: downloadWantedVersionManifest) {

	inputs.files versionFile

	outputs.files(clientJar, serverJar)

	outputs.upToDateWhen {
		def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile))
		return clientJar.exists() && serverJar.exists() && validateChecksum(clientJar, version.downloads.client.sha1) && validateChecksum(serverJar, version.downloads.server.sha1)
	}

	doLast {
		if (!versionFile.exists()) {
			throw new RuntimeException("Can't download the jars without the ${versionFile.name} file!")
		}

		//reload in case it changed
		def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile))

		logger.lifecycle(":downloading minecraft jars")

		if (!clientJar.exists() || !validateChecksum(clientJar, version.downloads.client.sha1)) {
			logger.lifecycle(":downloading minecraft client")
			FileUtils.copyURLToFile(new URL(version.downloads.client.url), clientJar)
		}

		if (!serverJar.exists() || !validateChecksum(serverJar, version.downloads.server.sha1)) {
			logger.lifecycle(":downloading minecraft server")
			FileUtils.copyURLToFile(new URL(version.downloads.server.url), serverJar)
		}
	}
}

task mergeJars(dependsOn: downloadMcJars) {
	inputs.files downloadMcJars.outputs.files.files
	outputs.file(mergedFile)

	doLast {
		logger.lifecycle(":merging jars")
		def client = inputs.files.files.find {it.name.endsWith("-client.jar")}
		def server = inputs.files.files.find {it.name.endsWith("-server.jar")}
		def merged = mergedFile

		if(merged.exists()){
			return
		}

		def jarMerger = new JarMerger(client, server, merged)
		jarMerger.merge()
		jarMerger.close()
	}

}

task setupYarn(dependsOn: mergeJars) {

}

task yarn(dependsOn: setupYarn) {
	doLast {
		ant.java(
			classname: 'cuchaz.enigma.Main',
			classpath: configurations.enigmaRuntime.asPath,
			fork: true,
			spawn: true
		) {
			jvmarg(value: "-Xmx2048m")
			arg(value: mergedFile.getAbsolutePath())
			arg(value: mappingsDir.getAbsolutePath())
		}
	}
}

task buildEnigma(type: Zip) {
	from mappingsDir
	include "**/*"
	archiveName "yarn-enigma-${yarnVersion}.zip"
	destinationDir(file("build/libs"))
}

task checkMappings {
	inputs.dir mappingsDir
	doLast {
		logger.lifecycle(":checking mappings")

		String[] args = [
			mergedFile.getAbsolutePath(),
			mappingsDir.getAbsolutePath()
		]

		new CommandFindMappingErrors().run(args)
	}
}

task downloadIntermediary(type: Download){
    def url = "https://github.com/FabricMC/intermediary/raw/master/mappings/${minecraft_version}.tiny"
    src url.replaceAll(" ", "%20")
    dest new File(cacheFilesMinecraft, "${minecraft_version}-intermediary.tiny")
}

task patchIntermediary(dependsOn: ["mergeJars", "downloadIntermediary"], type: FileOutput) {
	def intermediaryTinyInput = downloadIntermediary.dest

	def outputFile = new File(cacheFilesMinecraft, "${minecraft_version}-intermediary-full.tiny")
	outputs.file(outputFile)
	fileOutput = outputFile

	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":patching intermediary")
		String[] args = [
			mergedFile.getAbsolutePath(),
			intermediaryTinyInput.getAbsolutePath(),
			outputFile.getAbsolutePath(),
			"--writeAll"
		]

		new CommandRewriteIntermediary().run(args)
	}
}

task buildYarnTiny(dependsOn: "mergeJars",type: FileOutput) {
	inputs.dir mappingsDir
	if (!libs.exists()) {
		libs.mkdirs()
	}

	def yarnTiny = new File(tempDir, "yarn-mappings.tiny")
	fileOutput = yarnTiny
	
	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":generating tiny mappings")

		String[] args = [
			mergedFile.getAbsolutePath(),
			mappingsDir.getAbsolutePath(),
			yarnTiny.getAbsolutePath(),
			"official",
			"named"
		]

		new CommandTinyify().run(args)
	}
}

task mergeTiny(dependsOn: ["buildYarnTiny", "patchIntermediary"], type: FileOutput) {
	def yarnTinyInput = buildYarnTiny.fileOutput
	def intermediaryTinyInput = patchIntermediary.fileOutput

	def resultMappings = new File(tempDir, "mappings.tiny")
	outputs.file(resultMappings)
	fileOutput = resultMappings

	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":merging yarn and intermediary")
		String[] args = [
			yarnTinyInput.getAbsolutePath(),
			intermediaryTinyInput.getAbsolutePath(),
			resultMappings.getAbsolutePath(),
			"intermediary",
			"official"
		]

		new CommandMergeTiny().run(args)
	}
}

task tinyJar(type: Jar, dependsOn: "mergeTiny") {
	outputs.upToDateWhen {false}
	archiveName = "yarn-${yarnVersion}.jar"
	destinationDir(file("build/libs"))
	classifier = ""
	from (mergeTiny.fileOutput) {
		rename { "mappings/mappings.tiny" }
	}
}

task compressTiny(dependsOn: ["tinyJar", "mergeTiny"], type: FileOutput){
	def outputFile = new File(libs, "yarn-tiny-${yarnVersion}.gz")
	outputs.file(outputFile)
	fileOutput = outputFile

	def inputFile = mergeTiny.fileOutput

	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":compressing tiny mappings")

		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()
		inputFile.delete()
	}
}

clean.doFirst {
	delete tempDir, cacheFilesMinecraft
}

tasks.build.dependsOn "compressTiny"
tasks.build.dependsOn "buildEnigma"
tasks.build.dependsOn "tinyJar"

task downloadMcLibs(dependsOn: downloadWantedVersionManifest) {
	inputs.files versionFile

	outputs.file(libraries)

	outputs.upToDateWhen {false}

	doLast {
		if (!versionFile.exists()) {
			throw new RuntimeException("Can't download the jars without the ${versionFile.name} file!")
		}

		def version = new JsonSlurper().parseText(FileUtils.readFileToString(versionFile))

		logger.lifecycle(":downloading minecraft libraries")

		if (!libraries.exists()) {
			libraries.mkdirs()
		}

		version.libraries.each {
			def downloadUrl = it.downloads.artifact.url

			download {
				src downloadUrl
				dest new File(libraries, downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1))
				overwrite false
			}
		}
	}
}

task mapIntermediaryJar(dependsOn: [downloadMcLibs, build]) {
	inputs.files downloadMcLibs.outputs.files.files
	outputs.file(intermediaryJar)

	//Force the task to always run
	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":mapping minecraft to intermdiary")
		def tinyInput = new File("build/libs/yarn-tiny-${yarnVersion}.gz")
		mapJar(intermediaryJar, mergedFile, tinyInput, libraries, "official", "intermediary")
	}
}

task mapNamedJar(dependsOn: mapIntermediaryJar) {
	inputs.files downloadMcLibs.outputs.files.files
	outputs.file(namedJar)

	//Force the task to always run
	outputs.upToDateWhen {false}

	doLast {
		logger.lifecycle(":mapping minecraft to named")

		def tinyInput = new File("build/libs/yarn-tiny-${yarnVersion}.gz")
		mapJar(namedJar, intermediaryJar, tinyInput, libraries, "intermediary", "named")
	}
}

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

			artifact (compressTiny.fileOutput) {
				classifier "tiny"
				builtBy compressTiny
			}
			artifact (buildEnigma) {
				classifier "enigma"
			}
			artifact (tinyJar)
		}
	}
	repositories {
		maven {
			url "http://mavenupload.modmuss50.me/"
			if (project.hasProperty('mavenPass')) {
				credentials {
					username 'buildslave'
					password project.getProperty('mavenPass')
				}
			}
		}
	}
}


void mapJar(File output, File input, File mappings, File libraries, String from, String to){
	if (output.exists()) {
		output.delete()
	}

	def remapper = TinyRemapper.newRemapper()
		.withMappings(TinyUtils.createTinyMappingProvider(mappings.toPath(), from, to))
		.renameInvalidLocals(true)
		.rebuildSourceFilenames(true)
		.build()
      
	try {
		def outputConsumer = new OutputConsumerPath(output.toPath())
		outputConsumer.addNonClassFiles(input.toPath())
		remapper.readInputs(input.toPath())

		libraries.eachFileRecurse(FileType.FILES) {file ->
			remapper.readClassPath(file.toPath())
		}
		remapper.apply(outputConsumer)
		outputConsumer.close()
		remapper.finish()
	} catch (Exception e) {
		remapper.finish()
		throw new RuntimeException("Failed to remap jar", e);
	}
}

class FileOutput extends DefaultTask {
	@OutputFile File fileOutput
}
