package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

type Instance struct {
	InstanceName string
	ModpackId string
	ModpackVersion string
	MinecraftVersion string
	ForgeVersion string
	NeoForgeVersion string
	FabricVersion string
	QuiltVersion string
	JavaVersion int
}

type InstanceManager struct {
	instances []Instance
	app	*App
}

type mmcpack struct {
	Components []component
}

type component struct {
	Uid string
	Version string
}

func (i *InstanceManager)SearchInstances() {
	i.instances = []Instance{}
	dir, _ := os.UserConfigDir()
	dir = filepath.Join(dir, "FCLauncher", "instances")
	if _, err := os.Stat(dir); err != nil {
		return
	}
	subdirs, _ := os.ReadDir(dir)
	for _, d := range subdirs {
		if !d.IsDir() {
			continue
		}
		if _, err := os.Stat(filepath.Join(dir, d.Name(), "instance.json")); err != nil {
			continue
		}
		f, _ := os.OpenFile(filepath.Join(dir, d.Name(), "instance.json"), os.O_RDONLY, 0755)
		defer f.Close()
		buff := new(bytes.Buffer)
		io.Copy(buff, f)
		instance := Instance{}
		json.Unmarshal(buff.Bytes(), &instance)
		fmt.Printf("Found Instance: %+v\n", instance)
		i.instances = append(i.instances, instance)
	}
}

func (i *InstanceManager)checkJavaVersion(instance Instance){
	infoPath := filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "mmc-pack.json")
	f, _ := os.OpenFile(infoPath, os.O_RDONLY, 0755)
	defer f.Close()
	dataStr,_ := io.ReadAll(f)
	var data mmcpack
	json.Unmarshal(dataStr, &data)
	mc_version := "0.0"
	for _, comp := range data.Components {
		if comp.Uid == "net.minecraft" {
			mc_version = comp.Version
			break;
		}
	}
	fmt.Printf("MC Version: %s",mc_version)
	tokensStr := strings.Split(mc_version, ".")
	tokens := []int{0, 0, 0}
	tokens[0], _ = strconv.Atoi(tokensStr[0])
	tokens[1], _ = strconv.Atoi(tokensStr[1])
	if len(tokensStr) > 2 {
		tokens[2], _ = strconv.Atoi(tokensStr[2])
	}
	javaVer := 8
	if tokens[1] == 17 {
		javaVer = 17
	} else if tokens[1] == 18 || tokens[1] == 19 {
		javaVer = 17
	} else if tokens[1] > 19 {
		if tokens[1] == 20 && tokens[2] < 5 {
			javaVer = 17
		} else {
			javaVer = 21
		}
	}
	fmt.Printf("Req Java Version: %d",javaVer)
	if !i.app.Java.CheckJavaVer(javaVer) {
		i.app.Java.InstallJavaVer(javaVer)
	}
	
	confPath := filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "instance.cfg")
	f, _ = os.OpenFile(confPath, os.O_RDONLY, 0755)
	defer f.Close()
	buff := new(bytes.Buffer)
	io.Copy(buff, f)
	sc := bufio.NewScanner(buff)
	f, _ = os.OpenFile(confPath, os.O_CREATE|os.O_RDWR, 0755)
	plat := "lin"
	exe := "java"
	if runtime.GOOS == "windows" {
		plat = "win"
		exe = "Java.exe"
	}
	confDir, _ := os.UserConfigDir()
	found := false
	for sc.Scan() {
		line := sc.Text()
		if strings.HasPrefix(line, "JavaPath=") {
			line = fmt.Sprintf("JavaPath=%s/FCLauncher/java/java-%d-%s/bin/%s", strings.ReplaceAll(confDir, "\\", "/"), javaVer, plat, exe)
			found = true
		}
		f.WriteString(line+"\n")
	}
	if !found {
		line := fmt.Sprintf("JavaPath=%s/FCLauncher/java/java-%d-%s/bin/%s", strings.ReplaceAll(confDir, "\\", "/"), javaVer, plat, exe)
		f.WriteString(line+"\n")
		f.WriteString("OverrideJavaLocation=true\nOverrideJava=true\n")
	}
	f.Close()
}

