package grid

import (
	"openquell/components"
	. "openquell/config"

	"log/slog"
)

// Check a collision  on the grid. We check if  the entity in question
// bumps into the  egde of the grid  or if it bumps onto  a solid wall
// tile. For both cases the user must provide responder funcs in which
// it must be implemented how to react on those events.
func (grid *Grid) CheckGridCollision(
	position *components.Position,
	velocity *components.Velocity,
	respond_edge func(*components.Position, *components.Velocity, *components.Position),
	respond_solid func(*components.Position, *components.Velocity, *components.Position),
) {

	if velocity.Moving() {
		ok, newpos := grid.BumpEdge(position, velocity)
		if ok {
			//slog.Debug("falling off the edge", "newpos", newpos)
			respond_edge(position, velocity, newpos)
		} else {
			ok, tilepos := grid.GetSolidNeighborPosition(position, velocity, true)
			if ok {
				slog.Debug("(3) HaveSolidNeighbor", "tilepos",
					tilepos.String(), "playpos", position.String())
				intersects, newpos := tilepos.Intersects(position, velocity)
				if intersects {
					respond_solid(position, velocity, newpos)
				}
			}
		}
	}
}

func (grid *Grid) GetSolidNeighborPosition(
	position *components.Position,
	velocity *components.Velocity,
	solid bool) (bool, *components.Position) {

	if !solid {
		return false, nil
	}

	// set to true  if we are already on the last  tile in the current
	// direction, i.e. on the edge of the grid
	edge := true
	moving := position.GetMoved(velocity)

	neighborpos := position.Point()
	if velocity.Direction == East || velocity.Direction == South {
		// FIXES snapin  to down+right, but now top+left  fail. Only in
		// these cases  we need to look at the  neighbor in the future
		// position where we will be when we move forward. I don't
		// know why it is like this, in fact it doesn't really make
		// any sense. But it works, so I'll keep it that way for the
		// moment.
		neighborpos = moving.Point()
	}

	switch velocity.Direction {
	case East:
		if neighborpos.X < grid.TilesX {
			neighborpos.X++
			edge = false
		}
	case West:
		if neighborpos.X > 0 {
			neighborpos.X--
			edge = false
		}
	case South:
		if neighborpos.Y < grid.TilesY {
			neighborpos.Y++
			edge = false
		}
	case North:
		if neighborpos.Y > 0 {
			neighborpos.Y--
			edge = false
		}
	}

	newpos := components.NewPosition(neighborpos, grid.Tilesize)

	slog.Debug("SolidNeighbor?", "player", position.Point(),
		"neighbor", neighborpos, "edge", edge, "neighbor-solid",
		grid.Map[neighborpos].Solid, "newpos", newpos.Point())
	if !edge && grid.Map[neighborpos].Solid {
		return true, newpos
	}

	return false, nil
}

func (grid *Grid) OrigGetSolidNeighborPosition(
	position *components.Position,
	velocity *components.Velocity,
	solid bool) (bool, *components.Position) {

	if !solid {
		return false, nil
	}

	// set to true  if we are already on the last  tile in the current
	// direction, i.e. on the edge of the grid
	edge := true
	neighborpos := position.Point()

	switch velocity.Direction {
	case East:
		if neighborpos.X < grid.TilesX {
			neighborpos.X++
			edge = false
		}
	case West:
		if neighborpos.X > 0 {
			neighborpos.X--
			edge = false
		}
	case South:
		if neighborpos.Y < grid.TilesY {
			neighborpos.Y++
			edge = false
		}
	case North:
		if neighborpos.Y > 0 {
			neighborpos.Y--
			edge = false
		}
	}

	newpos := components.NewPosition(neighborpos, grid.Tilesize)

	slog.Debug("SolidNeighbor?", "player", position.Point(),
		"neighbor", neighborpos, "edge", edge, "neighbor-solid",
		grid.Map[neighborpos].Solid, "newpos", newpos.Point())
	if !edge && grid.Map[neighborpos].Solid {
		return true, newpos
	}

	return false, nil
}

func (grid *Grid) BumpEdge(
	pos *components.Position,
	velocity *components.Velocity) (bool, *components.Position) {

	x := pos.X + velocity.Data.X
	y := pos.Y + velocity.Data.Y

	if x < 0 || x > grid.Width-grid.Tilesize || y < 0 || y > grid.Height-grid.Tilesize {
		newpos := &components.Position{}
		X := pos.X
		Y := pos.Y

		switch velocity.Direction {
		case East:
			X = 0
		case West:
			X = grid.Width - grid.Tilesize
		case South:
			Y = 0
		case North:
			Y = grid.Height - grid.Tilesize
		}

		newpos.Update(X, Y, grid.Tilesize)
		return true, newpos
	}

	return false, nil
}
