package calculator

import (
	"errors"
	"galaxy/calc"
	"galaxy/client/widget/numeric"
	"galaxy/util"
	"slices"
	"strconv"
	"sync"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/lang"
	"fyne.io/fyne/v2/widget"
)

type CalculatorOpt func(*Calculator)
type ShipClass struct {
	Name     string
	Drive    float64
	Armament uint
	Weapons  float64
	Shields  float64
	Cargo    float64
}
type ShipClassFn func(string, float64, uint, float64, float64, float64)

type Calculator struct {
	CanvasObject fyne.CanvasObject

	playerDrivesTech  float64
	playerWeaponsTech float64
	playerShieldsTech float64
	playerCargoTech   float64

	shipDriveEntry    *numeric.FloatEntry
	shipWeaponsEntry  *numeric.FloatEntry
	shipArmamentEntry *numeric.IntEntry
	shipShieldsEntry  *numeric.FloatEntry
	shipCargoEntry    *numeric.FloatEntry

	playerDrivesTechEntry  *numeric.FloatEntry
	playerWeaponsTechEntry *numeric.FloatEntry
	playerShieldsTechEntry *numeric.FloatEntry
	playerCargoTechEntry   *numeric.FloatEntry

	drivesTechOverride  *widget.Check
	weaponsTechOverride *widget.Check
	shieldsTechOverride *widget.Check
	cargoTechOverride   *widget.Check

	massEntry      *numeric.FloatEntry
	speedEntry     *numeric.FloatEntry
	attackEntry    *numeric.FloatEntry
	defenseEntry   *numeric.FloatEntry
	cargoLoadEntry *numeric.FloatEntry
	planetMatEntry *numeric.FloatEntry

	massOverride      *widget.Check
	speedOverride     *widget.Check
	attackOverride    *widget.Check
	defenseOverride   *widget.Check
	cargoLoadMaximize *widget.Check
	planetMatOverride *widget.Check

	planetLabel          *widget.Label
	planetMassProdLabel  *widget.Label
	planetShipsProdLabel *widget.Label
	planetContainer      fyne.CanvasObject
	planetProdContainer  fyne.CanvasObject

	shipSelector     *widget.SelectEntry
	shipCreateButton *widget.Button

	onCreateHandler ShipClassFn
	loader          ShipClassFn
	knownClasses    []ShipClass

	validateMu sync.RWMutex

	l, mat, res float64

	Valid bool
}

func WithPlayerDrives(v float64) CalculatorOpt {
	return func(c *Calculator) { c.playerDrivesTech = v }
}

func WithPlayerWeapons(v float64) CalculatorOpt {
	return func(c *Calculator) { c.playerWeaponsTech = v }
}

func WithPlayerShields(v float64) CalculatorOpt {
	return func(c *Calculator) { c.playerShieldsTech = v }
}

func WithPlayerCargo(v float64) CalculatorOpt {
	return func(c *Calculator) { c.playerCargoTech = v }
}

func WithCreateHandler(f ShipClassFn) CalculatorOpt {
	return func(c *Calculator) { c.onCreateHandler = f }
}

