extends ESCGame


const VERB_USE = "use"
const VERB_WALK = "walk"


"""
Implement methods to react to inputs.

- left_click_on_bg(position: Vector2)
- right_click_on_bg(position: Vector2)
- left_double_click_on_bg(position: Vector2)

- element_focused(element_id: String)
- element_unfocused()

- left_click_on_item(item_global_id: String, event: InputEvent)
- right_click_on_item(item_global_id: String, event: InputEvent)
- left_double_click_on_item(item_global_id: String, event: InputEvent)

- left_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent)
- right_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent)
- left_double_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent)
- inventory_item_focused(inventory_item_global_id: String)
- inventory_item_unfocused()
- open_inventory()
- close_inventory()

- mousewheel_action(direction: int)

- hide_ui()
- show_ui()

- pause_game()
- unpause_game()
- show_main_menu()
- hide_main_menu()

- apply_custom_settings()

- _on_event_done(event_name: String)
"""

# Value to use for `device` argument to various `Input.get_joy` methods.
const JOY_DEVICE = 0

# See https://docs.godotengine.org/en/stable/tutorials/inputs/controllers_gamepads_joysticks.html?#dead-zone
const DEADZONE = 0.2

# Multiplier to apply to axis when it exceeds DEADZONE.
const AXIS_WEIGHT = 50.0

# JOY_BUTTON_X corresponds to the "X" button on an XBox controller
# or the Square button on a Playstation controller. These appear to
# map to the "primary action," in practice, so we treat it like a left click.
const PRIMARY_ACTION_BUTTON = JOY_BUTTON_X

# JOY_BUTTON_Y corresponds to the "Y" button on an XBox controller
# or the Triangle button on a Playstation controller. These appear to
# map to the "secondary action," in practice, so we treat it like a right click.
const CHANGE_VERB_BUTTON = JOY_BUTTON_Y

# Input action for use by InputMap
const ESC_UI_CHANGE_VERB_ACTION = "esc_change_verb"

# true when a gamepad is connected.
var _is_gamepad_connected = false

# Tracks the mouse's current position onscreen.
var _current_mouse_pos: Vector2 = Vector2.ZERO

var targeted_node: Node


func _ready():
	hide_ui()
	$ui/tooltip.connect("tooltip_size_updated", Callable(self, "update_tooltip_following_mouse_position"))
	escoria.main.room_ready.connect(_on_room_ready)

func _enter_tree():
	initialize_esc_game()

	var room_selector_parent = $ui/HBoxContainer/VBoxContainer

	if ESCProjectSettingsManager.get_setting(ESCProjectSettingsManager.ENABLE_ROOM_SELECTOR) \
		and room_selector_parent.get_node_or_null("room_select") == null:

		room_selector_parent.add_child(
			preload(
				"res://addons/escoria-core/ui_library/tools/room_select" +\
				"/room_select.tscn"
			).instantiate()
		)

	var input_handler = Callable(self._process_input)
	escoria.inputs_manager.register_custom_input_handler(input_handler)

	_is_gamepad_connected = Input.is_joy_known(JOY_DEVICE)
	if _is_gamepad_connected:
		_on_gamepad_connected()

	Input.connect("joy_connection_changed", Callable(self, "_on_joy_connection_changed"))


func _exit_tree():
	escoria.inputs_manager.register_custom_input_handler(null)
	Input.disconnect("joy_connection_changed", Callable(self, "_on_joy_connection_changed"))
	if _is_gamepad_connected:
		_on_gamepad_disconnected()


func _input(event: InputEvent) -> void:
	super._input(event)
	if escoria.get_escoria().is_ready_for_inputs():
		if event is InputEventMouseMotion:
			_current_mouse_pos = get_global_mouse_position()
			update_tooltip_following_mouse_position()


# https://github.com/godotengine/godot-demo-projects/blob/3.4-585455e/misc/joypads/joypads.gd
# was informative in wiring up the gamepad properly.

func _on_gamepad_connected():
	set_physics_process(true)

	var primary_event = InputEventJoypadButton.new()
	primary_event.button_index = PRIMARY_ACTION_BUTTON
	InputMap.add_action(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)
	InputMap.action_add_event(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION, primary_event)

	var verb_event = InputEventJoypadButton.new()
	verb_event.button_index = CHANGE_VERB_BUTTON
	InputMap.add_action(ESC_UI_CHANGE_VERB_ACTION)
	InputMap.action_add_event(ESC_UI_CHANGE_VERB_ACTION, verb_event)


