@tool
@icon("res://addons/escoria-core/design/esc_room.svg")
## A room in an Escoria based game.
extends Node2D
class_name ESCRoom


## Debugging displays for a room.
enum EditorRoomDebugDisplay {
	NONE, # No debug display
	CAMERA_LIMITS # Display the camera limits
}

## The global id of this room
@export var global_id: String = ""

## The ASHES script of this room
@export_file("*.esc", "*.ash") var esc_script: String = ""

## The player scene to use inside this room
@export var player_scene: PackedScene

## The camera limits available in this room
@export var camera_limits: Array = [Rect2()]: # (Array, Rect2)
	set = set_camera_limits

## The room's debug display mode.[br]
## Camera Limits: show a colored frame for each camera limit of the room.[br]
## None: no debug display
@export var editor_debug_mode: EditorRoomDebugDisplay = EditorRoomDebugDisplay.NONE:
	set = set_editor_debug_mode


## Container of the player scene instance.
var player

## Container of player camera
var player_camera: ESCCamera

## Container of game scene instance
var game

## Container of compiled ESCScript
var compiled_script: ESCScript

## Whether automatic transition are enabled or not. This is modified by
## the Room Manager.
var enabled_automatic_transitions = true

# Whether this room was run directly with Play Scene (F6)
var is_run_directly = false

###### @Tool properties ######
# Default font for tool display in the editor
var _tool_default_font: Font

# Start the random number generator when the camera limits should be displayed
func _enter_tree():
	if editor_debug_mode == EditorRoomDebugDisplay.CAMERA_LIMITS:
		randomize()


# Sanitize camera limits, add player node and set the global id to the
# name of this node if it's not set manually
func _ready():
	# Might as well just check here.
	if get_parent() == get_tree().root \
			and ESCProjectSettingsManager.get_setting(
				"application/run/main_scene"
			) != self.scene_file_path:
		is_run_directly = true

	var temp_control: Control = Control.new()
	_tool_default_font = temp_control.get_theme_default_font()
	temp_control.queue_free()

	child_entered_tree.connect(_on_child_entered_tree)

	if Engine.is_editor_hint():
		_connect_location_nodes()
		_validate_start_locations()
		return

	# If room has no ESCBackground child, add one
	var found_escbackground: bool = false
	for child in get_children():
		if child is ESCBackground:
			found_escbackground = true
			move_child(child, 0)
	if not found_escbackground:
		var esc_bg = ESCBackground.new()
		if not camera_limits.is_empty():
			esc_bg.set_size(camera_limits.front().size)
		add_child(esc_bg)
		move_child(esc_bg, 0)

	escoria.room_manager.init_room(self)


# Draw the camera limits visualization if enabled
func _draw():
	if not Engine.is_editor_hint():
		return
	if editor_debug_mode == EditorRoomDebugDisplay.NONE:
		return

	var camera_limits_colors: Array = [
		Color("red"), Color("blue"), Color("green")
	]

	# If there are more camera limits than colors defined for them, add more.
	if camera_limits.size() > camera_limits_colors.size():
		for i in camera_limits.size() - camera_limits_colors.size():
			camera_limits_colors.push_back(Color(randf(), randf(), randf(), 1.0))

	# Draw lines for camera limits
	for i in camera_limits.size():
		draw_rect(camera_limits[i], camera_limits_colors[i], false, 10.0)
		draw_string(
			_tool_default_font,
			Vector2(camera_limits[i].position.x + 30, camera_limits[i].position.y + 30),
			str(i),
			HORIZONTAL_ALIGNMENT_LEFT,
			-1,
			16,
			camera_limits_colors[i])

## Escoria editor plugin:[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |new_node|`Node`|Child node that entered the room's scene tree.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _on_child_entered_tree(new_node: Node):
	if Engine.is_editor_hint() and new_node is ESCLocation:
			_connect_location_nodes()

## Escoria editor plugin: Listen for any signals from ESCLocation indicating that the is_start_location attribute has been set/unset in order to update start location validation.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _connect_location_nodes() -> void:
	if not Engine.is_editor_hint():
		return
	_connect_location_nodes_in_tree(self)

## Escoria editor plugin:[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |node|`Node`|Node whose descendants should be scanned for `ESCLocation` children.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _connect_location_nodes_in_tree(node: Node) -> void:
	for n in node.get_children():
		if n is ESCLocation:
			if not n.is_connected("editor_is_start_location_set",_validate_start_locations):
				n.connect("editor_is_start_location_set", _validate_start_locations)

		if n.get_child_count() > 0:
			_connect_location_nodes_in_tree(n)


## Validate that we only have one start location for this scene. If we don't, call it out in the scene tree via configuration warnings. We may have to ignore a node if it's being removed/deleted from the scene tree.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |to_ignore|`ESCLocation`|Location node that should be excluded from validation (usually the one being removed).|no|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _validate_start_locations(to_ignore: ESCLocation = null):
	var esc_locations: Array = _find_esc_locations(self)
	var num_start_locations: int = 0

	for n in esc_locations:
		if n == to_ignore:
			continue

		num_start_locations += 1 if n.is_start_location else 0

	for n in esc_locations:
		if n == to_ignore:
			continue

		n.set_multiple_start_locations_exist(n.is_start_location and num_start_locations > 1)


## Escoria plugin for editor: get the list of ESCLocation nodes in the room. ####[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |node|`Node`|Root node to inspect when searching for `ESCLocation` instances.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns a `Array` value. (`Array`)
func _find_esc_locations(node: Node) -> Array:
	var esc_locations: Array = []

	for n in node.get_children():
		if n is ESCLocation:
			esc_locations.append(n)

		if n.get_child_count() > 0:
			esc_locations.append_array(_find_esc_locations(n))

	return esc_locations


# Set the camera limits
#
# #### Parameters
#
# - p_camera_limits: An array of Rect2Ds as camera limits
func set_camera_limits(p_camera_limits: Array) -> void:
	var fixed_camera_limits: Array = []
	var viewport_height = ProjectSettings.get_setting("display/window/size/viewport_height")
	for cam_limit in p_camera_limits:
		var fixed_camera_limit = Rect2(cam_limit)
		if fixed_camera_limit.size.y < viewport_height:
			fixed_camera_limit.size.y = viewport_height
		fixed_camera_limits.append(fixed_camera_limit)
	camera_limits = fixed_camera_limits

	queue_redraw()


# Set the editor debug mode
#
# #### Parameters
#
# - p_editor_debug_mode: The debug mode to set for the room
func set_editor_debug_mode(p_editor_debug_mode: int) -> void:
	editor_debug_mode = p_editor_debug_mode
	queue_redraw()
