package systems

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

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

type ObstacleSystem struct {
	World         *ecs.World
	Selector      *generic.Filter4[Position, Velocity, Obstacle, Renderable]
	GridContainer *grid.GridContainer
}

func NewObstacleSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
	system := &ObstacleSystem{
		Selector:      generic.NewFilter4[Position, Velocity, Obstacle, Renderable](),
		World:         world,
		GridContainer: gridcontainer,
	}

	return system
}

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

	pos.Set(newpos)
}

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

	pos.Set(newpos)
	vel.ResetDirectionAndStop()
}

func (system *ObstacleSystem) Update() error {
	observer := observers.GetGameObserver(system.World)

	if observer.Lost {
		return nil
	}

	posID := ecs.ComponentID[components.Position](system.World)
	veloID := ecs.ComponentID[components.Velocity](system.World)

	EntitiesToRemove := []ecs.Entity{}

	query := system.Selector.Query(system.World)

	for query.Next() {
		obsposition, obsvelocity, _, _ := query.Get()

		// check if one player has bumped into current obstacle
		for _, player := range observer.GetPlayers() {
			if !system.World.Alive(player) {
				continue
			}

			playerposition := (*Position)(system.World.Get(player, posID))
			playervelocity := (*Velocity)(system.World.Get(player, veloID))

			ok, newpos := obsposition.Intersects(playerposition, playervelocity)
			if ok {
				if CheckObstacleSide(playervelocity, obsvelocity) {
					// player died
					EntitiesToRemove = append(EntitiesToRemove, player)
				} else {
					// bumped  into nonlethal obstacle side,  stop the
					// player, set the obstacle in motion, if possible
					//slog.Debug("bump not die", "playervelo", playervelocity)
					obsvelocity.Set(playervelocity)
					//slog.Debug("bump not die", "obsvelo", obsvelocity)
					playervelocity.Change(Stop)
					playerposition.Set(newpos)
				}
			}
		}

		// check if current obstacle bumped into another obstacle
		for _, foreign_obstacle := range observer.GetObstacles() {
			if foreign_obstacle == query.Entity() {
				// don't check obstacle against itself
				continue
			}

			foreign_obstacle_position := (*Position)(system.World.Get(foreign_obstacle, posID))

			ok, newpos := foreign_obstacle_position.Intersects(obsposition, obsvelocity)
			if ok {
				//slog.Debug("bumped into foreign obstacle", "obstacle", foreign_obstacle)
				obsposition.Set(newpos)
				obsvelocity.ResetDirectionAndStop()
			}
		}

		// check if [moving] obstacle collides with walls or edges
		system.GridContainer.Grid.CheckGridCollision(
			obsposition, obsvelocity, ObstacleBumpEdgeResponder, ObstacleBumpWallResponder)

		if obsvelocity.Moving() {
			obsposition.Move(obsvelocity)
		}
	}

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

	if len(observer.GetPlayers()) == 0 {
		// lost
		timer := observer.StopTimer

		if !timer.Running {
			timer.Start(LEVEL_END_WAIT)
		}

		observer.Gameover()
	}

	return nil
}

func (system *ObstacleSystem) 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)
	}
}

// return true if obstacle weapon points into the direction the player is moving
// OR if the weapon points towards a non-moving player
func CheckObstacleSide(playervelocity *Velocity, obsvelocity *Velocity) bool {
	movingdirection := playervelocity.InvertDirection()

	if obsvelocity.PointingAt == All || movingdirection == obsvelocity.PointingAt {
		slog.Debug("Damaging obstacle collision",
			"playerdirection", util.DirectionStr(playervelocity.Direction),
			"obsdirection", util.DirectionStr(obsvelocity.PointingAt),
			"movingdirection", util.DirectionStr(movingdirection),
		)
		return true
	}

	return false
}