func _on_gamepad_disconnected():
	InputMap.action_erase_events(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)
	InputMap.erase_action(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION)

	InputMap.action_erase_events(ESC_UI_CHANGE_VERB_ACTION)
	InputMap.erase_action(ESC_UI_CHANGE_VERB_ACTION)

	set_physics_process(false)
	_is_gamepad_connected = false


func _on_joy_connection_changed(device: int, connected: bool) -> void:
	if device != JOY_DEVICE:
		return
	elif connected:
		_on_gamepad_connected()
	else:
		_on_gamepad_disconnected()


func _process(_delta) -> void:
	if !_is_gamepad_connected:
		return

	var x = Input.get_joy_axis(JOY_DEVICE, JOY_AXIS_LEFT_X)
	var y = Input.get_joy_axis(JOY_DEVICE, JOY_AXIS_LEFT_Y)
	var delta_x = int(x * AXIS_WEIGHT) if abs(x) > DEADZONE else 0
	var delta_y = int(y * AXIS_WEIGHT) if abs(y) > DEADZONE else 0
	if delta_x or delta_y:
		var direction: Vector2
		direction.x = delta_x
		direction.y = delta_y
		escoria.logger.trace(self, "gamepad direction: %s" % [direction])
		var viewport = get_viewport()
		viewport.warp_mouse(viewport.get_mouse_position() + direction)


func _process_input(event: InputEvent, is_default_state: bool) -> bool:
	if not is_default_state:
		# ESCBackground is not guaranteed to be set, as we may be on
		# the "New Game" screen.
		return false
	elif _is_gamepad_connected and event is InputEventJoypadButton:
		escoria.logger.trace(self, "InputEventJoypadButton: %s" % [event.as_text()])
		if event.is_action_pressed(escoria.inputs_manager.ESC_UI_PRIMARY_ACTION):
			# Admittedly, this breaks abstraction barriers and is completely
			# inappropriate, but it's what works right now.
			escoria.inputs_manager._on_left_click_on_bg(get_global_mouse_position())
			return true
		elif event.is_action_pressed(ESC_UI_CHANGE_VERB_ACTION):
			mousewheel_action(1)
			return true
	return false


## BACKGROUND ##

func left_click_on_bg(position: Vector2) -> void:
	if escoria.main.current_scene.player:
		escoria.action_manager.do(
			escoria.action_manager.ACTION.BACKGROUND_CLICK,
			[escoria.main.current_scene.player.global_id, position],
			true
		)
		$mouse_layer/verbs_menu.set_by_name(VERB_WALK)
		$mouse_layer/verbs_menu.clear_tool_texture()
		$mouse_layer/verbs_menu.action_manually_changed = false
	close_inventory()

func right_click_on_bg(position: Vector2) -> void:
	left_double_click_on_bg(position)

func left_double_click_on_bg(position: Vector2) -> void:
	if escoria.main.current_scene.player:
		escoria.action_manager.do(
			escoria.action_manager.ACTION.BACKGROUND_CLICK,
			[escoria.main.current_scene.player.global_id, position, true],
			true
		)
		$mouse_layer/verbs_menu.set_by_name(VERB_WALK)
		$mouse_layer/verbs_menu.clear_tool_texture()
		$mouse_layer/verbs_menu.action_manually_changed = false
	close_inventory()

## ITEM/HOTSPOT FOCUS ##

func element_focused(element_id: String) -> void:
	var target_obj: ESCItem = escoria.object_manager.get_object(element_id).node

	# This code is commented to demonstrate how to implement a simple hover
	# behaviour on an item.
	#if target_obj.has_method("get_sprite") and target_obj.get_sprite().texture:
		#targeted_node = target_obj.get_sprite()
		#targeted_node.modulate = Color.GRAY

	$ui/tooltip.set_target(target_obj.tooltip_name)

	if escoria.action_manager.current_action != VERB_USE \
			and escoria.action_manager.current_tool == null \
			and target_obj is ESCItem:

			if target_obj.is_exit:
				if element_id.contains("_l_"):
					$mouse_layer/verbs_menu.set_by_name("exit_left", "walk")
				elif element_id.contains("_r_"):
					$mouse_layer/verbs_menu.set_by_name("exit_right", "walk")
				else:
					$mouse_layer/verbs_menu.set_by_name("walk")
			elif not $mouse_layer/verbs_menu.action_manually_changed:
				$mouse_layer/verbs_menu.set_by_name(target_obj.default_action)

