package systems

import (
	"fmt"
	"log/slog"
	"openquell/components"
	. "openquell/components"
	. "openquell/config"
	"openquell/grid"
	"openquell/observers"

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

type PlayerSystem struct {
	World         *ecs.World
	Selector      *generic.Filter4[Position, Velocity, Player, Renderable]
	GridContainer *grid.GridContainer
	Width, Height int
}

func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer, width, height int) System {
	system := &PlayerSystem{
		Selector:      generic.NewFilter4[Position, Velocity, Player, Renderable](),
		GridContainer: gridcontainer,
		World:         world,
		Width:         width,
		Height:        height,
	}

	return system
}

func PlayerBumpEdgeResponder(
	pos *components.Position,
	vel *components.Velocity,
	newpos *components.Position) {

	pos.Set(newpos)
}

func PlayerBumpWallResponder(
	pos *components.Position,
	vel *components.Velocity,
	newpos *components.Position) {

	slog.Debug("(2) PlayerBumpWallResponder", "old", pos.String(), "new", newpos.String())
	pos.Set(newpos)
	vel.Change(Stop)
}

func (system PlayerSystem) Update() error {
	var EntitiesToRemove []ecs.Entity

	// check if we need to switch player[s]
	system.SwitchPlayers()

	// check player movements etc
	query := system.Selector.Query(system.World)
	count := query.Count()

	for query.Next() {
		playerposition, velocity, player, _ := query.Get()

		if !player.IsPrimary {
			continue
		}

		//  check  if the  user  alters  or initiates  movement,  only
		// changes player direction
		system.CheckMovement(playerposition, velocity, player)

		// check if player collides with walls or edges
		if velocity.Moving() {
			slog.Debug("(2) checking grid collision")
			system.GridContainer.Grid.CheckGridCollision(
				playerposition, velocity, PlayerBumpEdgeResponder, PlayerBumpWallResponder)
		}

		if count > 1 {
			// check if player collides with another player, fuse them if any
			EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity())
			if len(EntitiesToRemove) > 0 {
				slog.Debug("other player collision")
			}
		} else {
			// only 1 player left or one is max
			EntitiesToRemove = system.CheckPlayerLooping(
				playerposition, velocity, player, query.Entity())
			if len(EntitiesToRemove) > 0 {
				slog.Debug("player loops", "entities", EntitiesToRemove)
			}
		}

		if !velocity.Moving() {
			// disable loop detection
			player.LoopCount = 0
		}
	}

	// 2nd pass: move player after obstacle or collectible updates
	query = system.Selector.Query(system.World)
	for query.Next() {
		playerposition, velocity, _, _ := query.Get()
		oldpos := playerposition.String()
		if velocity.Moving() {
			playerposition.Move(velocity)
			slog.Debug("(4) moving player", "old", oldpos, "new", playerposition.String())
		}
	}

	// we may have lost players, remove them here

	for _, entity := range EntitiesToRemove {
		slog.Debug("remove player", "ent", entity.IsZero())
		system.World.RemoveEntity(entity)
	}

	return nil
}

func (system *PlayerSystem) Draw(screen *ebiten.Image) {
	// write the movable tiles
	op := &ebiten.DrawImageOptions{}
	query := system.Selector.Query(system.World)

	for query.Next() {
		pos, _, _, sprite := query.Get()

		op.GeoM.Reset()
		op.GeoM.Translate(float64(pos.X), float64(pos.Y))

		screen.DrawImage(sprite.Image, op)

		ebitenutil.DebugPrintAt(screen, pos.String(), pos.X, pos.Y) // print player pos
	}
}

func (system *PlayerSystem) SwitchPlayers() {
	// first check if we need to switch player
	switchable := false
	query := system.Selector.Query(system.World)
	count := query.Count()

	for query.Next() {
		_, _, player, _ := query.Get()
		if !player.IsPrimary {
			switchable = true
		}
	}

	if switchable {
		query := system.Selector.Query(system.World)
		for query.Next() {
			_, _, player, render := query.Get()

			if count == 1 && !player.IsPrimary {
				// there's only one player left, make it the primary one
				player.IsPrimary = true
				render.Image = player.SwitchSprite()
			} else {
				// many players, switch when requested
				if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
					slog.Debug("switch players")
					if player.IsPrimary {
						player.IsPrimary = false
						render.Image = player.SwitchSprite()
					} else {
						player.IsPrimary = true
						render.Image = player.SwitchSprite()
					}
				}
			}
		}
	}
}

func (system *PlayerSystem) CheckMovement(
	position *components.Position,
	velocity *components.Velocity,
	player *components.Player) {

	moved := false
	observer := observers.GetGameObserver(system.World)

	if !velocity.Moving() {
		switch {
		case inpututil.IsKeyJustPressed(ebiten.KeyRight):
			velocity.Change(East)
			moved = true
		case inpututil.IsKeyJustPressed(ebiten.KeyLeft):
			velocity.Change(West)
			moved = true
		case inpututil.IsKeyJustPressed(ebiten.KeyDown):
			velocity.Change(South)
			moved = true
		case inpututil.IsKeyJustPressed(ebiten.KeyUp):
			velocity.Change(North)
			moved = true
		}

		if moved {
			// will be reset every time the user initiates player movement
			player.LoopPos.Set(position)
			player.LoopCount = 0
			observer.AddMove()
		}
	} else {
		fmt.Println("------------------------")
		slog.Debug("(1) player is at", "current", position.String())
	}
}

func (system *PlayerSystem) CheckPlayerCollision(
	position *components.Position,
	velocity *components.Velocity,
	player ecs.Entity) []ecs.Entity {

	observer := observers.GetGameObserver(system.World)
	posID := ecs.ComponentID[components.Position](system.World)
	EntitiesToRemove := []ecs.Entity{}

	for _, otherplayer := range observer.GetPlayers() {
		if !system.World.Alive(otherplayer) {
			continue
		}

		if otherplayer == player {
			continue
		}

		otherposition := (*Position)(system.World.Get(otherplayer, posID))

		ok, _ := otherposition.Intersects(position, velocity)
		if ok {
			// keep displaying it until the two fully intersect
			EntitiesToRemove = append(EntitiesToRemove, otherplayer)
		}
	}

	// FIXME: add an animation highlighting the fusion
	return EntitiesToRemove
}

func (system *PlayerSystem) CheckPlayerLooping(
	position *components.Position,
	velocity *components.Velocity,
	player *components.Player,
	entity ecs.Entity) []ecs.Entity {

	if player.LoopPos.Rect == nil {
		// hasn't moved
		return nil
	}
	EntitiesToRemove := []ecs.Entity{}

	ok, _ := player.LoopPos.Intersects(position, velocity)
	if ok {
		// Fatal: loop detected with last player
		player.LoopStart = true
	} else {
		// no intersection with old pos anymore
		if player.LoopStart {
			// loop detection active
			player.LoopCount++
			max := system.Width
			if velocity.Direction == North || velocity.Direction == South {
				max = system.Height
			}

			max += system.GridContainer.Grid.Tilesize * 5

			// we haved moved one time around the whole screen, loose
			if (player.LoopCount * velocity.Speed) > max {
				slog.Debug("loop?", "loopcount", player.LoopCount, "speed", velocity.Speed, "max", max)
				EntitiesToRemove = append(EntitiesToRemove, entity)
			}
		}
	}

	return EntitiesToRemove
}
