class_name StandardWorldGenerator
extends WorldGenerator

@export
var rooms: Array[PackedScene] = []

@export
var max_room_path_length: int = 10

@export
var spawn_room: PackedScene = null

# Rooms are sorted into these arrays based on their exits
# Each PackedScene is a StandardRoom
var _rooms_left: Array[PackedScene] = []
var _rooms_right: Array[PackedScene] = []
var _rooms_top: Array[PackedScene] = []
var _rooms_bottom: Array[PackedScene] = []
var _rooms_blocking: Array[PackedScene] = []

var _progress_tracker: ProgressTracker = ProgressTracker.new(1)

func _generate(map: TileMap) -> void:

	self._sort_rooms(_progress_tracker.next_step("Sorting Rooms"))

	# Create initial room
	var init_room = self._create_initial_room(map, self.spawn_room, _progress_tracker.next_step("Creating Initial Room"))
	init_room.queue_free.call_deferred()

	# Create rooms
	self._create_rooms(map, init_room, self.max_room_path_length, _progress_tracker.next_step("Creating Rooms"))

func _sort_rooms(step_tracker: ProgressStepTracker, validate: bool = false) -> void:
	step_tracker.complete.call_deferred()
	
	step_tracker.set_substeps(len(self.rooms))
	for room_scene in rooms:
		step_tracker.substep(room_scene.get_name())
		var room: StandardRoom = room_scene.instantiate()
		if room.left:
			self._rooms_left.append(room_scene)
		if room.right:
			self._rooms_right.append(room_scene)
		if room.top:
			self._rooms_top.append(room_scene)
		if room.bottom:
			self._rooms_bottom.append(room_scene)
		if room.is_block():
			self._rooms_blocking.append(room_scene)
			
		room.queue_free()
	
	if len(self._rooms_left) == 0:
		push_error("0 left rooms!")
	if len(self._rooms_right) == 0:
		push_error("0 right rooms!")
	if len(self._rooms_top) == 0:
		push_error("0 top rooms!")
	if len(self._rooms_bottom) == 0:
		push_error("0 bottom rooms!")
	if len(self._rooms_blocking) == 0:
		push_warning("0 blocking rooms!")

# Create initial room 
func _create_initial_room(map: TileMap, room: PackedScene, step_tracker: ProgressStepTracker) -> StandardRoom:
	step_tracker.complete.call_deferred()

	# Instantiate spawn room
	var spawn_room: StandardRoom = room.instance()
	
	# Copy spawn room into map
	spawn_room.copy_to_map(map, Vector2i(0, 0))

	return spawn_room


# Create rooms by randomly selecting from the available rooms in every direction with openings from the current room
# where the maximum path length has not been exceeded and the room does not overlap with any other room.
#
# Each room is marked with boolean flags for each direction that it has an opening to another room.
# 
# This is done recursively until the maximum path length is exceeded or there are no more rooms to add
func _create_rooms(map: TileMap, parent_room: StandardRoom, max_path_length: int, step_tracker: ProgressStepTracker) -> void:
	step_tracker.complete.call_deferred()

	var exits: Array[Vector2i] = parent_room.get_exits()

	# If the maximum path length has been exceeded, stop
	if max_path_length <= 0:
		return
	
	# If there are no more exits, stop
	if len(exits) == 0:
		return
	
	# If there are no more rooms to add, stop
	if len(self._rooms_left) == 0 and len(self._rooms_right) == 0 and len(self._rooms_top) == 0 and len(self._rooms_bottom) == 0:
		push_warning("No more rooms to add!")
		return
	
	# For every exit, try to add a room
	for exit in exits:
		# Find exit pos in current room
		var exit_pos: Vector2i = parent_room.get_exit_pos(exit)

		var room_edge_pos: Vector2i = exit_pos + Vector2i(parent_room.position)
		
		# If there is already a room at this position, skip it
		if map.get_cellv(room_edge_pos) != -1:
			continue
		
		# Get the rooms that can be added in this direction
		var rooms: Array[PackedScene] = self._get_rooms(exit)
		
		# If there are no rooms that can be added in this direction, skip it
		if len(rooms) == 0:
			continue
		
		# Get the room to add
		var room: PackedScene = rooms[randi() % len(rooms)]
		
		# Instantiate the room
		var room_instance: StandardRoom = room.instance()
		room_instance.position = room_edge_pos + (room_instance.size / 2)
		room_instance.queue_free.call_deferred()
		
		# Copy the room into the map
		room_instance.copy_to_map(map, Vector2i(room_instance.position))
		
		# Create rooms from the exits of the room that was just added
		self._create_rooms(map, room_instance, max_path_length - 1, step_tracker.next_step("Creating Rooms"))
	
	# If there are no more exits, stop
	return

func _get_rooms(exit: Vector2i) -> Array[PackedScene]:
	if exit == Vector2i.LEFT:
		return self._rooms_left
	elif exit == Vector2i.RIGHT:
		return self._rooms_right
	elif exit == Vector2i.UP:
		return self._rooms_top
	elif exit == Vector2i.DOWN:
		return self._rooms_bottom
	else:
		push_error("Invalid exit: " + str(exit))
		return []

func _get_progress_tracker() -> ProgressTracker:
	return self._progress_tracker