func element_unfocused() -> void:
	$ui/tooltip.set_target("")
	if not $mouse_layer/verbs_menu.action_manually_changed:
		$mouse_layer/verbs_menu.set_by_name("walk")


	# This code is commented to demonstrate how to implement a simple unhover
	# behaviour on an item.
	#if targeted_node != null:
		#targeted_node.modulate = Color.WHITE

## ITEMS ##
func left_click_on_item(item_global_id: String, event: InputEvent) -> void:
	var target_obj = escoria.object_manager.get_object(item_global_id).node

	# current_action will be empty if an event completes between when you stop
	# moving the mouse and when you click.
	if escoria.action_manager.current_action == "":
		if target_obj is ESCItem:
				$mouse_layer/verbs_menu.set_by_name(
					target_obj.default_action
				)

	escoria.action_manager.do(
		escoria.action_manager.ACTION.ITEM_LEFT_CLICK,
		[item_global_id, event],
		true
	)

	$mouse_layer/verbs_menu.clear_tool_texture()
	close_inventory()


func right_click_on_item(item_global_id: String, event: InputEvent) -> void:
	element_focused(item_global_id)
	var object = escoria.object_manager.get_object(item_global_id)
	if object != null:
		$mouse_layer/verbs_menu.set_by_name("look")
	escoria.action_manager.do(
		escoria.action_manager.ACTION.ITEM_RIGHT_CLICK,
		[item_global_id, event],
		true
	)

func left_double_click_on_item(item_global_id: String, event: InputEvent) -> void:
	escoria.action_manager.do(
		escoria.action_manager.ACTION.ITEM_LEFT_CLICK,
		[item_global_id, event],
		true
	)


## INVENTORY ##
func left_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent) -> void:
	escoria.action_manager.do(
		escoria.action_manager.ACTION.ITEM_LEFT_CLICK,
		[inventory_item_global_id, event]
	)

	if escoria.action_manager.current_action == VERB_USE:
		var object = escoria.object_manager.get_object(
			inventory_item_global_id
		)
		var item = object.node
		if item.has_method("get_sprite") and item.get_sprite().texture:
			$mouse_layer/verbs_menu.set_tool_texture(
				item.get_sprite().texture
			)
		elif item.inventory_item.texture_normal:
			$mouse_layer/verbs_menu.set_tool_texture(
				item.inventory_item.texture_normal
			)
		escoria.action_manager.current_tool = object

		if escoria.action_manager.current_target != null:
			$mouse_layer/verbs_menu.clear_tool_texture()
			$mouse_layer/verbs_menu.set_by_name(VERB_WALK)

func right_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent) -> void:
	element_focused(inventory_item_global_id)
	var object = escoria.object_manager.get_object(inventory_item_global_id)
	if object != null:
		escoria.action_manager.set_current_action("look")
	escoria.action_manager.do(
		escoria.action_manager.ACTION.ITEM_RIGHT_CLICK,
		[inventory_item_global_id, event],
		true
	)


func left_double_click_on_inventory_item(inventory_item_global_id: String, event: InputEvent) -> void:
	pass


func inventory_item_focused(inventory_item_global_id: String) -> void:
	if not $mouse_layer/verbs_menu.action_manually_changed:
		var item_node: ESCItem = escoria.object_manager.get_object(
				inventory_item_global_id
			).node
		$ui/tooltip.set_target(item_node.tooltip_name)
		$mouse_layer/verbs_menu.set_by_name(item_node.default_action_inventory)


func inventory_item_unfocused() -> void:
	$ui/tooltip.clear()
	if escoria.action_manager.current_action == VERB_WALK:
		$mouse_layer/verbs_menu.set_by_name(VERB_WALK)
		if $mouse_layer/verbs_menu.action_manually_changed:
			$mouse_layer/verbs_menu.action_manually_changed = false
	elif not $mouse_layer/verbs_menu.action_manually_changed \
			and escoria.action_manager.current_tool == null:
		$mouse_layer/verbs_menu.set_by_name(VERB_WALK)


func open_inventory():
	$ui/inventory_ui.show_inventory()


func close_inventory():
	$ui/inventory_ui.hide_inventory()


func mousewheel_action(direction: int):
	$mouse_layer/verbs_menu.iterate_actions_cursor(direction)