func NewCaclulator(opts ...CalculatorOpt) *Calculator {
	c := &Calculator{}

	c.shipCreateButton = widget.NewButton(lang.L("ship.action.create"), c.onCreateShipClassButton)
	c.shipCreateButton.Disable()
	c.loader = c.LoadShipClass

	c.planetMatEntry = numeric.NewFloatEntry(10, c.onPlanetMatChange)
	c.planetMatOverride = widget.NewCheck("", c.overridePlanetMat)
	c.planetMatOverride.Disable()
	c.planetLabel = widget.NewLabel("")
	c.planetMassProdLabel = bareLabel("")
	c.planetShipsProdLabel = bareLabel("")
	c.planetProdContainer = container.NewHBox(
		label(lang.L("planet.prod.mass")+":"),
		fixedLabel(c.planetMassProdLabel, 80),
		label(lang.L("planet.prod.ships")+":"),
		fixedLabel(c.planetShipsProdLabel, 80),
	)
	c.planetProdContainer.Hide()

	c.planetContainer = container.NewVBox(
		widget.NewSeparator(),
		container.NewHBox(c.planetLabel),
		rowForItem(lang.L("planet.mat")+":", floatEntry(c.planetMatEntry, 100), c.planetMatOverride),
		c.planetProdContainer,
	)
	c.planetContainer.Hide()

	c.shipSelector = widget.NewSelectEntry(nil)
	c.shipSelector.OnChanged = c.onShipSelectorChange

	c.shipDriveEntry = numeric.NewFloatEntry(7, c.onShipDriveChange)
	c.shipWeaponsEntry = numeric.NewFloatEntry(7, c.onShipWeaponsChange)
	c.shipArmamentEntry = numeric.NewIntEntry(7, c.onShipArmamentChange)
	c.shipShieldsEntry = numeric.NewFloatEntry(7, c.onShipShieldsChange)
	c.shipCargoEntry = numeric.NewFloatEntry(7, c.onShipCargoChange)

	c.playerDrivesTechEntry = numeric.NewFloatEntry(7, c.onDrivesTechChange)
	c.playerWeaponsTechEntry = numeric.NewFloatEntry(7, c.onWeaponsTechChange)
	c.playerShieldsTechEntry = numeric.NewFloatEntry(7, c.onShieldsTechChange)
	c.playerCargoTechEntry = numeric.NewFloatEntry(7, c.onCargoTechChange)

	c.massEntry = numeric.NewFloatEntry(7, c.onMassChange)
	c.speedEntry = numeric.NewFloatEntry(7, c.onSpeedChange)
	c.attackEntry = numeric.NewFloatEntry(7, c.onAttackChange)
	c.defenseEntry = numeric.NewFloatEntry(7, c.onDefenseChange)
	c.cargoLoadEntry = numeric.NewFloatEntry(7, c.onCargoLoadChange)

	c.drivesTechOverride = widget.NewCheck("", c.overrideDrivesTech)
	c.drivesTechOverride.Disable()
	c.weaponsTechOverride = widget.NewCheck("", c.overrideWeaponsTech)
	c.weaponsTechOverride.Disable()
	c.shieldsTechOverride = widget.NewCheck("", c.overrideShieldsTech)
	c.shieldsTechOverride.Disable()
	c.cargoTechOverride = widget.NewCheck("", c.overrideCargoTech)
	c.cargoTechOverride.Disable()

	c.massOverride = widget.NewCheck("", c.overrideMass)
	c.massOverride.Disable()
	c.speedOverride = widget.NewCheck("", c.overrideSpeed)
	c.speedOverride.Disable()
	c.attackOverride = widget.NewCheck("", c.overrideAttack)
	c.attackOverride.Disable()
	c.defenseOverride = widget.NewCheck("", c.overrideDefense)
	c.defenseOverride.Disable()
	c.cargoLoadMaximize = widget.NewCheck(lang.L("label.max"), c.maximizeCargoLoad)
	c.cargoLoadMaximize.SetChecked(true)

	createShip := container.NewBorder(
		nil,                // top
		nil,                // bottom
		nil,                // left
		c.shipCreateButton, // right
		c.shipSelector,     // center
	)

	c.CanvasObject = container.NewVBox(
		container.NewPadded(createShip),
		widget.NewSeparator(),
		rowForTech(lang.L("tech.d")+":",
			c.shipDriveEntry, floatEntry(c.playerDrivesTechEntry, 80), c.drivesTechOverride),
		rowForWeapons(lang.L("tech.w")+":",
			c.shipArmamentEntry, c.shipWeaponsEntry, floatEntry(c.playerWeaponsTechEntry, 80), c.weaponsTechOverride),
		rowForTech(lang.L("tech.s")+":",
			c.shipShieldsEntry, floatEntry(c.playerShieldsTechEntry, 80), c.shieldsTechOverride),
		rowForTech(lang.L("tech.c")+":",
			c.shipCargoEntry, floatEntry(c.playerCargoTechEntry, 80), c.cargoTechOverride),
		widget.NewSeparator(),
		rowForItem(lang.L("ship.load")+":",
			floatEntry(c.cargoLoadEntry, 80), c.cargoLoadMaximize),
		rowForItem(lang.L("ship.mass")+":",
			floatEntry(c.massEntry, 80), c.massOverride),
		rowForItem(lang.L("ship.speed")+":",
			floatEntry(c.speedEntry, 80), c.speedOverride),
		rowForItem(lang.L("ship.attack")+":",
			floatEntry(c.attackEntry, 80), c.attackOverride),
		rowForItem(lang.L("ship.defense")+":",
			floatEntry(c.defenseEntry, 80), c.defenseOverride),
		c.planetContainer,
	)

	c.Init(opts...)

	return c
}

