package channelserver

import (
	"encoding/binary"

	"erupe-ce/common/bfutil"
	"erupe-ce/common/stringsupport"
	cfg "erupe-ce/config"
	"erupe-ce/server/channelserver/compression/nullcomp"
)

// SavePointer identifies a section within the character save data blob.
type SavePointer int

const (
	pGender = iota
	pRP
	pHouseTier
	pHouseData
	pBookshelfData
	pGalleryData
	pToreData
	pGardenData
	pPlaytime
	pWeaponType
	pWeaponID
	pHR
	pGRP
	pKQF
	lBookshelfData
)

// CharacterSaveData holds a character's save data and its parsed fields.
type CharacterSaveData struct {
	CharID         uint32
	Name           string
	IsNewCharacter bool
	Mode           cfg.Mode
	Pointers       map[SavePointer]int

	Gender        bool
	RP            uint16
	HouseTier     []byte
	HouseData     []byte
	BookshelfData []byte
	GalleryData   []byte
	ToreData      []byte
	GardenData    []byte
	Playtime      uint32
	WeaponType    uint8
	WeaponID      uint16
	HR            uint16
	GR            uint16
	KQF           []byte

	compSave   []byte
	decompSave []byte
}

func getPointers(mode cfg.Mode) map[SavePointer]int {
	pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
	switch mode {
	case cfg.ZZ:
		pointers[pPlaytime] = 128356
		pointers[pWeaponID] = 128522
		pointers[pWeaponType] = 128789
		pointers[pHouseTier] = 129900
		pointers[pToreData] = 130228
		pointers[pHR] = 130550
		pointers[pGRP] = 130556
		pointers[pHouseData] = 130561
		pointers[pBookshelfData] = 139928
		pointers[pGalleryData] = 140064
		pointers[pGardenData] = 142424
		pointers[pRP] = 142614
		pointers[pKQF] = 146720
	case cfg.Z2, cfg.Z1, cfg.G101, cfg.G10, cfg.G91, cfg.G9, cfg.G81, cfg.G8,
		cfg.G7, cfg.G61, cfg.G6, cfg.G52, cfg.G51, cfg.G5, cfg.GG, cfg.G32, cfg.G31,
		cfg.G3, cfg.G2, cfg.G1:
		pointers[pPlaytime] = 92356
		pointers[pWeaponID] = 92522
		pointers[pWeaponType] = 92789
		pointers[pHouseTier] = 93900
		pointers[pToreData] = 94228
		pointers[pHR] = 94550
		pointers[pGRP] = 94556
		pointers[pHouseData] = 94561
		pointers[pBookshelfData] = 103928
		pointers[pGalleryData] = 104064
		pointers[pGardenData] = 106424
		pointers[pRP] = 106614
		pointers[pKQF] = 110720
	case cfg.F5, cfg.F4:
		pointers[pPlaytime] = 60356
		pointers[pWeaponID] = 60522
		pointers[pWeaponType] = 60789
		pointers[pHouseTier] = 61900
		pointers[pToreData] = 62228
		pointers[pHR] = 62550
		pointers[pHouseData] = 62561
		pointers[pBookshelfData] = 71928
		pointers[pGalleryData] = 72064
		pointers[pGardenData] = 74424
		pointers[pRP] = 74614
	case cfg.S6:
		pointers[pPlaytime] = 12356
		pointers[pWeaponID] = 12522
		pointers[pWeaponType] = 12789
		pointers[pHouseTier] = 13900
		pointers[pToreData] = 14228
		pointers[pHR] = 14550
		pointers[pHouseData] = 14561
		pointers[pBookshelfData] = 23928
		pointers[pGalleryData] = 24064
		pointers[pGardenData] = 26424
		pointers[pRP] = 26614
	}
	if mode == cfg.G5 {
		pointers[lBookshelfData] = 5548
	} else if mode <= cfg.GG {
		pointers[lBookshelfData] = 4520
	}
	return pointers
}

func (save *CharacterSaveData) Compress() error {
	var err error
	save.compSave, err = nullcomp.Compress(save.decompSave)
	if err != nil {
		return err
	}
	return nil
}

func (save *CharacterSaveData) Decompress() error {
	var err error
	save.decompSave, err = nullcomp.DecompressWithLimit(save.compSave, saveDataMaxDecompressedPayload)
	if err != nil {
		return err
	}
	return nil
}

// This will update the character save with the values stored in the save struct
func (save *CharacterSaveData) updateSaveDataWithStruct() {
	rpBytes := make([]byte, 2)
	binary.LittleEndian.PutUint16(rpBytes, save.RP)
	if save.Mode >= cfg.F4 {
		copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+saveFieldRP], rpBytes)
	}
	if save.Mode >= cfg.S6 {
		playtimeBytes := make([]byte, 4)
		binary.LittleEndian.PutUint32(playtimeBytes, save.Playtime)
		copy(save.decompSave[save.Pointers[pPlaytime]:save.Pointers[pPlaytime]+saveFieldPlaytime], playtimeBytes)
	}
	if save.Mode >= cfg.G10 {
		copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+saveFieldKQF], save.KQF)
	}
}

// This will update the save struct with the values stored in the character save
// Save data field sizes
const (
	saveFieldRP         = 2
	saveFieldHouseTier  = 5
	saveFieldHouseData  = 195
	saveFieldGallery    = 1748
	saveFieldTore       = 240
	saveFieldGarden     = 68
	saveFieldPlaytime   = 4
	saveFieldWeaponID   = 2
	saveFieldHR         = 2
	saveFieldGRP        = 4
	saveFieldKQF        = 8
	saveFieldNameOffset = 88
	saveFieldNameLen    = 12
)

func (save *CharacterSaveData) updateStructWithSaveData() {
	save.Name = stringsupport.SJISToUTF8Lossy(bfutil.UpToNull(save.decompSave[saveFieldNameOffset : saveFieldNameOffset+saveFieldNameLen]))
	if save.decompSave[save.Pointers[pGender]] == 1 {
		save.Gender = true
	} else {
		save.Gender = false
	}
	if !save.IsNewCharacter {
		if save.Mode >= cfg.S6 {
			save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+saveFieldRP])
			save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+saveFieldHouseTier]
			save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+saveFieldHouseData]
			save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]]
			save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+saveFieldGallery]
			save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+saveFieldTore]
			save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+saveFieldGarden]
			save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+saveFieldPlaytime])
			save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
			save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+saveFieldWeaponID])
			save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+saveFieldHR])
			if save.Mode >= cfg.G1 {
				if save.HR == uint16(999) {
					save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+saveFieldGRP])))
				}
			}
			if save.Mode >= cfg.G10 {
				save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+saveFieldKQF]
			}
		}
	}
}

// isHouseTierCorrupted checks whether the house tier field contains 0xFF
// bytes, which indicates an uninitialized or -1 value from the game client.
// The game uses small positive integers for theme IDs; 0xFF is never valid.
func (save *CharacterSaveData) isHouseTierCorrupted() bool {
	for _, b := range save.HouseTier {
		if b == 0xFF {
			return true
		}
	}
	return false
}

// restoreHouseTier replaces the current house tier with the given value in
// both the struct field and the underlying decompressed save blob, keeping
// them consistent for Save().
func (save *CharacterSaveData) restoreHouseTier(valid []byte) {
	save.HouseTier = make([]byte, len(valid))
	copy(save.HouseTier, valid)
	offset, ok := save.Pointers[pHouseTier]
	if ok && offset+len(valid) <= len(save.decompSave) {
		copy(save.decompSave[offset:offset+len(valid)], valid)
	}
}