func hide_ui():
	$ui/inventory_ui.propagate_call("set_visible", [false], true)
	$ui/tooltip.propagate_call("set_visible", [false], true)
	$ui/HBoxContainer/VBoxContainer.visible = false
	$ui/HBoxContainer/VBoxContainer/MenuButton.visible = false


func show_ui():
	$ui/inventory_ui.propagate_call("set_visible", [true], true)
	$ui/tooltip.propagate_call("set_visible", [true], true)
	$ui/HBoxContainer/VBoxContainer.visible = true
	$ui/HBoxContainer/VBoxContainer/MenuButton.visible = true


func hide_main_menu():
	show_ui()
	escoria.current_state = escoria.GAME_STATE.DEFAULT
	if get_node(main_menu).visible:
		get_node(main_menu).hide()

func show_main_menu():
	if escoria.current_state == escoria.GAME_STATE.PAUSED:
		return
	escoria.current_state = escoria.GAME_STATE.PAUSED
	hide_ui()
	if not get_node(main_menu).visible:
		get_node(main_menu).reset()
		get_node(main_menu).show()

func unpause_game():
	show_ui()
	escoria.current_state = escoria.GAME_STATE.DEFAULT
	if get_node(pause_menu).visible:
		get_node(pause_menu).hide()
		escoria.object_manager.get_object(ESCObjectManager.SPEECH).node.resume()
		escoria.main.current_scene.game.show_ui()
		escoria.main.current_scene.show()
		escoria.set_game_paused(false)

func pause_game():
	if escoria.current_state == escoria.GAME_STATE.PAUSED:
		return
	show_ui()
	escoria.current_state = escoria.GAME_STATE.PAUSED
	if not get_node(pause_menu).visible:
		get_node(main_menu).reset()
		get_node(pause_menu).reset()
		get_node(pause_menu).set_save_enabled(
			escoria.save_manager.save_enabled
		)
		get_node(pause_menu).show()
		escoria.object_manager.get_object(ESCObjectManager.SPEECH).node.pause()
		escoria.main.current_scene.game.hide_ui()
		escoria.main.current_scene.hide()
		escoria.set_game_paused(true)


func apply_custom_settings(custom_settings: Dictionary):
	if custom_settings.has("a_custom_setting"):
		escoria.logger.info(
			self,
			"custom setting value loaded: %s."
					% str(custom_settings["a_custom_setting"])
		)


func get_custom_data() -> Dictionary:
	return {
		"ui_type": "simplemouse"
	}


# Update the tooltip
func update_tooltip_following_mouse_position():
	_current_mouse_pos = get_global_mouse_position()
	var corrected_position = _current_mouse_pos \
		+ Vector2(32, -tooltip_node.size.y/2)

	# clamp TOP
	if tooltip_node.tooltip_distance_to_edge_top(_current_mouse_pos) <= mouse_tooltip_margin:
		corrected_position.y = mouse_tooltip_margin

	# clamp BOTTOM
	if tooltip_node.tooltip_distance_to_edge_bottom(_current_mouse_pos + tooltip_node.size) <= mouse_tooltip_margin:
		corrected_position.y = escoria.game_size.y - mouse_tooltip_margin - tooltip_node.size.y

	# clamp LEFT
	if tooltip_node.tooltip_distance_to_edge_left(_current_mouse_pos - tooltip_node.size/2) <= mouse_tooltip_margin:
		corrected_position.x = mouse_tooltip_margin

	# clamp RIGHT
	if tooltip_node.tooltip_distance_to_edge_right(_current_mouse_pos + tooltip_node.size/2) <= mouse_tooltip_margin:
		corrected_position.x = escoria.game_size.x - mouse_tooltip_margin - tooltip_node.size.x

	tooltip_node.position = corrected_position + tooltip_node.offset_from_cursor


func _on_action_finished():
	$mouse_layer/verbs_menu.clear_tool_texture()
	$mouse_layer/verbs_menu.iterate_actions_cursor(0)


func _on_event_done(_return_code: int, _event_name: String):
	if _return_code == ESCExecution.RC_OK:
		escoria.action_manager.clear_current_action()
		$ui/tooltip.set_target("")
		$mouse_layer/verbs_menu.set_by_name(VERB_WALK)


func _on_room_ready():
	$mouse_layer/verbs_menu.set_by_name(VERB_WALK)
	$mouse_layer/verbs_menu.action_manually_changed = false