func (c *Calculator) Init(opts ...CalculatorOpt) {
	for i := range opts {
		opts[i](c)
	}

	c.playerDrivesTechEntry.SetOrigin(c.playerDrivesTech)
	c.playerWeaponsTechEntry.SetOrigin(c.playerWeaponsTech)
	c.playerShieldsTechEntry.SetOrigin(c.playerShieldsTech)
	c.playerCargoTechEntry.SetOrigin(c.playerCargoTech)

	c.CanvasObject.Show()
}

func (c *Calculator) Refresh() {
	c.validate()
	c.CanvasObject.Refresh()
}

func (c *Calculator) RegisterClasses(shipClass ...ShipClass) {
	c.knownClasses = shipClass
	names := make([]string, len(c.knownClasses))
	for i := range c.knownClasses {
		names[i] = c.knownClasses[i].Name
	}
	slices.Sort(names)
	c.shipSelector = widget.NewSelectEntry(names)
	c.shipSelector.OnChanged = c.onShipSelectorChange
}

func (c *Calculator) onCreateShipClassButton() {
	if c.onCreateHandler == nil || !c.Valid {
		return
	}
}

func (c *Calculator) validate() {
	fyne.Do(func() {
		c.validateMu.Lock()
		err := c.validateEntries()
		c.Valid = err == nil
		if err != nil {
		} else {
		}
		c.shipClassNameValidate()
		c.validateMu.Unlock()
	})
}

func (c *Calculator) validateEntries() (err error) {
	defer func() {
		if err != nil {
			c.cargoLoadEntry.Clear()
			if !c.massOverride.Checked {
				c.massEntry.Clear()
			}
			if !c.speedOverride.Checked {
				c.speedEntry.Clear()
			}
			if !c.attackOverride.Checked {
				c.attackEntry.Clear()
			}
			if !c.defenseOverride.Checked {
				c.defenseEntry.Clear()
			}
			// c.planetProdContainer.Hide()
		}
	}()
	drive, ok := c.shipDriveEntry.Value()
	if !ok {
		err = errors.New("Parameter Drive is not valid")
		return
	}
	driveTech, ok := c.playerDrivesTechEntry.Value()
	if !ok {
		err = errors.New("Drive tech level is not valid")
		return
	}
	armament, ok := c.shipArmamentEntry.Value()
	if !ok {
		err = errors.New("Parameter Armament is not valid")
		return
	}
	weapons, ok := c.shipWeaponsEntry.Value()
	if !ok {
		err = errors.New("Parameter Weapons is not valid")
		return
	}
	weaponsTech, ok := c.playerWeaponsTechEntry.Value()
	if !ok {
		err = errors.New("Weapons tech level is not valid")
		return
	}
	shields, ok := c.shipShieldsEntry.Value()
	if !ok {
		err = errors.New("Parameter Shields is not valid")
		return
	}
	shieldsTech, ok := c.playerShieldsTechEntry.Value()
	if !ok {
		err = errors.New("Shields tech level is not valid")
		return
	}
	cargo, ok := c.shipCargoEntry.Value()
	if !ok {
		err = errors.New("Parameter Cargo is not valid")
		return
	}
	cargoTech, ok := c.playerCargoTechEntry.Value()
	if !ok {
		err = errors.New("Cargo tech level is not valid")
		return
	}

	err = calc.ValidateShipTypeValues(drive, armament, weapons, shields, cargo)
	if err != nil {
		return
	}

	var cargoLoad float64
	if c.cargoLoadMaximize.Checked {
		cargoLoad = calc.CargoCapacity(cargo, cargoTech)
		c.cargoLoadEntry.SetOrigin(cargoLoad)
	} else if cargoLoad, ok = c.cargoLoadEntry.Value(); !ok {
		err = errors.New("Cargo load value is not valid")
		return
	}

	emptyMass, ok := calc.EmptyMass(drive, weapons, uint(armament), shields, cargo)
	if !ok {
		err = errors.New("Unable to calculate empty mass (check armament and weapons)")
		return
	}
	fullMass := calc.FullMass(emptyMass, cargoLoad)
	speed := calc.Speed(calc.DriveEffective(drive, driveTech), fullMass)
	effectiveAttack := calc.EffectiveAttack(weapons, weaponsTech)
	effectiveDefense := calc.EffectiveDefence(shields, shieldsTech, fullMass)

	c.massEntry.SetOrigin(emptyMass)
	c.speedEntry.SetOrigin(speed)
	c.attackEntry.SetOrigin(effectiveAttack)
	c.defenseEntry.SetOrigin(effectiveDefense)

	planetMat, ok := c.planetMatEntry.Value()
	if !ok {
		// c.planetProdContainer.Hide()
	} else {
		massProd := calc.PlanetProduceShipMass(c.l, planetMat, c.res)
		c.planetMassProdLabel.SetText(strconv.FormatFloat(util.Fixed3(massProd), 'f', -1, 64))
		ships := 0.
		if emptyMass > 0 {
			ships = massProd / emptyMass
		}
		c.planetShipsProdLabel.SetText(strconv.FormatFloat(util.Fixed3(ships), 'f', -1, 64))
		c.planetProdContainer.Show()
	}

	return
}

