import com.github.hexomod.macro.PreprocessorPlugin
import groovy.json.JsonOutput
import groovy.json.JsonSlurper

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath "com.github.hexomod:MacroPreprocessor:0.9"
    }
}

apply plugin: JavaPlugin
apply plugin: PreprocessorPlugin

static def parseVersion(version, undefined) {
    // if is a limiter, ignore.
    def value = 0
    if (version.startsWith("(") || version.endsWith(")")) {
        if (version.size() == 1) {
            return undefined;
        }
        value = -1
    }
    // remove all limiter if needs.
    version = version.replaceAll("[()\\[\\]]", "")
    def (major, minor, patch) = version.tokenize('-')[0].tokenize('.')
    return "${major}${minor.padLeft(2, '0')}${(patch ?: '').padLeft(2, '0')}" as int + value
}

static def parseVersionFromFile(file) {
    // only process the source file.
    if (file.name.matches(/.+\.(java|tj|jmc\d+|tj\d+)$/)) {
        // read version range in the file.
        def regex = ~/@Available\((\s*value\s*=)?\s*"(.+)"\s*\)/
        def results = file.getText("UTF-8") =~ regex
        if (results.size() != 0) {
            def range = results[0][2].split(",\\s*")
            if (range.size() == 1) {
                range = [range[0], range[0]]
            }
            return [parseVersion(range[0], 0), parseVersion(range[1], Integer.MAX_VALUE)]
        }
    }
    return null
}

// we needs convert the version to number, for example: 1.16.5 => 11605.
def minecraft_version = project.rootDir.getName()
var minecraft_version_number = parseVersion(minecraft_version, 0)

def targets = ["common", "forge", "fabric"]

def renameDataPack = { version, old_name, new_name, content_handler = null ->
    if (minecraft_version_number < version) {
        // swap when version missing.
        def tmp = new_name
        new_name = old_name
        old_name = tmp
    }
    sourceSets.forEach {
        // rename when file exists.
        it.resources.srcDirs.forEach {
            def data = new File(it, "data")
            if (data.exists()) {
                data.listFiles().each {
                    def e = new File(it, old_name)
                    if (e.exists()) {
                        e.renameTo(new File(it, new_name))
                    }
                }
            }
        }
        // replace contents when file exists.
        if (content_handler != null) {
            it.resources.matching { it.include "data/**/$new_name/" }.forEach(content_handler)
        }
    }
}

def transformRecipeData = { contents, version ->
    // version 1.12.2~1.21.1: { "item": "minecraft:item" }
    // version 1.12.2~1.21.1: { "tag": "minecraft:tag" }
    // version 1.12.2~1.21.1: [ { "item": "minecraft:item" }, { "tag": "#minecraft:tag" } ]
    // version 1.21.2+: "minecraft:item"
    // version 1.21.2+: "#minecraft:tag"
    // version 1.21.2+: [ "minecraft:item", "#minecraft:tag" ]
    def applyInputVersion = null
    applyInputVersion = { it ->
        //
        if (it instanceof List) {
            return it.collect(applyInputVersion)
        }
        if (it instanceof String) {
            if (it.startsWith("#")) {
                it = [tag: it.substring(1)]
            } else {
                it = [item: it]
            }
        }
        if (version >= 12102) {
            if (it.tag != null) {
                return "#${it.tag}"
            }
            return it.item
        }
        return it
    }
    // 1.12.2~1.20.1: { "item": "minecraft:item" }
    // 1.20.2+: { "id": "minecraft:item" }
    // 1.21.2+: "minecraft:item"
    def applyOutputVersion = { it ->
        var id = it.id ?: it.item
        it.remove("id")
        it.remove("item")
        if (version >= 12002) {
            return [id: id] + it
        } else {
            return [item: id] + it
        }
    }

    // "minecraft:crafting_shaped": [ "key:d", "result:r" ]
    // "minecraft:crafting_shapeless": [ "ingredients:a", "result:r" ]
    // "minecraft:campfire_cooking": [ "ingredient:e", "result:r" ]
    // "minecraft:stonecutting": [ "ingredient:e", "result:r" ]
    // "minecraft:blasting": [ "ingredient:e", "result:r" ]
    // "minecraft:smoking": [ "ingredient:e", "result:r" ]
    // "minecraft:smelting": [ "ingredient:e", "result:r" ]
    // "minecraft:smithing_trim": [ "base:e", "addition:e", "template:e", "result:r" ]
    // "minecraft:crafting_transmute": [ "input:e", "material:e", "result:s" ]
    def keys = ["key:d", "ingredients:a", "ingredient:e", "base:e", "addition:e", "template:e", "input:e", "material:e", "result:r"]
    def values = [
            e: { applyInputVersion(it) },
            a: { it.collect { v -> applyInputVersion(v) } },
            d: { it.collectEntries { k, v -> [(k): applyInputVersion(v)] } },
            r: { applyOutputVersion(it) }
    ]
    def json = new JsonSlurper().parseText(contents)
    for (key in keys) {
        var func = values[key.split(":")[1]]
        var value = json[key.split(":")[0]]
        if (value != null && func != null) {
            json[key.split(":")[0]] = func(value)
        }
    }
    return JsonOutput.prettyPrint(JsonOutput.toJson(json)).replaceAll(" {4}", "  ")
}

