package game

import (
	"fmt"
	"image"
	"log"
	"openquell/assets"
	"openquell/components"
	. "openquell/config"

	"github.com/alecthomas/repr"
	"github.com/mlange-42/arche/generic"
)

type Grid struct {
	Width    int
	Height   int
	Size     int
	Tilesize int
	TilesX   int
	TilesY   int
	Map      map[image.Point]*assets.Tile
}

// FIXME: put component addition into extra callable function, to be called
// by game.go in a level-loop. Also remove components of previous level
func NewGrid(game *Game, tilesize int, mapslice map[image.Point]*assets.Tile) *Grid {
	// we use this to turn  our tiles into iterable entities, used for
	// collision detection, transformation and other things
	playermapper := generic.NewMap4[
		components.Position,
		components.Velocity,
		components.Renderable,
		components.Player](game.World)

	solidmapper := generic.NewMap4[
		components.Position,
		components.Renderable,
		components.Tilish,
		components.Solid](game.World)

	emptymapper := generic.NewMap2[components.Position, components.Tilish](game.World)

	colmapper := generic.NewMap3[
		components.Position,
		components.Renderable,
		components.Collectible](game.World)

	ptmapper := generic.NewMap2[
		components.Position,
		components.Particle,
	](game.World)

	var pos *components.Position
	var render *components.Renderable

	for point, tile := range mapslice {
		switch tile.Renderable {
		case true:
			switch {
			case tile.Solid:
				entity := solidmapper.New()
				pos, render, _, _ = solidmapper.Get(entity)
			case tile.Player:
				entity := playermapper.New()
				pos, _, render, _ = playermapper.Get(entity)
				fmt.Printf("player start pos: %d,%d\n", point.X*tilesize, point.Y*tilesize)
			case tile.Collectible:
				entity := colmapper.New()
				pos, render, _ = colmapper.Get(entity)
			default:
				log.Fatalln("unsupported tile type encountered")
			}

			render.Image = tile.Sprite

		default:
			// empty  cell, this is  where the player[s]  move. No
			// sprite required since  every level has a background
			// image.
			entity := emptymapper.New()
			pos, _ = emptymapper.Get(entity)
		}

		// however, every tile has a position
		pos.Update(point.X*tilesize, point.Y*tilesize, tilesize)
	}

	// not part of the grid, but add them as well
	entity := ptmapper.New()
	_, particle := ptmapper.Get(entity)
	particle.Index = assets.Tiles['*'].Particle
	particle.Particles = assets.Tiles['*'].Particles

	return &Grid{
		Size:     len(mapslice),
		Tilesize: tilesize,
		Width:    game.ScreenWidth,
		Height:   game.ScreenHeight,
		TilesX:   game.ScreenWidth / tilesize,
		TilesY:   game.ScreenHeight / tilesize,
		Map:      mapslice,
	}
}

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

	if !solid {
		return false, nil
	}

	// set to true,  ifwe 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)
	repr.Println(newpos)

	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
			fmt.Println("east X=0")
		case West:
			X = grid.Width - grid.Tilesize
			fmt.Println("west X=max")
		case South:
			Y = 0
			fmt.Println("south y=0")
		case North:
			Y = grid.Height - grid.Tilesize
			fmt.Println("north y=max")
		}

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

	return false, nil
}