func (c *Calculator) onOriginInputChange(cb *widget.Check, e *numeric.FloatEntry) {
	if e == nil {
		return
	}
	if cb != nil {
		cb.Checked = e.Overriden()
		if !cb.Checked {
			cb.Disable()
		} else {
			cb.Enable()
		}
	}
	c.onFloatEntryChange(e)
}

func (c *Calculator) onFloatEntryChange(e *numeric.FloatEntry) {
	if e == nil {
		return
	}
	e.Validate()
	c.validate()
}

func (c *Calculator) onIntEntryChange(e *numeric.IntEntry) {
	if e == nil {
		return
	}
	e.Validate()
	c.validate()
}

func (c *Calculator) overrideChecked(cb *widget.Check, e *numeric.FloatEntry) {
	if cb == nil || e == nil {
		return
	}
	if !cb.Checked {
		e.Reset()
		cb.Disable()
	}
}

func (c *Calculator) onShipDriveChange(string) {
	c.onFloatEntryChange(c.shipDriveEntry)
}

func (c *Calculator) onShipArmamentChange(string) {
	defer c.onIntEntryChange(c.shipArmamentEntry)
	if weapons, ok := c.shipWeaponsEntry.Value(); !ok || !c.shipWeaponsEntry.Valid {
		return
	} else if armament, ok := c.shipArmamentEntry.Value(); !ok || !c.shipArmamentEntry.Valid {
		return
	} else if armament > 0 && weapons == 0 {
		c.shipWeaponsEntry.SetOrigin(1.0)
	} else if armament == 0 && weapons > 0 {
		c.shipWeaponsEntry.SetOrigin(0.0)
	}
}

func (c *Calculator) onShipWeaponsChange(string) {
	defer c.onFloatEntryChange(c.shipWeaponsEntry)
	if weapons, ok := c.shipWeaponsEntry.Value(); !ok || !c.shipWeaponsEntry.Valid {
		return
	} else if armament, ok := c.shipArmamentEntry.Value(); !ok || !c.shipArmamentEntry.Valid {
		return
	} else if weapons > 0 && armament == 0 {
		c.shipArmamentEntry.SetOrigin(1)
	} else if weapons == 0 && armament > 0 {
		c.shipArmamentEntry.SetOrigin(0)
	}
}

func (c *Calculator) onShipShieldsChange(string) {
	c.onFloatEntryChange(c.shipShieldsEntry)
}

func (c *Calculator) onShipCargoChange(string) {
	c.onFloatEntryChange(c.shipCargoEntry)
}

func (c *Calculator) onDrivesTechChange(string) {
	c.onOriginInputChange(c.drivesTechOverride, c.playerDrivesTechEntry)
}

func (c *Calculator) overrideDrivesTech(bool) {
	c.overrideChecked(c.drivesTechOverride, c.playerDrivesTechEntry)
}

func (c *Calculator) onWeaponsTechChange(string) {
	c.onOriginInputChange(c.weaponsTechOverride, c.playerWeaponsTechEntry)
}

func (c *Calculator) overrideWeaponsTech(bool) {
	c.overrideChecked(c.weaponsTechOverride, c.playerWeaponsTechEntry)
}

func (c *Calculator) onShieldsTechChange(string) {
	c.onOriginInputChange(c.shieldsTechOverride, c.playerShieldsTechEntry)
}

func (c *Calculator) overrideShieldsTech(bool) {
	c.overrideChecked(c.shieldsTechOverride, c.playerShieldsTechEntry)
}

func (c *Calculator) onCargoTechChange(string) {
	c.onOriginInputChange(c.cargoTechOverride, c.playerCargoTechEntry)
}

func (c *Calculator) overrideCargoTech(bool) {
	c.overrideChecked(c.cargoTechOverride, c.playerCargoTechEntry)
}

func (c *Calculator) onCargoLoadChange(string) {
	c.onFloatEntryChange(c.cargoLoadEntry)
}

func (c *Calculator) onMassChange(string) {
	c.onOriginInputChange(c.massOverride, c.massEntry)
}

