package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)

type Fabric struct {
}

type FabricDefinition struct {
	Separator string
	Build     int
	Maven     string
	Version   string
	Stable    bool
}

type FabricLibrary struct {
	Name string
	Url  string
	Sha1 string
}

type FabricLibraries struct {
	Client []FabricLibrary
	Common []FabricLibrary
	Server []FabricLibrary
}

type FabricMeta struct {
	Version   int
	Libraries FabricLibraries
	MainClass map[string]string
}

type FabricVersion struct {
	Loader       FabricDefinition
	Intermediary FabricDefinition
	LauncherMeta FabricMeta
}

func (Fabric) GetFabricVersions(mcVersion string) ([]FabricVersion, error) {
	resp, err := http.Get("https://meta.fabricmc.net/v2/versions/loader/" + mcVersion)
	if err != nil {
		return []FabricVersion{}, fmt.Errorf("Unable to pull fabric version manifest: %s\n", err)
	}
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	versions := []FabricVersion{}
	json.Unmarshal(data, &versions)
	return versions, nil
}

func GetFabricMetadata(mcVersion string, fabricVersion string) (FabricVersion, error) {
	versions, err := Fabric{}.GetFabricVersions(mcVersion)
	if err != nil {
		return FabricVersion{}, fmt.Errorf("unable to download versions manifest: %e\n", err)
	}
	for _, version := range versions {
		if version.Loader.Version == fabricVersion {
			return version, nil
		}
	}
	return FabricVersion{}, fmt.Errorf("Unable to find requested version.\n")
}

func InstallLib(lib FabricLibrary, libDir string, a *App) {
	a.Status(fmt.Sprintf("Checking %s\n", lib.Name))
	path := filepath.Join(ProcessMavenPath(lib.Name), ProcessMavenFilename(lib.Name))
	if _, err := os.Stat(filepath.Join(libDir, path)); err == nil {
		f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_RDONLY, 0755)
		defer f.Close()
		data, _ := io.ReadAll(f)
		sha := sha1.Sum(data)
		if hex.EncodeToString(sha[:20]) == lib.Sha1 {
			return
		}
	}
	a.Status(fmt.Sprintf("Downloading %s\n", lib.Name))
	unixPath := strings.ReplaceAll(path, "\\", "/")
	resp, err := http.Get(lib.Url + unixPath)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	buff := new(bytes.Buffer)
	downloaded := 0
	for {
		count, err := io.CopyN(buff, resp.Body, BlockSize)
		if err == io.EOF {
			downloaded += int(count)
			break
		}
		if err != nil {
			fmt.Printf("Error Downloading libs: %e\n", err)
			return
		}
		downloaded += int(count)
		wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
	}
	os.MkdirAll(filepath.Join(libDir, ProcessMavenPath(lib.Name)), 0755)
	f, _ := os.OpenFile(filepath.Join(libDir, path), os.O_CREATE|os.O_RDWR, 0755)
	defer f.Close()
	io.Copy(f, buff)
	wruntime.EventsEmit(a.Ctx, "download_complete")
}

func InstallFabricLibs(mcVersion string, fabricVersion string, libDir string, a *App) {
	metadata, _ := GetFabricMetadata(mcVersion, fabricVersion)
	for _, lib := range metadata.LauncherMeta.Libraries.Client {
		InstallLib(lib, libDir, a)
	}
	for _, lib := range metadata.LauncherMeta.Libraries.Common {
		InstallLib(lib, libDir, a)
	}
	InstallLib(FabricLibrary{Name: metadata.Loader.Maven, Sha1: "", Url: "https://maven.fabricmc.net/"}, libDir, a)
	InstallLib(FabricLibrary{Name: metadata.Intermediary.Maven, Sha1: "", Url: "https://maven.fabricmc.net/"}, libDir, a)
}

func ProcessMavenPath(maven string) string {
	tokens := strings.Split(maven, ":")
	path := filepath.Join(strings.Split(tokens[0], ".")...)
	pack := tokens[1]
	version := tokens[2]
	path = filepath.Join(path, pack, version)
	return path
}

func ProcessMavenFilename(maven string) string {
	tokens := strings.Split(maven, ":")
	pack := tokens[1]
	version := tokens[2]
	return pack + "-" + version + ".jar"
}
