package model

import (
	"mvvasilev/last_light/engine"

	"github.com/gdamore/tcell/v2"
)

type Map interface {
	Size() engine.Size
	Tiles() [][]Tile
	ExploredTiles() map[engine.Position]Tile
	ExploredTileStyle() tcell.Style
	DefaultTile() Tile

	PlayerSpawnPoint() *Map_PlayerSpawnPointComponent
	Rooms() *Map_RoomsComponent
	NextLevelStaircase() *Map_NextLevelStaircaseComponent
	PreviousLevelStaircase() *Map_PreviousLevelStaircaseComponent
}

type Map_PlayerSpawnPointComponent struct {
	Position engine.Position
}

type Map_RoomsComponent struct {
	Rooms []engine.BoundingBox
}

type Map_NextLevelStaircaseComponent struct {
	Position engine.Position
}

type Map_PreviousLevelStaircaseComponent struct {
	Position engine.Position
}

type BaseMap struct {
	size          engine.Size
	tiles         [][]Tile
	exploredTiles map[engine.Position]Tile
	exploredStyle tcell.Style
	defaultTile   Tile

	playerSpawnPos *Map_PlayerSpawnPointComponent
	rooms          *Map_RoomsComponent
	nextLevel      *Map_NextLevelStaircaseComponent
	prevLevel      *Map_PreviousLevelStaircaseComponent
}

func CreateMap(size engine.Size, tiles [][]Tile, exploredStyle tcell.Style, defaultTile Tile, components ...func(*BaseMap)) Map {
	m := &BaseMap{
		size:          size,
		tiles:         tiles,
		exploredTiles: make(map[engine.Position]Tile, 0),
		exploredStyle: exploredStyle,
		defaultTile:   defaultTile,
	}

	for _, c := range components {
		c(m)
	}

	return m
}

func (m *BaseMap) Size() engine.Size {
	return m.size
}

func (m *BaseMap) Tiles() [][]Tile {
	return m.tiles
}

func (m *BaseMap) ExploredTiles() map[engine.Position]Tile {
	return m.exploredTiles
}

func (m *BaseMap) ExploredTileStyle() tcell.Style {
	return m.exploredStyle
}

func (m *BaseMap) DefaultTile() Tile {
	return m.defaultTile
}

func (m *BaseMap) PlayerSpawnPoint() *Map_PlayerSpawnPointComponent {
	return m.playerSpawnPos
}

func (m *BaseMap) Rooms() *Map_RoomsComponent {
	return m.rooms
}

func (m *BaseMap) NextLevelStaircase() *Map_NextLevelStaircaseComponent {
	return m.nextLevel
}

func (m *BaseMap) PreviousLevelStaircase() *Map_PreviousLevelStaircaseComponent {
	return m.prevLevel
}

func Map_WithRooms(rooms []engine.BoundingBox) func(*BaseMap) {
	return func(bm *BaseMap) {
		bm.rooms = &Map_RoomsComponent{
			Rooms: rooms,
		}
	}
}

func Map_WithPlayerSpawnPoint(pos engine.Position) func(*BaseMap) {
	return func(bm *BaseMap) {
		bm.playerSpawnPos = &Map_PlayerSpawnPointComponent{
			Position: pos,
		}
	}
}

func Map_WithNextLevelStaircase(pos engine.Position) func(*BaseMap) {
	return func(bm *BaseMap) {
		bm.nextLevel = &Map_NextLevelStaircaseComponent{
			Position: pos,
		}
	}
}

func Map_WithPreviousLevelStaircase(pos engine.Position) func(*BaseMap) {
	return func(bm *BaseMap) {
		bm.prevLevel = &Map_PreviousLevelStaircaseComponent{
			Position: pos,
		}
	}
}

func Map_SetTileAt(bm Map, x int, y int, t Tile) Tile {
	if !Map_IsInBounds(bm, x, y) {
		return bm.DefaultTile()
	}

	bm.Tiles()[y][x] = t

	return bm.Tiles()[y][x]
}

func Map_TileAt(bm Map, x int, y int) Tile {
	if !Map_IsInBounds(bm, x, y) {
		return bm.DefaultTile()
	}

	tile := bm.Tiles()[y][x]

	if tile == nil {
		return bm.DefaultTile()
	}

	return tile
}

func Map_IsInBounds(bm Map, x, y int) bool {
	if x < 0 || y < 0 {
		return false
	}

	if x >= bm.Size().Width() || y >= bm.Size().Height() {
		return false
	}

	return true
}

func Map_ExploredTileAt(bm Map, x, y int) Tile {
	return bm.ExploredTiles()[engine.PositionAt(x, y)]
}

func Map_MarkExplored(bm Map, x, y int) {
	if !Map_IsInBounds(bm, x, y) {
		return
	}

	tile := Map_TileAt(bm, x, y)

	symbol, _ := tile.DefaultPresentation()

	bm.ExploredTiles()[engine.PositionAt(x, y)] = &BaseTile{
		defaultSymbol: symbol,
		defaultStyle:  bm.ExploredTileStyle(),
	}
}