@tool
# ############################################################################ #
# Copyright © 2018-2022 Paul Joannon
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
# Licensed under the MIT License.
# See LICENSE in the project root for license information.
# ############################################################################ #

extends Control

# Hiding this type to prevent registration of "private" nodes.
# See https://github.com/godotengine/godot-proposals/issues/1047
# class_name InkPreviewPanel

# ############################################################################ #
# Imports
# ############################################################################ #

var InkPlayerFactory := preload("res://addons/inkgd/ink_player_factory.gd") as GDScript

# ############################################################################ #
# Enums
# ############################################################################ #

enum StoryOrigin {
	CONFIGURATION,
	FILE
}

# ############################################################################ #
# Constants
# ############################################################################ #

const NAME = "name"
const STORY_ORIGIN = "story_origin"
const FILE_PATH = "file_path"

# ############################################################################ #
# Public Properties
# ############################################################################ #

var editor_interface: InkEditorInterface
var configuration: InkConfiguration
var progress_texture: AnimatedTexture

# ############################################################################ #
# Private Properties
# ############################################################################ #

var _scrollbar_max_value = -1
var _current_story_index = -1

var _custom_stories: Array = []
var _available_stories: Array = []

var _file_dialog = EditorFileDialog.new()

var _ink_player = InkPlayerFactory.create()

# ############################################################################ #
# On Ready | Private Properties
# ############################################################################ #

@onready var _play_icon = get_theme_icon("Play", "EditorIcons")

# ############################################################################ #
# On Ready | Private Nodes
# ############################################################################

@onready var _command_strip = find_child("CommandStripHBoxContainer")

@onready var _pick_story_button = _command_strip.get_node("PickStoryOptionButton")
@onready var _load_story_button = _command_strip.get_node("LoadStoryButton")
@onready var _start_button = _command_strip.get_node("StartButton")
@onready var _stop_button = _command_strip.get_node("StopButton")
@onready var _clear_button = _command_strip.get_node("ClearButton")

@onready var _scroll_container = find_child("ScrollContainer")
@onready var _story_container = _scroll_container.get_node("MarginContainer/StoryVBoxContainer")

@onready var _choice_area_container = find_child("ChoicesAreaVBoxContainer")
@onready var _choices_container = _choice_area_container.get_node("ChoicesVBoxContainer")

# ############################################################################ #
# Overrides
# ############################################################################ #

func _ready():
	# FIXME: This needs investigating.
	# Sanity check. It seems the editor instantiates tools script on their
	# own, probably to add them to its tree. In that case, they won't have
	# their dependencies injected, so we're not doing anything.
	if editor_interface == null || configuration == null || progress_texture == null:
		print("[inkgd] [INFO] Ink Preview Tab: dependencies not met, ignoring.")
		return

	add_child(_ink_player)

	_connect_signals()
	_apply_configuration()
	_update_story_picker()

	var load_icon = get_theme_icon("Load", "EditorIcons")
	var stop_icon = get_theme_icon("Stop", "EditorIcons")
	var clear_icon = get_theme_icon("Clear", "EditorIcons")

	_start_button.icon = _play_icon
	_load_story_button.icon = load_icon
	_stop_button.icon = stop_icon
	_clear_button.icon = clear_icon

	_stop_button.visible = false

	_choice_area_container.custom_minimum_size = Vector2(200, 0) * editor_interface.scale
	_choice_area_container.visible = false

	_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
	add_child(_file_dialog)

# ############################################################################ #
# Signal Receivers
# ############################################################################ #

func _start_button_pressed():
	var file_path = _get_current_story_file_path()
	if file_path == null:
		return

	print("[inkgd] [INFO] Previewing %s" % file_path)
	_clear_content()

	_ink_player.destroy()
	_ink_player.ink_file = load(file_path)

	_start_button.icon = progress_texture
	_disable_command_strip(true)

	_ink_player.create_story()


func _stop_button_pressed():
	_start_button.visible = true
	_stop_button.visible = false
	_choice_area_container.visible = false

	_ink_player.destroy()

	_clear_choices()
	_clear_content()


func _story_loaded(successfully: bool):
	_disable_command_strip(false)
	_start_button.icon = _play_icon

	if !successfully:
		return

	_start_button.visible = false
	_stop_button.visible = true

	_ink_player.allow_external_function_fallbacks = true
	_continue_story()

func _pick_story_button_selected(index):
	if _current_story_index != index:
		_stop_button_pressed()

	_current_story_index = index


func _load_story_button_pressed():
	_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
	_file_dialog.access = FileDialog.ACCESS_RESOURCES
	_file_dialog.add_filter("*.json;Compiled Ink story")
	_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)


func _choice_button_pressed(index):
	_clear_choices()

	_ink_player.choose_choice_index(index)
	_continue_story()


func _on_file_selected(path: String):
	if _custom_stories.has(path):
		return

	for story_configuration in self.configuration.stories:
		var target_file_path = self.configuration.get_target_file_path(story_configuration)
		if target_file_path == path:
			return

	_custom_stories.append(path)
	_apply_configuration()
	_update_story_picker(path)