func (c *Calculator) overrideMass(bool) {
	c.overrideChecked(c.massOverride, c.massEntry)
}

func (c *Calculator) onSpeedChange(string) {
	c.onOriginInputChange(c.speedOverride, c.speedEntry)
}

func (c *Calculator) overrideSpeed(bool) {
	c.overrideChecked(c.speedOverride, c.speedEntry)
}

func (c *Calculator) onAttackChange(string) {
	c.onOriginInputChange(c.attackOverride, c.attackEntry)
}

func (c *Calculator) overrideAttack(bool) {
	c.overrideChecked(c.attackOverride, c.attackEntry)
}

func (c *Calculator) onDefenseChange(string) {
	c.onOriginInputChange(c.defenseOverride, c.defenseEntry)
}

func (c *Calculator) overrideDefense(bool) {
	c.overrideChecked(c.defenseOverride, c.defenseEntry)
}

func (c *Calculator) maximizeCargoLoad(bool) {
	c.validate()
}

func (c *Calculator) onPlanetMatChange(string) {
	c.onOriginInputChange(c.planetMatOverride, c.planetMatEntry)
}

func (c *Calculator) overridePlanetMat(bool) {
	c.overrideChecked(c.planetMatOverride, c.planetMatEntry)
}

func (c *Calculator) onShipSelectorChange(v string) {
	i, ok := c.shipClassNameValidate()
	if i < 0 || !ok || c.loader == nil {
		return
	}
	c.loader(
		c.knownClasses[i].Name,
		c.knownClasses[i].Drive,
		c.knownClasses[i].Armament,
		c.knownClasses[i].Weapons,
		c.knownClasses[i].Shields,
		c.knownClasses[i].Cargo,
	)
}

func (c *Calculator) shipClassNameValidate() (int, bool) {
	var canCreateShip bool
	defer func() {
		if canCreateShip && c.Valid {
			c.shipCreateButton.Enable()
		} else {
			c.shipCreateButton.Disable()
		}
	}()
	name, canCreateShip := util.ValidateTypeName(c.shipSelector.Text)
	if canCreateShip {
		c.shipSelector.Text = name
	}
	i := slices.IndexFunc(c.knownClasses, func(v ShipClass) bool { return v.Name == name })
	canCreateShip = canCreateShip && i < 0
	return i, canCreateShip
}

func (c *Calculator) LoadShipClass(n string, D float64, A uint, W float64, S float64, C float64) {
	c.shipDriveEntry.SetOrigin(D)
	c.shipArmamentEntry.SetOrigin(int(A))
	c.shipWeaponsEntry.SetOrigin(W)
	c.shipShieldsEntry.SetOrigin(S)
	c.shipCargoEntry.SetOrigin(C)
}

func rowForItem(l string, entry, override fyne.CanvasObject) fyne.CanvasObject {
	i := []fyne.CanvasObject{label(l), entry}
	if override != nil {
		i = append(i, override)
	}
	return container.NewHBox(i...)
}

func rowForTech(l string, shipEntry, techEntry, btn fyne.CanvasObject) fyne.CanvasObject {
	return container.NewHBox(
		label(l),
		floatEntry(shipEntry, 115),
		widget.NewLabel("@"),
		techEntry,
		btn,
	)
}

func rowForWeapons(l string, armamentEntry, weaponsEntry, techEntry, btn fyne.CanvasObject) fyne.CanvasObject {
	return container.NewHBox(
		label(l),
		intEntry(armamentEntry, 35),
		floatEntry(weaponsEntry, 75),
		widget.NewLabel("@"),
		techEntry,
		btn,
	)
}

func label(l string) fyne.CanvasObject {
	return fixedLabel(bareLabel(l), 110)
}

func fixedLabel(w *widget.Label, width float32) fyne.CanvasObject {
	s := container.NewHScroll(w)
	s.SetMinSize(fyne.NewSize(width, 1))
	return s
}

func bareLabel(l string) *widget.Label {
	w := widget.NewLabelWithStyle(l, fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true, Symbol: false})
	w.Selectable = false
	w.Truncation = fyne.TextTruncateOff
	return w
}

func intEntry(content fyne.CanvasObject, width float32) fyne.CanvasObject {
	s := container.NewHScroll(content)
	s.SetMinSize(fyne.NewSize(width, 1))
	return s
}

func floatEntry(content fyne.CanvasObject, width float32) fyne.CanvasObject {
	s := container.NewHScroll(content)
	s.SetMinSize(fyne.NewSize(width, 1))
	return s
}
