## Caches the sprite location and terrains of each tile in the TileMapDual world grid.
class_name TileCache
extends Resource


##[br] Maps a cell coordinate to the stored tile data.
##[codeblock]
##  Dictionary{
##    key: Vector2i = # The coordinates of this tile in the world grid.
##    value: Dictionary{
##      'sid': int = # The Source ID of this tile.
##      'tile': Vector2i = # The coordinates of this tile in its Atlas.
##      'terrain': int = # The terrain assigned to this tile.
##    } = # The data stored at this tile.
##  }
##[/codeblock]
var cells := {}
func _init() -> void:
	pass

##[br] Updates specific cells of the TileCache based on the current layer data at those points.
##[br] Makes corrections in case the user accidentally places invalid tiles.
func update(world: TileMapLayer, edited: Array = cells.keys()) -> void:
	var tile_set := world.tile_set
	if tile_set == null:
		push_error('Attempted to update TileCache while tile set was null')
		return
	for cell in edited:
		# Invalid cells will be treated as empty and ignored
		var sid := world.get_cell_source_id(cell)
		if sid == -1:
			cells.erase(cell)
			continue
		if not tile_set.has_source(sid):
			continue
		var src = tile_set.get_source(sid)
		var tile := world.get_cell_atlas_coords(cell)
		if not src.has_tile(tile):
			continue
		var data := world.get_cell_tile_data(cell)
		if data == null:
			continue
		# Accidental cells should be reset to their previous value
		# They will be treated as unchanged
		if data.terrain == -1 or data.terrain_set != 0:
			if cell not in cells:
				world.erase_cell(cell)
				continue
			var cached: Dictionary = cells[cell]
			sid = cached.sid
			tile = cached.tile
			world.set_cell(cell, sid, tile)
			data = world.get_cell_tile_data(cell)
		cells[cell] = {'sid': sid, 'tile': tile, 'terrain': data.terrain}


## Returns the symmetric difference (xor) of two tile caches.
func xor(other: TileCache) -> Array[Vector2i]:
	var out: Array[Vector2i] = []
	for key in cells:
		if key not in other.cells or cells[key].terrain != other.cells[key].terrain:
			out.push_back(key)
	for key in other.cells:
		if key not in cells:
			out.push_back(key)
	return out


##[br] Returns the terrain value of the tile at the given cell coordinates.
##[br] Empty cells have a terrain of -1.
func get_terrain_at(cell: Vector2i) -> int:
	if cell not in cells:
		return -1
	return cells[cell].terrain
