package d2datadict

import (
	"fmt"
	"log"

	"github.com/OpenDiablo2/OpenDiablo2/d2common"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)

// ItemStatCostRecord represents a row from itemstatcost.txt
// these records describe the stat values and costs (in shops) of items
// refer to https://d2mods.info/forum/kb/viewarticle?a=448
type ItemStatCostRecord struct {
	Name    string
	OpBase  string
	OpStat1 string
	OpStat2 string
	OpStat3 string

	MaxStat         string // if Direct true, will not exceed val of MaxStat
	DescStrPos      string // string used when val is positive
	DescStrNeg      string
	DescStr2        string // additional string used by some string funcs
	DescGroupStrPos string // string used when val is positive
	DescGroupStrNeg string
	DescGroupStr2   string // additional string used by some string funcs

	// Stuff
	// Stay far away from this column unless you really know what you're
	// doing and / or work for Blizzard, this column is used during bin-file
	// creation to generate a cache regulating the op-stat stuff and other
	// things, changing it can be futile, it works like the constants column
	// in MonUMod.txt and has no other relation to ItemStatCost.txt, the first
	// stat in the file simply must have this set or else you may break the
	// entire op stuff.
	Stuff string

	Index int

	// path_d2.mpq version doesnt have Ranged columne, excluding for now
	// Ranged  bool // game attempts to keep stat in a range, like strength >-1
	MinAccr int // minimum ranged value

	SendBits  int // #bits to send in stat update
	SendParam int // #bits to send in stat update

	SavedBits int // #bits allocated to the value in .d2s file

	SaveBits      int // #bits saved to .d2s files, max == 2^SaveBits-1
	SaveAdd       int // how large the negative range is (lowers max, as well)
	SaveParamBits int // #param bits are saved (safe value is 17)

	Encode d2enum.EncodingType // how the stat is encoded in .d2s files

	// these two fields control additional cost on items
	// cost * (1 + value * multiply / 1024)) + add (...)
	CostAdd      int
	CostMultiply int
	// CostDivide // exists in txt, but division hardcoded to 1024
	// if divide is used, could we do (?):
	// cost * (1 + value * multiply / divide)) + add (...)

	ValShift int // controls how stat is stored in .d2s
	// so that you can save `+1` instead of `+256`

	OperatorType d2enum.OperatorType
	OpParam      int

	EventID1     d2enum.ItemEventType
	EventID2     d2enum.ItemEventType
	EventFuncID1 d2enum.ItemEventFuncID
	EventFuncID2 d2enum.ItemEventFuncID

	DescPriority int // determines order when displayed
	DescFnID     int

	// Controls whenever and if so in what way the stat value is shown
	// 0 = doesn't show the value of the stat
	// 1 = shows the value of the stat infront of the description
	// 2 = shows the value of the stat after the description.
	DescVal int

	// when stats in the same group have the same value they use the
	// group func for desc (they need to be in the same affix)
	DescGroup       int
	DescGroupVal    int
	DescGroupFuncID int

	CallbackEnabled bool // whether callback fn is called if value changes
	Signed          bool // whether the stat is signed
	KeepZero        bool // prevent from going negative (assume only client side)
	UpdateAnimRate  bool // when altered, forces speed handler to adjust speed
	SendOther       bool // whether to send to other clients
	Saved           bool // whether this stat is saved in .d2s files
	SavedSigned     bool // whether the stat is saved as signed/unsigned
	Direct          bool // whether is temporary or permanent
	ItemSpecific    bool // prevents stacking with an existing stat on item
	// like when socketing a jewel

	DamageRelated bool // prevents stacking of stats while dual wielding
}

//nolint:gochecknoglobals // better for lookup
var itemStatCostRecordLookup = []string{
	"+%f %s",
	"%f%% %s",
	"%f %s",
	"+%f%% %s",
	"%f%% %s",
	"+%f %s %s",
	"%f%% %s %s",
	"+%f%% %s %s",
	"%f %s %s",
	"%f %s %s",
	"Repairs 1 Durability In %.0f Seconds",
	"+%f %s",
	"+%.0f to %s Skill Levels",
	"+%.0f to %s Skill Levels (%s Only)",
	"%.0f%% chance to cast %d %s on %s",
	"Level %d %s Aura When Equipped",
	"%f %s (Increases near %d)",
	"%f%% %s (Increases near %d)",
	"",
	"%f%% %s",
	"%f %s",
	"%f%% %s %s",
	"",
	"%f%% %s %s",
	"Level %.0f %s (%d/%d Charges)",
	"",
	"",
	"+%f to %s (%s Only)",
	"+%.0f to %s",
}

