package curseforge

import (
	"fmt"
	"github.com/aviddiviner/go-murmur"
	"github.com/packwiz/packwiz/core"
	"github.com/spf13/cobra"
	"os"
	"path/filepath"
	"strings"
)

// TODO: make all of this less bad and hardcoded

// detectCmd represents the detect command
var detectCmd = &cobra.Command{
	Use:   "detect",
	Short: "Detect .jar files in the mods folder (experimental)",
	Args:  cobra.NoArgs,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Loading modpack...")
		pack, err := core.LoadPack()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		index, err := pack.LoadIndex()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}

		// Walk files in the mods folder
		var hashes []uint32
		modPaths := make(map[uint32]string)
		err = filepath.Walk("mods", func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			if !strings.HasSuffix(path, ".jar") && !strings.HasSuffix(path, ".litemod") {
				// TODO: make this less bad
				return nil
			}
			fmt.Println("Hashing " + path)
			bytes, err := os.ReadFile(path)
			if err != nil {
				return err
			}
			hash := getByteArrayHash(bytes)
			hashes = append(hashes, hash)
			modPaths[hash] = path
			return nil
		})
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Printf("Found %d files, submitting...\n", len(hashes))

		res, err := cfDefaultClient.getFingerprintInfo(hashes)
		if err != nil {
			fmt.Println(err)
			return
		}

		fmt.Printf("Successfully matched %d files\n", len(res.ExactFingerprints))
		if len(res.PartialMatches) > 0 {
			fmt.Println("The following fingerprints were partial and I don't know what to do!!!")
			for _, v := range res.PartialMatches {
				fmt.Printf("%s (%d)", modPaths[v], v)
			}
		}
		if len(res.UnmatchedFingerprints) > 0 {
			fmt.Printf("Failed to match the following %d files:\n", len(res.UnmatchedFingerprints))
			for _, v := range res.UnmatchedFingerprints {
				fmt.Printf("%s (%d)\n", modPaths[v], v)
			}
		}

		fmt.Println("Retrieving metadata...")
		ids := make([]uint32, len(res.ExactMatches))
		for i, v := range res.ExactMatches {
			ids[i] = v.ID
		}
		modInfos, err := cfDefaultClient.getModInfoMultiple(ids)
		if err != nil {
			fmt.Printf("Failed to retrieve metadata: %v", err)
			os.Exit(1)
		}
		modInfosMap := make(map[uint32]modInfo)
		for _, v := range modInfos {
			modInfosMap[v.ID] = v
		}

		fmt.Println("Creating metadata files...")
		for _, v := range res.ExactMatches {
			err = createModFile(modInfosMap[v.ID], v.File, &index, false)
			if err != nil {
				fmt.Println(err)
				os.Exit(1)
			}

			path, ok := modPaths[v.File.Fingerprint]
			if ok {
				err = os.Remove(path)
				if err != nil {
					fmt.Println(err)
					os.Exit(1)
				}
			}
		}
		fmt.Println("Detection complete!")

		err = index.Refresh()
		if err != nil {
			fmt.Println(err)
			return
		}
		err = index.Write()
		if err != nil {
			fmt.Println(err)
			return
		}
		err = pack.UpdateIndexHash()
		if err != nil {
			fmt.Println(err)
			return
		}
		err = pack.Write()
		if err != nil {
			fmt.Println(err)
			return
		}
	},
}

func init() {
	curseforgeCmd.AddCommand(detectCmd)
}

func getByteArrayHash(bytes []byte) uint32 {
	return murmur.MurmurHash2(computeNormalizedArray(bytes), 1)
}

func computeNormalizedArray(bytes []byte) []byte {
	var newArray []byte
	for _, b := range bytes {
		if !isWhitespaceCharacter(b) {
			newArray = append(newArray, b)
		}
	}
	return newArray
}

func isWhitespaceCharacter(b byte) bool {
	return b == 9 || b == 10 || b == 13 || b == 32
}