targets.forEach {
    sourceSets.register(it) {
        it.java.srcDirs("../../${it.name}/src/main/java")
        it.resources.srcDirs("../../${it.name}/src/main/resources")
        it.java.srcDirs("../library/${it.name}/src/main/java")
        it.resources.srcDirs("../library/${it.name}/src/main/resources")
        it.java.srcDirs("../${minecraft_version}/${it.name}/src/main/java")
        it.resources.srcDirs("../${minecraft_version}/${it.name}/src/main/resources")
    }
}

macroPreprocessorSettings {
    inPlace = true
    remove = true
    vars = [MC: minecraft_version_number]
    com.github.hexomod.macro.Preprocessor.SLASH_KEYWORDS.put("comment", "//#")
}

tasks.register("switchCoreSource") {
    sourceSets.forEach {
        def sourceSet = it
        def allFiles = [:]
        sourceSet.allSource.forEach {
            def range = parseVersionFromFile(it)
            if (range != null) {
                def path = it.path.replaceAll(/^(.+)\.(.+?)$/, /$1/)
                allFiles.computeIfAbsent(path, k -> []).add([range, it])
            }
        }
        allFiles.each {
            def path = it.key
            def apply = new HashMap<File, File>()
            def pending = new HashMap<File, File>()
            def versions = []
            it.value.sort().each {
                def range = it[0]
                if (minecraft_version_number < range[0] || minecraft_version_number > range[1]) {
                    // disable the file
                    def ver = "${range[0]}"
                    def suffix = ver.replaceAll(/(\d)(\d{2})(\d+)/, /$2/) // jmc18
                    if (!versions.add(suffix)) {
                        suffix = ver.replaceAll(/(\d)(\d{2})0*(\d*)/, /$2$3/) // jmc18x
                    }
                    pending.put(it[1] as File, new File("${path}.tj${suffix}"))
                } else {
                    // enable the file
                    pending.put(it[1] as File, new File("${path}.java"))
                }
            }
            // change to tmp
            pending.each {
                def tmp = new File(it.value.path + ".tmp")
                if (it.key != it.value && !tmp.exists()) {
                    it.key.renameTo(tmp)
                    apply.put(tmp, it.value)
                }
            }
            // change to final
            apply.each {
                if (it.key != it.value && !it.value.exists()) {
                    it.key.renameTo(it.value)
                    println "Apply ${it.value.toString().replaceAll("(/.+/main/java/)", "")}"
                }
            }
        }
    }
}

tasks.register("applyCoreSources") {
    dependsOn "macroPreprocessorJava"
    dependsOn "macroPreprocessorResource"
    dependsOn "applyCoreProperties"
    doLast {
        println "Minecraft Version: ${minecraft_version}(${minecraft_version_number})"
    }
}

tasks.register("applyCoreResources") {
    dependsOn "applyCoreSources"

    renameDataPack(12100, "tags/items", "tags/item")
    renameDataPack(12100, "tags/blocks", "tags/block")
    renameDataPack(12100, "loot_tables", "loot_table")
    renameDataPack(12100, "recipes", "recipe") {
        def oldValue = it.getText("UTF-8")
        def newValue = transformRecipeData(oldValue, minecraft_version_number)
        if (newValue != oldValue) {
            it.setText(newValue, "UTF-8")
        }
    }
}

tasks.register("applyCoreProperties") {
    def rootPath = rootDir.absolutePath
    fileTree(rootPath).matching { include "**/*.properties" }.forEach {
        def target = new File(rootDir, "../../${it.absolutePath.replace(rootPath, "")}")
        def contents = target.getText("UTF-8")
        def properties = new Properties()
        properties.load(it.newDataInputStream())
        properties.forEach { key, value ->
            contents = contents.replaceAll("\\b${key}\\b=[^#\\n]*", "${key}=${value}")
        }
        target.write(contents, "UTF-8")
    }
}

tasks.register("applyCoreConfigurations") {
    doLast {
        def target = "../../.idea/runConfigurations"
        def version_file = file("../../.idea/versions.txt");
        if (version_file.exists()) {
            def old_version = version_file.getText("UTF-8")
            ant.move(file: file(target), tofile: file(target + "-${old_version}"), failonerror: false, quiet: true)
        }
        ant.move(file: file(target + "-${minecraft_version}"), tofile: file(target), failonerror: false, quiet: true)
        // save the version info.
        version_file.parentFile.mkdirs()
        version_file.setText("${minecraft_version}", "UTF-8")
    }
}

tasks.register("applyRunConfigurations") {
    doLast {
        targets.forEach {
            def target = file("../../${it}/run/server.properties")
            if (target.exists()) {
                def contents = target.getText("UTF-8")
                target.setText(contents.replaceAll("(level-name)=(.+)", "\$1=world-${minecraft_version_number}"), "UTF-8")
            }
        }
    }
}

defaultTasks("switchCoreSource", "applyCoreSources", "applyCoreResources", "applyCoreConfigurations", "applyRunConfigurations")