package grid

import (
	"image"
	"log"
	"openquell/assets"
	"openquell/components"
	"openquell/config"
	"openquell/observers"

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

type Grid struct {
	World          *ecs.World
	Width          int
	Height         int
	Size           int
	Tilesize       int
	TilesX, 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(world *ecs.World,
	tilesize, width, height 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](world)

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

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

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

	obsmapper := generic.NewMap4[
		components.Position,
		components.Velocity,
		components.Renderable,
		components.Obstacle](world)

	transmapper := generic.NewMap3[
		components.Position,
		components.Renderable,
		components.Transient](world)

	doormapper := generic.NewMap3[
		components.Position,
		components.Renderable,
		components.Destroyable](world)

	var pos *components.Position
	var vel *components.Velocity
	var render *components.Renderable
	var transient *components.Transient
	var player *components.Player
	var destroyable *components.Destroyable

	playerID := ecs.ComponentID[components.Player](world)
	obstacleID := ecs.ComponentID[components.Obstacle](world)

	observer := observers.GetGameObserver(world)

	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, vel, render, player = playermapper.Get(entity)
				observer.AddEntity(entity, playerID)
				vel.Speed = config.PLAYERSPEED
				player.IsPrimary = tile.IsPrimary
				player.Sprites = tile.Tiles
				player.LoopPos = &components.Position{Cellsize: tilesize}
			case tile.Collectible:
				entity := colmapper.New()
				pos, render, _ = colmapper.Get(entity)
			case tile.Obstacle:
				entity := obsmapper.New()
				pos, vel, render, _ = obsmapper.Get(entity)
				vel.Direction = tile.Direction
				vel.PointingAt = tile.Direction
				vel.Speed = config.PLAYERSPEED
				observer.AddEntity(entity, obstacleID)
			case tile.Transient:
				entity := transmapper.New()
				pos, render, transient = transmapper.Get(entity)
				transient.Sprites = tile.TileNames
			case tile.Destroyable:
				entity := doormapper.New()
				pos, render, destroyable = doormapper.Get(entity)
				destroyable.Sprites = tile.Tiles
			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)
	}

	return &Grid{
		Size:     len(mapslice),
		Tilesize: tilesize,
		Width:    width,
		Height:   height,
		Map:      mapslice,
		World:    world,
		TilesX:   width / tilesize,
		TilesY:   height / tilesize,
	}
}

// return the tile the moving object would end up on if indeed moving
func (grid *Grid) GetTile(
	position *components.Position,
	velocity *components.Velocity) *assets.Tile {

	newpoint := image.Point{
		int(position.X+velocity.Data.X) / grid.Tilesize,
		int(position.Y+velocity.Data.Y) / grid.Tilesize,
	}

	tile := grid.Map[newpoint]
	return tile
}

func (grid *Grid) RemoveTile(point image.Point) {
	delete(grid.Map, point)
}

func (grid *Grid) SetFloorTile(point image.Point) {
	grid.Map[point] = assets.Tiles["floor"]
}

func (grid *Grid) SetSolidTile(tile *assets.Tile, point image.Point) {
	solidmapper := generic.NewMap4[
		components.Position,
		components.Renderable,
		components.Tilish,
		components.Solid](grid.World)

	grid.Map[point] = tile

	entity := solidmapper.New()
	pos, render, _, _ := solidmapper.Get(entity)

	render.Image = tile.Sprite
	pos.Update(point.X*grid.Tilesize, point.Y*grid.Tilesize, grid.Tilesize)
}
