package main

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io"
	"os"
	"reflect"
	"regexp"
	"strings"

	"github.com/superp00t/etc/yo"
	"github.com/superp00t/gophercraft/datapack"
	"github.com/superp00t/gophercraft/datapack/text"
	"github.com/superp00t/gophercraft/format/dbc"
	"github.com/superp00t/gophercraft/format/mpq"
	"github.com/superp00t/gophercraft/gcore"
	"github.com/superp00t/gophercraft/vsn"
	"github.com/superp00t/gophercraft/worldserver/wdb"
)

func getDBC(name string) *dbc.DBC {
	bts, err := wizGamePool.ReadFile(name)
	if err != nil {
		yo.Fatal(name, err)
	}

	dd, err := dbc.Parse(wizGamePool.Build(), bts)
	if err != nil {
		yo.Fatal(name, err)
	}

	return dd
}

func copyBytes(to string, data []byte) {
	copyReaderToPack(to, bytes.NewReader(data))
}

func copyFile(to string, src string) {
	file, err := os.Open(src)
	if err != nil {
		wizQuit(err)
	}

	copyReaderToPack(to, file)
}

func copyReaderToPack(to string, src io.Reader) {
	file, err := wizPack.CreateHeader(&zip.FileHeader{
		Name:   to,
		Method: zip.Deflate,
	})
	if err != nil {
		wizQuit(err)
	}
	_, err = io.Copy(file, src)
	if err != nil {
		wizQuit(err)
	}
}

func writeDBC(to string, s reflect.Value) {
	file, err := wizPack.CreateHeader(&zip.FileHeader{
		Name:   to,
		Method: zip.Deflate,
	})
	if err != nil {
		wizQuit(err)
	}

	file.Write([]byte("// DO NOT EDIT OR DISTRIBUTE: generated by gcraft_wizard on " + datapack.Timestamp() + "\r\n"))

	for i := 0; i < s.Len(); i++ {
		enc := text.NewEncoder(file)

		if err := enc.Encode(s.Index(i).Interface()); err != nil {
			panic(err)
		}
	}
}

func extractDBC(path, to string, sli interface{}) {
	path = "DBFilesClient\\" + path + ".dbc" // TODO: replace extension depending on version

	db := getDBC(path)

	if err := db.ParseRecords(sli); err != nil {
		fmt.Println(path)
		panic(err)
	}

	slice := reflect.TypeOf(sli).Elem()

	switch slice.Kind() {
	case reflect.Slice:
		s := reflect.ValueOf(sli).Elem()

		writeDBC(to, s)
	}
}