func _scrollbar_changed():
	var max_value = _scroll_container.get_v_scroll_bar().max_value

	if _scrollbar_max_value == max_value && _scrollbar_max_value != -1:
		return

	_scrollbar_max_value = max_value
	_scroll_container.scroll_vertical = max_value


func _configuration_changed():
	# Cleaning everything on configuration change may end up being frustrating.
	# But for now, it's going to make everybody's life easier.
	_stop_button_pressed()
	_apply_configuration()
	_update_story_picker()


# ############################################################################ #
# Private Methods
# ############################################################################ #

func _apply_configuration():
	_available_stories.clear()

	_current_story_index = -1
	_pick_story_button.selected = _current_story_index

	var i = 0
	for story_configuration in self.configuration.stories:
		var target_file_path = self.configuration.get_target_file_path(story_configuration)
		if target_file_path != null && !target_file_path.is_empty():
			_available_stories.append({
				NAME: "Story %d - %s" % [i + 1, target_file_path.get_file()],
				FILE_PATH: target_file_path,
				STORY_ORIGIN: StoryOrigin.CONFIGURATION
			})
		i += 1

	var j = 0
	for custom_story_path in _custom_stories:
		if custom_story_path != null && !custom_story_path.is_empty():
			_available_stories.append({
				NAME: custom_story_path.get_file(),
				FILE_PATH: ProjectSettings.localize_path(custom_story_path),
				STORY_ORIGIN: StoryOrigin.FILE
			})
		j += 1


func _update_story_picker(selected_path = null):
	_pick_story_button.clear()

	var i = 0
	for story in _available_stories:
		_pick_story_button.add_item(story[NAME], i)
		if selected_path != null && story[FILE_PATH] == selected_path:
			_current_story_index = i
			_pick_story_button.selected = _current_story_index
		i += 1

	if _available_stories.size() > 0:
		if _current_story_index == -1:
			_current_story_index = 0
			_pick_story_button.selected = _current_story_index

		_pick_story_button.visible = true
	else:
		_current_story_index = -1
		_pick_story_button.selected = _current_story_index

		_pick_story_button.visible = false


func _continue_story():
	while _ink_player.can_continue:
		var text = _ink_player.continue_story()

		if text.right(text.length() - 1) == "\n":
			text.erase(text.length() - 1, 1)

		var text_label = Label.new()
		text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
		text_label.text = text

		_story_container.add_child(text_label)

		var tags = _ink_player.current_tags
		if !tags.is_empty():
			var tag_label = Label.new()
			tag_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
			tag_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
			tag_label.text = "# " + ", ".join(PackedStringArray(tags))
			tag_label.add_theme_color_override("font_color", Color(1, 1, 1, 0.4))

			_story_container.add_child(tag_label)

	var separator = HSeparator.new()
	_story_container.add_child(separator)

	if _ink_player.current_choices.size() > 0:
		var i = 0
		for choice in _ink_player.current_choices:
			var button = Button.new()
			button.text = choice.text
			button.connect("pressed", Callable(self, "_choice_button_pressed").bind(i))

			_choices_container.add_child(button)
			i += 1

		_choice_area_container.visible = true
	else:
		var label = Label.new()
		label.text = "End of the story."
		label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT

		_story_container.add_child(label)

		_choice_area_container.visible = false


func _get_current_story_file_path():
	if _current_story_index >= 0 && _current_story_index < _available_stories.size():
		return _available_stories[_current_story_index][FILE_PATH]
	else:
		return null


func _clear_content():
	for child in _story_container.get_children():
		_story_container.remove_child(child)
		child.queue_free()


func _clear_choices():
	for child in _choices_container.get_children():
		_choices_container.remove_child(child)
		child.queue_free()

func _disable_command_strip(disabled: bool):
	_pick_story_button.disabled = disabled
	_load_story_button.disabled = disabled
	_start_button.disabled = disabled
	_stop_button.disabled = disabled
	_clear_button.disabled = disabled

func _connect_signals():
	if configuration != null:
		var is_signal_connected := configuration.is_connected(
				"story_configuration_changed",
				Callable(self, "_configuration_changed")
		)

		if !is_signal_connected:
			configuration.connect(
					"story_configuration_changed",
					Callable(self, "_configuration_changed")
			)

	_ink_player.connect("loaded", Callable(self, "_story_loaded"))
	_pick_story_button.connect("item_selected", Callable(self, "_pick_story_button_selected"))
	_load_story_button.connect("pressed", Callable(self, "_load_story_button_pressed"))
	_start_button.connect("pressed", Callable(self, "_start_button_pressed"))
	_stop_button.connect("pressed", Callable(self, "_stop_button_pressed"))
	_clear_button.connect("pressed", Callable(self, "_clear_content"))

	_scroll_container.get_v_scroll_bar().connect("changed", Callable(self, "_scrollbar_changed"))