// DescString return a string based on the DescFnID
func (r *ItemStatCostRecord) DescString(a ...interface{}) string {
	if r.DescFnID < 0 || r.DescFnID > len(itemStatCostRecordLookup) {
		return ""
	}

	format := itemStatCostRecordLookup[r.DescFnID]

	return fmt.Sprintf(format, a...)
}

// DescGroupString return a string based on the DescGroupFuncID
func (r *ItemStatCostRecord) DescGroupString(a ...interface{}) string {
	if r.DescGroupFuncID < 0 || r.DescGroupFuncID > len(itemStatCostRecordLookup) {
		return ""
	}

	format := itemStatCostRecordLookup[r.DescGroupFuncID]

	return fmt.Sprintf(format, a...)
}

// ItemStatCosts stores all of the ItemStatCostRecords
//nolint:gochecknoglobals // Currently global by design
var ItemStatCosts map[string]*ItemStatCostRecord

// LoadItemStatCosts loads ItemStatCostRecord's from text
func LoadItemStatCosts(file []byte) {
	ItemStatCosts = make(map[string]*ItemStatCostRecord)

	d := d2common.LoadDataDictionary(file)
	for d.Next() {
		record := &ItemStatCostRecord{
			Name:  d.String("Stat"),
			Index: d.Number("ID"),

			Signed:   d.Number("Signed") > 0,
			KeepZero: d.Number("keepzero") > 0,

			// Ranged:  d.Number("Ranged") > 0,
			MinAccr: d.Number("MinAccr"),

			UpdateAnimRate: d.Number("UpdateAnimRate") > 0,

			SendOther: d.Number("Send Other") > 0,
			SendBits:  d.Number("Send Bits"),
			SendParam: d.Number("Send Param Bits"),

			Saved:         d.Number("CSvBits") > 0,
			SavedSigned:   d.Number("CSvSigned") > 0,
			SavedBits:     d.Number("CSvBits"),
			SaveBits:      d.Number("Save Bits"),
			SaveAdd:       d.Number("Save Add"),
			SaveParamBits: d.Number("Save Param Bits"),

			Encode: d2enum.EncodingType(d.Number("Encode")),

			CallbackEnabled: d.Number("fCallback") > 0,

			CostAdd:      d.Number("Add"),
			CostMultiply: d.Number("Multiply"),
			ValShift:     d.Number("ValShift"),

			OperatorType: d2enum.OperatorType(d.Number("op")),
			OpParam:      d.Number("op param"),
			OpBase:       d.String("op base"),
			OpStat1:      d.String("op stat1"),
			OpStat2:      d.String("op stat2"),
			OpStat3:      d.String("op stat3"),

			Direct:  d.Number("direct") > 0,
			MaxStat: d.String("maxstat"),

			ItemSpecific:  d.Number("itemspecific") > 0,
			DamageRelated: d.Number("damagerelated") > 0,

			EventID1:     d2enum.GetItemEventType(d.String("itemevent1")),
			EventID2:     d2enum.GetItemEventType(d.String("itemevent2")),
			EventFuncID1: d2enum.ItemEventFuncID(d.Number("itemeventfunc1")),
			EventFuncID2: d2enum.ItemEventFuncID(d.Number("itemeventfunc2")),

			DescPriority: d.Number("descpriority"),
			DescFnID:     d.Number("descfunc"),
			DescVal:      d.Number("descval"),
			DescStrPos:   d.String("descstrpos"),
			DescStrNeg:   d.String("descstrneg"),
			DescStr2:     d.String("descstr2"),

			DescGroup:       d.Number("dgrp"),
			DescGroupFuncID: d.Number("dgrpfunc"),

			DescGroupVal:    d.Number("dgrpval"),
			DescGroupStrPos: d.String("dgrpstrpos"),
			DescGroupStrNeg: d.String("dgrpstrneg"),
			DescGroupStr2:   d.String("dgrpstr2"),

			Stuff: d.String("stuff"),
		}
		ItemStatCosts[record.Name] = record
	}

	if d.Err != nil {
		panic(d.Err)
	}

	log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts))
}