func generateDatapack(dPack string) {
	var err error

	os.Remove(dPack)

	fileWriter, err := os.OpenFile(dPack, os.O_CREATE|os.O_RDWR, 0700)
	if err != nil {
		wizQuit(err)
	}

	wizPack = zip.NewWriter(fileWriter)

	// Open pack folder in temp
	manifest, err := text.Marshal(datapack.PackConfig{
		Name:        "Base",
		Description: "Gophercraft Base Content Pack",
		Author:      "DO NOT EDIT OR DISTRIBUTE: generated by gcraft_wizard on " + datapack.Timestamp(),
		Version:     gcore.Version,
		Depends: []string{
			"@gophercraft>=" + gcore.Version,
			"@gameVersion==" + fmt.Sprintf("%d", wizGamePool.Build())},
	})
	if err != nil {
		wizQuit(err)
	}

	copyBytes("Pack.txt", manifest)

	var cso []dbc.Ent_CharStartOutfit
	extractDBC("CharStartOutfit", "DB/DBC_CharStartOutfit.txt", &cso)

	var emotesText []dbc.Ent_EmotesText
	extractDBC("EmotesText", "DB/DBC_EmotesText.txt", &emotesText)

	var emotes []dbc.Ent_Emotes
	extractDBC("Emotes", "DB/DBC_Emotes.txt", &emotes)

	var races []dbc.Ent_ChrRaces
	extractDBC("ChrRaces", "DB/DBC_ChrRaces.txt", &races)

	var classes []dbc.Ent_ChrClasses
	extractDBC("ChrClasses", "DB/DBC_ChrClasses.txt", &classes)

	var areaTrigger []dbc.Ent_AreaTrigger
	extractDBC("AreaTrigger", "DB/DBC_AreaTrigger.txt", &areaTrigger)

	var creatureFamily []dbc.Ent_CreatureFamily
	extractDBC("CreatureFamily", "DB/DBC_CreatureFamily.txt", &creatureFamily)

	var spells []dbc.Ent_Spell
	extractDBC("Spell", "DB/DBC_Spell.txt", &spells)

	var zones []dbc.Ent_AreaTable
	extractDBC("AreaTable", "DB/DBC_AreaTable.txt", &zones)

	switch wizGamePool.Build() {
	case vsn.V1_12_1:
		var dmaps []dbc.Ent_Map_5875
		d := getDBC("DBFilesClient\\Map.dbc")
		if err := d.ParseRecords(&dmaps); err != nil {
			panic(err)
		}

		var maps []wdb.Map
		for _, dmap := range dmaps {
			var desc []string
			if dmap.Description0 != "" {
				desc = append(desc, dmap.Description0)
			}

			if dmap.Description1 != "" {
				desc = append(desc, dmap.Description1)
			}

			maps = append(maps, wdb.Map{
				ID:           dmap.ID,
				Directory:    dmap.Directory,
				Name:         dmap.Name,
				MapType:      dmap.MapType,
				InstanceType: dmap.InstanceType,
				MinLevel:     dmap.MinLevel,
				MaxLevel:     dmap.MaxLevel,
				MaxPlayers:   dmap.MaxPlayers,
				Descriptions: desc,
			})
		}

	case vsn.V2_4_3:
		var dmaps []dbc.Ent_Map_8606
		d := getDBC("DBFilesClient\\Map.dbc")
		if err := d.ParseRecords(&dmaps); err != nil {
			panic(err)
		}

		var maps []wdb.Map
		for _, dmap := range dmaps {
			var desc []string
			if dmap.Description0 != "" {
				desc = append(desc, dmap.Description0)
			}

			if dmap.Description1 != "" {
				desc = append(desc, dmap.Description1)
			}

			maps = append(maps, wdb.Map{
				ID:           dmap.ID,
				Directory:    dmap.Directory,
				Name:         dmap.Name,
				MapType:      dmap.MapType,
				InstanceType: dmap.InstanceType,
				// MinLevel:     dmap.MinLevel,
				// MaxLevel:     dmap.MaxLevel,
				// MaxPlayers:   dmap.MaxPlayers,
				Descriptions: desc,
			})
		}

		writeDBC("DB/Map.txt", reflect.ValueOf(maps))
	default:
		yo.Warn("No Map record type")
	}

	extractTerrain := false

	if extractTerrain {
		gFiles, err := wizGamePool.ListFiles()
		if err != nil {
			wizQuit(err)
		}

		match := regexp.MustCompile("^World.*(.adt|.wdt)$")

		for _, str := range gFiles {
			if match.MatchString(str) {
				toPath := strings.Join(strings.Split(str, "\\"), "/")
				fmt.Println("Extracting", toPath)
				data, err := wizGamePool.ReadFile(str)
				if err != nil {
					switch err.(type) {
					case mpq.FileWasDeletedError:
						continue
					case mpq.FileCorruptionError:
						cont := wizAsk(err, "Would you like to continue anyway?")
						if !cont {
							wizPack.Close()
							fileWriter.Close()
							os.Exit(0)
						}
						continue
					}
				}
				if err != nil {
					wizQuit(err)
				}
				copyBytes(toPath, data)
			}
		}
	}

	fmt.Println("Generating", dPack, "...")

	wizPack.Close()
	fileWriter.Close()
}
