package game

import (
	"fmt"
	"image"
	"log/slog"
	"openquell/config"
	"openquell/observers"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/mlange-42/arche/ecs"
)

type Game struct {
	World                               *ecs.World
	Bounds                              image.Rectangle
	ScreenWidth, ScreenHeight, Cellsize int
	Scenes                              map[SceneName]Scene
	CurrentScene                        SceneName
	Observer                            *observers.GameObserver
	Levels                              []*Level // fed in PlayScene.GenerateLevels()
	Config                              *config.Config
}

func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneName) *Game {
	world := ecs.NewWorld()

	game := &Game{
		Bounds:       image.Rectangle{},
		World:        &world,
		ScreenWidth:  width,
		ScreenHeight: height,
		Scenes:       map[SceneName]Scene{},
		Cellsize:     cellsize,
		Config:       cfg,
	}

	game.Observer = observers.NewGameObserver(
		&world, cfg.Startlevel, width, height, cellsize)

	game.Scenes[Welcome] = NewWelcomeScene(game)
	game.Scenes[Menu] = NewMenuScene(game)
	game.Scenes[About] = NewAboutScene(game)
	game.Scenes[Popup] = NewPopupScene(game)
	game.Scenes[Play] = NewPlayScene(game, cfg.Startlevel)
	game.Scenes[Select] = NewSelectScene(game)

	game.CurrentScene = startscene

	fmt.Println(game.World.Stats().String())

	return game
}

func (game *Game) GetCurrentScene() Scene {
	return game.Scenes[game.CurrentScene]
}

func (game *Game) Update() error {
	// handle level ends
	timer := game.Observer.StopTimer

	if timer.IsReady() {
		// a  level is either  lost or won,  we display a  small popup
		// asking the user how to continue from here
		timer.Reset()

		slog.Debug("timer ready", "lost", game.Observer.Lost, "retry", game.Observer.Retry)
		game.Observer.AddScore()

		game.Scenes[Nextlevel] = NewNextlevelScene(game)
		game.CurrentScene = Nextlevel
	}

	scene := game.GetCurrentScene()
	scene.Update()

	if scene.Clearscreen() {
		ebiten.SetScreenClearedEveryFrame(true)
	} else {
		ebiten.SetScreenClearedEveryFrame(false)
	}

	next := scene.GetNext()

	if next != game.CurrentScene {
		if next == Play && game.CurrentScene == Nextlevel {
			// switched from nextlevel (lost or won) popup to play (either retry or next level)
			if !game.Observer.Retry {
				game.Observer.CurrentLevel++
			}
			game.Observer.Retry = false
		}

		if next == Play {
			// fresh setup of actual level every time we enter the play scene
			game.Scenes[Play].SetLevel(game.Observer.CurrentLevel)
		}

		// make sure we stay on the selected scene
		scene.ResetNext()

		// finally switch
		game.CurrentScene = next

		// FIXME: add some reset function to game.Observer for these kinds of things
		game.Observer.Lost = false
	}

	timer.Update()

	return nil
}

func (game *Game) Draw(screen *ebiten.Image) {
	game.GetCurrentScene().Draw(screen)
}

func (g *Game) Layout(newWidth, newHeight int) (int, int) {
	return g.ScreenWidth, g.ScreenHeight
}