func (i *InstanceManager)InstallModpack(modpack Modpack, instanceName string){
	i.app.Status(fmt.Sprintf("Installing %s", modpack.Name))
	version := modpack.Versions[len(modpack.Versions)-1]
	dname, _ := os.MkdirTemp("", "fclauncher-*")
	f, _ := os.OpenFile(filepath.Join(dname, instanceName+".mrpack"), os.O_CREATE|os.O_RDWR, 0755)
	defer f.Close()
	HttpDownload(modpack.Id + "/" + version.File, f, i.app.Ctx)
	i.app.PrismLauncher.ImportModpack(f.Name())
	instance := Instance{InstanceName: instanceName, ModpackVersion: version.Version, ModpackId: modpack.Id}
	i.instances = append(i.instances, instance)
	f, _ = os.OpenFile(filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
	defer f.Close()
	data, _ := json.Marshal(instance)
	f.Write(data)
	i.checkJavaVersion(instance)

}

func (i *InstanceManager)InstallVanilla(version string, instanceName string) {
	dir, _ := os.UserConfigDir()
	err := DownloadAssets(version, filepath.Join(dir, "FCLauncher", "assets"), *i.app)
	if err != nil {
		fmt.Printf("Unable to download assets: %s\n", err)
	} else {
		fmt.Printf("Assets Downloaded")
	}
	err = DownloadLibraries(version, filepath.Join(dir, "FCLauncher", "lib"), *i.app)
	if err != nil {
		fmt.Printf("Unable to download libs: %s\n", err)
	} else {
		fmt.Printf("Libs Downloaded")
	}
	InstallNatives(version, filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft", "natives"))
	DownloadLoggingConfig(version, filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft"))
	err = DownloadExecutable(version, filepath.Join(dir, "FCLauncher", "bin"), *i.app)
	if err != nil {
		fmt.Printf("Unable to download binaries: %s\n", err)
	} else {
		fmt.Printf("Binaries Downloaded")
	}
	metadata, err := GetVersionMetadata(version)
	if err != nil {
		fmt.Printf("unable to pull metadata: %s\n", err)	
	}

	err = os.MkdirAll(filepath.Join(dir, "FCLauncher", "instances", instanceName, "minecraft"), 0755)
	if err != nil {
		fmt.Printf("unable to create directory: %s\n", err)	
	}


	instance := Instance{InstanceName: instanceName, MinecraftVersion: version, JavaVersion: metadata.JavaVersion.MajorVersion}
	data, err := json.Marshal(instance)
	if err != nil {
		fmt.Printf("unable to marshal json data: %s\n", err)	
	}
	f, err := os.OpenFile(filepath.Join(dir, "FCLauncher", "instances", instanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
	if err != nil {
		fmt.Printf("unable to open file: %s\n", err)	
	}

	defer f.Close()
	_, err = f.Write(data)
	if err != nil {
		fmt.Printf("unable to write data: %s\n", err)	
	}
	i.instances = append(i.instances, instance)
	
	if !i.app.Java.CheckJavaVer(instance.JavaVersion) {
		i.app.Status(fmt.Sprintf("Installing Java Version %d", instance.JavaVersion))
		i.app.Java.InstallJavaVer(instance.JavaVersion)
	}

	return
}

func (i *InstanceManager)GetInstances() []Instance{
	return i.instances
}

func (i *InstanceManager)CheckUpdate(instance Instance){
	return
	i.app.Status("Checking for Updates")
	i.app.Modpacks.QuerryModpacks()
	pack := i.app.Modpacks.GetModpack(instance.ModpackId)
	if pack.Versions[len(pack.Versions)-1].Version == instance.ModpackVersion {
		return
	}
	i.app.Status(fmt.Sprintf("Updating %s", instance.InstanceName))
	version := pack.Versions[len(pack.Versions)-1]
	dname, _ := os.MkdirTemp("", "fclauncher-*")
	f, _ := os.OpenFile(filepath.Join(dname, instance.InstanceName+".mrpack"), os.O_CREATE|os.O_RDWR, 0755)
	defer f.Close()
	HttpDownload(pack.Id + "/" + version.File, f, i.app.Ctx)
	i.app.PrismLauncher.ImportModpack(f.Name())
	instance.ModpackVersion = version.Version
	f, _ = os.OpenFile(filepath.Join(i.app.PrismLauncher.GetInstanceDir(), instance.InstanceName, "instance.json"), os.O_CREATE|os.O_RDWR, 0755)
	defer f.Close()
	data, _ := json.Marshal(instance)
	f.Write(data)
	i.checkJavaVersion(instance)
	i.SearchInstances()
}

func (i *InstanceManager)LaunchInstance(instance string) {
	dir, err := os.UserConfigDir()
	if err != nil {
		fmt.Printf("unable to get config directory\n")
	}
	instanceObject := Instance{}
	found := false
	for _, inst := range i.instances {
		if inst.InstanceName == instance {
			instanceObject = inst
			found = true
			break
		}
	}
	if !found {
		fmt.Printf("unable to find instance %s\n", instance)
	}
	execName := "java"
	suffix := "lin"
	if runtime.GOOS == "windows" {
		execName = "Java.exe"
		suffix = "win"
	}
	dir = filepath.Join(dir, "FCLauncher")
	args, err := GetOfflineLaunchArgs(instanceObject.MinecraftVersion, filepath.Join(dir, "lib"), filepath.Join(dir, "bin"), filepath.Join(dir, "assets"), filepath.Join(dir, "instances", instance, "minecraft"), "Player")
	if err != nil {
		fmt.Printf("unable to get launch args: %s\n", err)
	}
	fmt.Printf("Args: %+v", args)
	child := exec.Command(filepath.Join(dir, "java", fmt.Sprintf("java-%d-%s", instanceObject.JavaVersion, suffix), "bin", execName), args...)
	data, err := child.CombinedOutput()
	fmt.Printf("Command Output: %s\n", data)
}

