package observers

import (
	"math/rand"
	"openquell/components"

	"log/slog"

	"github.com/mlange-42/arche/ecs"
	"github.com/mlange-42/arche/ecs/event"
	"github.com/mlange-42/arche/generic"
	"github.com/mlange-42/arche/listener"
)

type Score struct {
	Min, Score int
}

// Used for global game state. Also stores mobile entities of the
// current level. The Entities map will be reset on each level
// initialization in grid/grid.go
type GameObserver struct {
	World                      *ecs.World
	CurrentLevel, Width, Moves int
	Height, Cellsize, Score    int
	StopTimer                  *components.Timer
	Lost                       bool // set to true if player is struck or something, by default: win!
	Retry                      bool
	NextlevelText              string
	Entities                   map[ecs.ID]map[ecs.Entity]int
	LevelScore                 map[int]*Score // one score per level
	Id                         int
	MaxLevels                  int
}

func (observer *GameObserver) GetListenerCallback(comp ecs.ID) listener.Callback {
	return listener.NewCallback(
		func(world *ecs.World, event ecs.EntityEvent) {
			observer.RemoveEntity(event.Entity, comp)
		},
		event.EntityRemoved,
		comp,
	)
}

func NewGameObserver(
	world *ecs.World, startlevel, width, height, cellsize int) *GameObserver {
	observer := &GameObserver{
		CurrentLevel: startlevel,
		StopTimer:    &components.Timer{},
		Width:        width,
		Height:       height,
		Cellsize:     cellsize,
		World:        world,
		Id:           rand.Intn(1000),
	}

	playerID := ecs.ComponentID[components.Player](world)
	obstacleID := ecs.ComponentID[components.Obstacle](world)
	animationID := ecs.ComponentID[components.Animation](world)

	playerListener := observer.GetListenerCallback(playerID)
	obstacleListener := observer.GetListenerCallback(obstacleID)
	animationListener := observer.GetListenerCallback(animationID)

	listen := listener.NewDispatch(
		&playerListener,
		&obstacleListener,
		&animationListener,
	)

	world.SetListener(&listen)

	observer.Entities = make(map[ecs.ID]map[ecs.Entity]int)
	observer.Entities[playerID] = make(map[ecs.Entity]int)
	observer.Entities[obstacleID] = make(map[ecs.Entity]int)
	observer.Entities[animationID] = make(map[ecs.Entity]int)

	resmanger := generic.NewResource[GameObserver](world)
	resmanger.Add(observer)

	return observer
}

func GetGameObserver(world *ecs.World) *GameObserver {
	resmanger := generic.NewResource[GameObserver](world)
	observer := resmanger.Get()

	return observer
}

func (observer *GameObserver) Gameover() {
	observer.Lost = true
}

func (observer *GameObserver) AddEntity(entity ecs.Entity, comp ecs.ID) {
	observer.Entities[comp][entity] = 1
}

func (observer *GameObserver) RemoveEntity(entity ecs.Entity, comp ecs.ID) {
	delete(observer.Entities[comp], entity)
}

func (observer *GameObserver) GetEntities(comp ecs.ID) []ecs.Entity {
	keys := make([]ecs.Entity, 0, len(observer.Entities[comp]))
	for k, _ := range observer.Entities[comp] {
		keys = append(keys, k)
	}
	return keys
}

func (observer *GameObserver) GetPlayers() []ecs.Entity {
	playerID := ecs.ComponentID[components.Player](observer.World)
	return observer.GetEntities(playerID)
}

func (observer *GameObserver) GetObstacles() []ecs.Entity {
	obstacleID := ecs.ComponentID[components.Obstacle](observer.World)
	return observer.GetEntities(obstacleID)
}

func (observer *GameObserver) GetAnimations() []ecs.Entity {
	animationID := ecs.ComponentID[components.Animation](observer.World)
	return observer.GetEntities(animationID)
}

func (observer *GameObserver) RemoveEntities() {
	playerID := ecs.ComponentID[components.Player](observer.World)
	obstacleID := ecs.ComponentID[components.Obstacle](observer.World)
	animationID := ecs.ComponentID[components.Animation](observer.World)

	observer.Entities = make(map[ecs.ID]map[ecs.Entity]int)
	observer.Entities[playerID] = make(map[ecs.Entity]int)
	observer.Entities[obstacleID] = make(map[ecs.Entity]int)
	observer.Entities[animationID] = make(map[ecs.Entity]int)
}

func (observer *GameObserver) SetupLevelScore(min []int) {
	observer.LevelScore = make(map[int]*Score, len(min))

	for level, minmoves := range min {
		observer.LevelScore[level] = &Score{Min: minmoves}
	}
}

func (observer *GameObserver) SetupMaxLevels(count int) {
	observer.MaxLevels = count
}

// set current level stats and reset counters
func (observer *GameObserver) AddScore() {
	level := observer.CurrentLevel
	moves := observer.Moves

	slog.Debug("AddScore", "moves", observer.Moves)

	if observer.Lost {
		observer.LevelScore[level].Score = 0
		slog.Debug("lost")
	} else {
		observer.LevelScore[level].Score = ((observer.LevelScore[level].Min * 100) / moves) / 30
		slog.Debug("won", "score", observer.LevelScore[level].Score,
			"Min", observer.LevelScore[level].Min,
			"Min-x-100", observer.LevelScore[level].Min*100,
			"Min-x-100-moves", (observer.LevelScore[level].Min*100)/moves,
		)
	}

	observer.Moves = 0
}

func (observer *GameObserver) AddMove() {
	observer.Moves++
}

func (observer *GameObserver) GetScore() int {
	sum := 0
	for _, score := range observer.LevelScore {
		sum += score.Score
	}

	return sum
}

func (observer *GameObserver) GetLevelScore() int {
	return observer.LevelScore[observer.CurrentLevel].Score
}
