package game

import (
	"fmt"
	"galaxy/calc"
	"math"
	"strings"

	"github.com/google/uuid"
)

type CargoType string

const (
	CargoColonist CargoType = "COL" // Колонисты
	CargoMaterial CargoType = "MAT" // Сырьё
	CargoCapital  CargoType = "CAP" // Промышленность
)

var (
	CargoTypeSet map[string]CargoType = map[string]CargoType{
		strings.ToLower(CargoColonist.String()): CargoColonist,
		strings.ToLower(CargoMaterial.String()): CargoMaterial,
		strings.ToLower(CargoCapital.String()):  CargoCapital,
	}
)

func (ct CargoType) Ref() *CargoType {
	return &ct
}

func (ct CargoType) String() string {
	return string(ct)
}

type ShipGroupState string

const (
	StateInOrbit  ShipGroupState = "In_Orbit"
	StateLaunched ShipGroupState = "Launched"
	StateInSpace  ShipGroupState = "In_Space"
	StateUpgrade  ShipGroupState = "Upgrade"
	StateTransfer ShipGroupState = "Transfer" // [ ] Группы будут передаваться мгновенно в начале производства хода
)

func (sgs ShipGroupState) String() string {
	return string(sgs)
}

type InSpace struct {
	// X, Y are nil for Launched state
	Origin uint   `json:"origin"`
	X      *Float `json:"x,omitempty"`
	Y      *Float `json:"y,omitempty"`
}

func (is InSpace) Equal(other InSpace) bool {
	return is.Origin == other.Origin && is.X == other.X && is.Y == other.Y
}

func (is InSpace) Launched() bool {
	if (is.X == nil && is.Y != nil) || (is.X != nil && is.Y == nil) {
		panic("group in space state invalid: one of coordinate is not set")
	}
	return is.X == nil && is.Y == is.X
}

type InUpgrade struct {
	UpgradeTech []UpgradePreference `json:"preference"`
}

func (iu InUpgrade) Cost() float64 {
	var sum float64
	for i := range iu.UpgradeTech {
		sum += iu.UpgradeTech[i].Cost.F()
	}
	return sum
}

func (iu InUpgrade) TechCost(t Tech) float64 {
	for i := range iu.UpgradeTech {
		if iu.UpgradeTech[i].Tech == t {
			return iu.UpgradeTech[i].Cost.F()
		}
	}
	return 0.
}

type UpgradePreference struct {
	Tech  Tech  `json:"tech"`
	Level Float `json:"level"`
	Cost  Float `json:"cost"`
}

type Tech string

const (
	TechAll     Tech = "ALL"
	TechDrive   Tech = "DRIVE"
	TechWeapons Tech = "WEAPONS"
	TechShields Tech = "SHIELDS"
	TechCargo   Tech = "CARGO"
)

func (t Tech) String() string {
	return string(t)
}

type ShipGroup struct {
	ID            uuid.UUID  `json:"id"`
	OwnerID       uuid.UUID  `json:"ownerId"`            // Race reference
	TypeID        uuid.UUID  `json:"typeId"`             // ShipType reference
	FleetID       *uuid.UUID `json:"fleetId,omitempty"`  // Fleet reference
	Number        uint       `json:"number"`             // Number (quantity) ships of specific ShipType
	CargoType     *CargoType `json:"loadType,omitempty"` //
	Load          Float      `json:"load"`               // Cargo loaded - "Масса груза"
	Tech          TechSet    `json:"tech"`               //
	Destination   uint       `json:"destination"`        //
	StateInSpace  *InSpace   `json:"inSpace,omitempty"`  //
	StateUpgrade  *InUpgrade `json:"upgrade,omitempty"`  //
	StateTransfer bool       `json:"transfer,omitempty"` //
}

func (sg ShipGroup) TechLevel(t Tech) Float {
	return F(sg.Tech.Value(t))
}

func (sg *ShipGroup) SetTechLevel(t Tech, v float64) {
	sg.Tech = sg.Tech.Set(t, v)
}

func (sg ShipGroup) State() ShipGroupState {
	switch {
	case sg.StateInSpace == nil && sg.StateUpgrade == nil:
		return StateInOrbit
	case sg.StateInSpace != nil && sg.StateUpgrade == nil:
		if !sg.StateInSpace.Launched() {
			return StateInSpace
		}
		if sg.StateTransfer {
			return StateTransfer
		}
		return StateLaunched
	case sg.StateUpgrade != nil && sg.StateInSpace == nil:
		return StateUpgrade
	default:
		panic(fmt.Sprintf("ambigous group state: in_space=%#v upgrage=%#v", sg.StateInSpace, sg.StateUpgrade))
	}
}

func (sg ShipGroup) AtPlanet() (uint, bool) {
	switch sg.State() {
	case StateInOrbit:
		return sg.Destination, true
	case StateLaunched:
		return sg.StateInSpace.Origin, true
	default:
		return 0, false
	}
}

func (sg ShipGroup) Coord() (float64, float64, bool) {
	state := sg.State()
	if state == StateInSpace || state == StateLaunched {
		return sg.StateInSpace.X.F(), sg.StateInSpace.Y.F(), true
	}
	return 0, 0, false
}

func (sg ShipGroup) Equal(other ShipGroup) bool {
	return sg.OwnerID == other.OwnerID &&
		sg.TypeID == other.TypeID &&
		sg.FleetID == other.FleetID &&
		sg.TechLevel(TechDrive) == other.TechLevel(TechDrive) &&
		sg.TechLevel(TechWeapons) == other.TechLevel(TechWeapons) &&
		sg.TechLevel(TechShields) == other.TechLevel(TechShields) &&
		sg.TechLevel(TechCargo) == other.TechLevel(TechCargo) &&
		sg.CargoType == other.CargoType &&
		(sg.Load/F(float64(sg.Number))).F() == (other.Load/F(float64(other.Number))).F() &&
		sg.State() == other.State()
}

// Грузоподъёмность группы
func (sg ShipGroup) CargoCapacity(st *ShipType) float64 {
	return calc.CargoCapacity(st.Cargo.F(), sg.TechLevel(TechCargo).F()) * float64(sg.Number)
}

func (sg ShipGroup) CarryingMass() float64 {
	return calc.CarryingMass(sg.Load.F(), sg.TechLevel(TechCargo).F())
}

// Масса группы без учёта груза
func (sg ShipGroup) EmptyMass(st *ShipType) float64 {
	return st.EmptyMass() * float64(sg.Number)
}

func (sg ShipGroup) FullMass(st *ShipType) float64 {
	return calc.FullMass(sg.EmptyMass(st), sg.CarryingMass())
}

func (sg ShipGroup) DriveEffective(st *ShipType) float64 {
	return calc.DriveEffective(st.Drive.F(), sg.TechLevel(TechDrive).F())
}

func (sg ShipGroup) Speed(st *ShipType) float64 {
	return calc.Speed(sg.DriveEffective(st), sg.FullMass(st))
}

// Мощность бомбардировки
func (sg ShipGroup) BombingPower(st *ShipType) float64 {
	return (math.Sqrt(st.Weapons.F()*sg.TechLevel(TechWeapons).F())/10. + 1.) *
		st.Weapons.F() *
		sg.TechLevel(TechWeapons).F() *
		float64(st.Armament) *
		float64(sg.Number)
}

func (sg ShipGroup) CargoString() string {
	if sg.CargoType == nil {
		return "-"
	}
	return sg.CargoType.String()
}
