package main

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"regexp"
	"strings"

	"github.com/pelletier/go-toml/v2"
)

// Fabric/Quilt Mod Json
type FabricModJson struct {
	ID      string `json:"id"`
	Version string `json:"version"`
	Name    string `json:"name"`
}
type QuiltModJson struct {
	QuiltLoader struct {
		ID       string `json:"id"`
		Version  string `json:"version"`
		Metadata struct {
			Name string `json:"name"`
		} `json:"metadata"`
	} `json:"quilt_loader"`
}
type ForgeModToml struct {
	Mods []struct {
		ModID       string `toml:"modId"`
		Version     string `toml:"version"`
		DisplayName string `toml:"displayName"`
	} `toml:"mods"`
}

var (
	fabricMods []FabricModJson
	quiltMods  []QuiltModJson
	forgeMods  []ForgeModToml

	cleanPattern = regexp.MustCompile(`[<>;:\"|?*]`)
)

// deduplicate mod jars based off the name of the jar
// if the jar name is the same, then we will only keep the one with the highest version
// or the latest modified date
func main() {
	// get list of jars in ./mods
	files, err := ioutil.ReadDir("./mods")
	if err != nil {
		log.Fatalln("[READ DIR]", err)
	}

	// for modjars,
	for _, file := range files {
		// filter out non-jars
		if !strings.HasSuffix(file.Name(), ".jar") {
			log.Println("[SKIP]", file.Name())
			continue
		}

		// read the mod.json file and return the name, version, and filename
		name, version, loader := readMod("./mods/" + file.Name())
		name = cleanUnicode(name)
		name = cleanPattern.ReplaceAllString(name, "")

		if loader != "" {
			err := os.Rename("./mods/"+file.Name(), "./mods/"+name+"-"+version+".jar")
			if err != nil {
				log.Fatalln("[RENAME MOD]", err)
			} else {
				log.Println("[RENAME MOD]", file.Name(), ">>", name+"-"+version+".jar")
			}
		} else {
			log.Println("Unsupported Loader", file.Name())
		}
	}
	for _, mod := range fabricMods {
		// check if there is a duplicate in the list
		for _, mod2 := range fabricMods {
			if mod.Name == mod2.Name && mod.Version != mod2.Version {
				// if there is a duplicate, delete the one with the lower semver version
				if mod.Version < mod2.Version {
					name := cleanPattern.ReplaceAllString(mod.Name, "")
					err := os.Remove("./mods/" + name + "-" + mod.Version + ".jar")
					if err != nil {
						log.Fatalln("[REMOVE MOD]", err)
					}
				}
			}
		}
	}
}

// read the mod.json file and return the name, version, and loader
func readMod(filename string) (string, string, string) {
	var fabricJson FabricModJson
	var quiltJson QuiltModJson
	var forgeMod ForgeModToml
	var loader string
	zipReader, err := zip.OpenReader(filename)
	if err != nil {
		log.Fatalln("[READ ZIP]", err)
	}
	defer zipReader.Close()
	for _, file := range zipReader.File {
		contents, _ := file.Open()

		// * Clean stray \n characters from the json
		var buf bytes.Buffer
		_, err := buf.ReadFrom(contents)
		if err != nil {
			log.Fatalln("[READ FROM]", err)
		}
		cleanedContents := strings.ReplaceAll(buf.String(), "\n", "")
		// * End Clean

		if file.Name == "fabric.mod.json" {
			loader = "fabric"
			fabricJsonErr := json.Unmarshal([]byte(cleanedContents), &fabricJson)
			if fabricJsonErr != nil {
				log.Println("[DECODE ERROR]", filename)
				log.Fatalln("[DECODE FABRIC MOD]", fabricJsonErr)
			}
			break
		}
		if file.Name == "quilt.mod.json" {
			loader = "quilt"
			quiltJsonErr := json.Unmarshal([]byte(cleanedContents), &quiltJson)
			if quiltJsonErr != nil {
				log.Println(filename)
				log.Println("[DECODE ERROR]", filename)
				log.Fatalln("[DECODE QUILT MOD]", quiltJsonErr)
			}
			break
		}
		if file.Name == "META-INF/mods.toml" {
			loader = "forge"
			forgeTomlErr := toml.NewDecoder(contents).Decode(&forgeMod)
			if forgeTomlErr != nil {
				log.Println("[DECODE ERROR]", filename)
				log.Fatalln("[DECODE FORGE MOD]", forgeTomlErr)
			}
			// NOTICE: We don't break here because we want to check if there is a fabric/quilt mod.json,
			// if there is, then we will use that instead of the forge mod.toml due to inconsistencies
			// between the forge mod.tomls
		}
	}

	if loader == "fabric" {
		fabricMods = append(fabricMods, fabricJson)
		return fabricJson.Name, fabricJson.Version, loader
	}
	if loader == "quilt" {
		quiltMods = append(quiltMods, quiltJson)
		return quiltJson.QuiltLoader.Metadata.Name, quiltJson.QuiltLoader.Version, loader
	}
	if loader == "forge" {
		if len(forgeMod.Mods) != 0 {
			forgeMods = append(forgeMods, forgeMod)
			log.Println("Forge Mod", filename)
			return forgeMod.Mods[0].DisplayName, forgeMod.Mods[0].Version, loader
		}
	}
	return "", "", ""
}
