package controller

import (
	"fmt"
	"iter"
	"slices"

	e "galaxy/error"

	"galaxy/game/internal/model/game"
)

func (c *Cache) Relation(r1, r2 int) game.Relation {
	if c.cacheRelation == nil {
		c.cacheRelation = make(map[int]map[int]game.Relation)
		for r1 := range c.listRaceActingIdx() {
			for r2 := range c.listRaceActingIdx() {
				if r1 == r2 {
					continue
				}
				rel := slices.IndexFunc(c.g.Race[r1].Relations, func(r game.RaceRelation) bool { return r.RaceID == c.g.Race[r2].ID })
				if rel < 0 {
					panic(fmt.Sprintf("Relation: opponent not found idx=%d", r2))
				}
				c.updateRelationCache(r1, r2, c.g.Race[r1].Relations[rel].Relation)
			}
		}

	}
	if _, ok := c.cacheRelation[r1]; !ok {
		panic(fmt.Sprintf("Relation: no left race idx=%d", r1))
	}
	if v, ok := c.cacheRelation[r1][r2]; !ok {
		panic(fmt.Sprintf("Relation: no right race idx=%d", r2))
	} else {
		return v
	}
}

func (c *Cache) updateRelationCache(r1, r2 int, rel game.Relation) {
	if r1 == r2 {
		return
	}
	if c.cacheRelation == nil {
		c.cacheRelation = make(map[int]map[int]game.Relation)
	}
	if _, ok := c.cacheRelation[r1]; !ok {
		c.cacheRelation[r1] = make(map[int]game.Relation)
	}
	c.cacheRelation[r1][r2] = rel
}

func (c *Cache) Voted(ri int) int {
	c.validateRaceIndex(ri)
	return c.RaceIndex(c.g.Race[ri].VoteFor)
}

func (c *Cache) UpdateRelation(ri, other int, rel game.Relation) (err error) {
	defer func() {
		if err == nil && c.cacheRelation != nil {
			c.updateRelationCache(ri, other, rel)
		}
	}()
	for o := range c.g.Race[ri].Relations {
		switch {
		case ri == other:
			c.g.Race[ri].Relations[o].Relation = rel
		case c.g.Race[ri].Relations[o].RaceID == c.g.Race[other].ID:
			c.g.Race[ri].Relations[o].Relation = rel
			return nil
		}
	}
	if ri != other {
		err = e.NewGameStateError("UpdateRelation: opponent not found")
	}
	return
}

func (c *Cache) validateRaceIndex(i int) {
	if i >= len(c.g.Race) {
		panic(fmt.Sprintf("race index out of range: %d >= %d", i, len(c.g.Race)))
	}
}

func (c *Cache) validActor(name string) (int, error) {
	i, err := c.validRace(name)
	if err != nil {
		return -1, err
	}
	c.g.Race[i].TTL = 10
	return i, nil
}

// validRace returns index of race with given name or error when race not found or extinct
func (c *Cache) validRace(name string) (int, error) {
	i, err := c.raceIndex(name)
	if err != nil {
		return -1, err
	}
	if c.g.Race[i].Extinct {
		return -1, e.NewRaceExinctError(name)
	}
	return i, nil
}

func (c *Cache) raceIndex(name string) (int, error) {
	i := slices.IndexFunc(c.g.Race, func(r game.Race) bool { return r.Name == name })
	if i < 0 {
		return i, e.NewRaceUnknownError(name)
	}
	return i, nil
}

func (c *Cache) raceTechLevel(ri int, t game.Tech, v float64) {
	c.validateRaceIndex(ri)
	c.g.Race[ri].Tech = c.g.Race[ri].Tech.Set(t, v)
}

func (c *Cache) TurnWipeExtinctRaces() {
	for i := range c.listRaceActingIdx() {
		if (c.g.Race[i].Extinct && c.g.Race[i].TTL > 0) || (!c.g.Race[i].Extinct && c.g.Race[i].TTL == 0) {
			c.wipeRace(i)
		}
	}
}

func (c *Cache) wipeRace(ri int) {
	c.validateRaceIndex(ri)
	r := &c.g.Race[ri]
	c.g.ShipGroups = slices.DeleteFunc(c.g.ShipGroups, func(v game.ShipGroup) bool { return v.OwnerID == r.ID })
	c.g.Fleets = slices.DeleteFunc(c.g.Fleets, func(v game.Fleet) bool { return v.OwnerID == r.ID })
	clear(r.ShipTypes)
	clear(r.Sciences)
	for i := range c.g.Map.Planet {
		p := &c.g.Map.Planet[i]
		if p.Owner != nil && *p.Owner != r.ID {
			continue
		}
		p.Wipe()
	}
	for i := range c.listRaceActingIdx() {
		if i == ri {
			continue
		}
		if c.g.Race[i].VoteFor == r.ID {
			c.g.Race[i].VoteFor = c.g.Race[i].ID
		}
	}
	r.Votes = 0
	r.VoteFor = r.ID
	r.Extinct = true
	r.TTL = 0
	c.invalidateFleetCache()
	c.invalidateShipGroupCache()
}

func (c *Cache) listRaceActingIdx() iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range c.listRaceIdx() {
			if c.g.Race[i].Extinct {
				continue
			}
			if !yield(i) {
				return
			}
		}
	}
}

func (c *Cache) listRaceIdx() iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := range c.g.Race {
			if !yield(i) {
				return
			}
		}
	}
}
