@tool
extends DialogicEditor

var current_glossary: DialogicGlossary = null
var current_entry_name := ""
var current_entry := {}

################################################################################
##					BASICS
################################################################################

func _get_title() -> String:
	return "Glossary"


func _get_icon() -> Texture:
	var base_directory: String = self.get_script().get_path().get_base_dir()
	var icon_path := base_directory + "/icon.svg"
	return load(icon_path)


func _register() -> void:
	editors_manager.register_simple_editor(self)
	alternative_text = "Create and edit glossaries."


func _ready() -> void:
	if get_parent() is SubViewport:
		return

	var add_glossary_icon_path: String = self.get_script().get_path().get_base_dir() + "/add-glossary.svg"
	var add_glossary_icon := load(add_glossary_icon_path)
	%AddGlossaryFile.icon = add_glossary_icon

	%LoadGlossaryFile.icon = get_theme_icon('Folder', 'EditorIcons')
	%DeleteGlossaryFile.icon = get_theme_icon('Remove', 'EditorIcons')
	%DeleteGlossaryEntry.icon = get_theme_icon('Remove', 'EditorIcons')

	%DeleteGlossaryFile.pressed.connect(_on_delete_glossary_file_pressed)

	%AddGlossaryEntry.icon = get_theme_icon('Add', 'EditorIcons')
	%EntrySearch.right_icon = get_theme_icon('Search', 'EditorIcons')

	%GlossaryList.item_selected.connect(_on_GlossaryList_item_selected)
	%EntryList.item_selected.connect(_on_EntryList_item_selected)

	%DefaultColor.color_changed.connect(set_setting.bind('dialogic/glossary/default_color'))
	%DefaultCaseSensitive.toggled.connect(set_setting.bind('dialogic/glossary/default_case_sensitive'))

	%EntryCaseSensitive.icon = get_theme_icon("MatchCase", "EditorIcons")

	%EntryAlternatives.text_changed.connect(_on_entry_alternatives_text_changed)


func set_setting(value: Variant, setting: String)  -> void:
	ProjectSettings.set_setting(setting, value)
	ProjectSettings.save()


func _open(_argument: Variant = null) -> void:
	%DefaultColor.color = ProjectSettings.get_setting('dialogic/glossary/default_color', Color.POWDER_BLUE)
	%DefaultCaseSensitive.button_pressed = ProjectSettings.get_setting('dialogic/glossary/default_case_sensitive', true)

	%GlossaryList.clear()
	var idx := 0
	for file: String in ProjectSettings.get_setting('dialogic/glossary/glossary_files', []):
		## TODO REMOVE WHEN DROPPING 4.4 support
		var path := ""
		if Engine.get_version_info().hex >= 0x040500:
			path = ResourceUID.call("uid_to_path", file) if file.begins_with("uid:") else file
		else:
			path = file

		if ResourceLoader.exists(path):
			%GlossaryList.add_item(DialogicUtil.pretty_name(path), get_theme_icon('FileList', 'EditorIcons'))
		else:
			%GlossaryList.add_item(DialogicUtil.pretty_name(path), get_theme_icon('FileDead', 'EditorIcons'))

		%GlossaryList.set_item_tooltip(idx, path)
		idx += 1

	%EntryList.clear()

	if %GlossaryList.item_count != 0:
		%GlossaryList.select(0)
		_on_GlossaryList_item_selected(0)
	else:
		current_glossary = null
		hide_entry_editor()

################################################################################
##					GLOSSARY LIST
################################################################################
func _on_GlossaryList_item_selected(idx: int) -> void:
	%EntryList.clear()
	var tooltip_item: String = %GlossaryList.get_item_tooltip(idx)

	if ResourceLoader.exists(tooltip_item):
		var glossary_item := load(tooltip_item)

		if not glossary_item is DialogicGlossary:
			return

		current_glossary = load(tooltip_item)

		if not current_glossary is DialogicGlossary:
			return

		var entry_idx := 0

		for entry_key: String in current_glossary.entries.keys():
			var entry: Variant = current_glossary.entries.get(entry_key)

			if entry is String:
				continue

			# Older glossary entries may not have the name property and the
			# alternatives may not be set up as alias entries.
			if not entry.has(DialogicGlossary.NAME_PROPERTY):
				entry[DialogicGlossary.NAME_PROPERTY] = entry_key
				var alternatives_array: Array = entry.get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])
				var alternatives := ",".join(alternatives_array)
				_on_entry_alternatives_text_changed(alternatives)
				ResourceSaver.save(current_glossary)

			%EntryList.add_item(entry.get(DialogicGlossary.NAME_PROPERTY, str(DialogicGlossary.NAME_PROPERTY)), get_theme_icon("Breakpoint", "EditorIcons"))
			var modulate_color: Color = entry.get('color', %DefaultColor.color)
			%EntryList.set_item_metadata(entry_idx, entry)
			%EntryList.set_item_icon_modulate(entry_idx, modulate_color)

			entry_idx += 1

	if %EntryList.item_count != 0:
		%EntryList.select(0)
		_on_EntryList_item_selected(0)
	else:
		hide_entry_editor()


func _on_add_glossary_file_pressed() -> void:
	find_parent('EditorView').godot_file_dialog(create_new_glossary_file, '*.tres', EditorFileDialog.FILE_MODE_SAVE_FILE, 'Create new glossary resource')


func create_new_glossary_file(path:String) -> void:
	var glossary := DialogicGlossary.new()
	glossary.resource_path = path
	ResourceSaver.save(glossary, path)
	load_glossary_file(path)


func _on_load_glossary_file_pressed() -> void:
	find_parent('EditorView').godot_file_dialog(load_glossary_files, '*.tres', EditorFileDialog.FILE_MODE_OPEN_FILES, 'Select glossary resource')


func load_glossary_files(paths:Array[String]) -> void:
	for i in paths:
		load_glossary_file(i)


func load_glossary_file(file:String) -> void:
	var list: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])

	if not file in list:
		## TODO REMOVE WHEN DROPPING 4.4 support
		var path := ""
		if Engine.get_version_info().hex >= 0x040300:
			path = ResourceUID.call("uid_to_path", file) if file.begins_with("uid:") else file
			list.append(ResourceUID.call("path_to_uid",path))
		else:
			path = file
			list.append(path)

		ProjectSettings.set_setting('dialogic/glossary/glossary_files', list)
		ProjectSettings.save()
		%GlossaryList.add_item(DialogicUtil.pretty_name(path), get_theme_icon('FileList', 'EditorIcons'))

		var selected_item_index: int = %GlossaryList.item_count - 1

		%GlossaryList.set_item_tooltip(selected_item_index, path)
		%GlossaryList.select(selected_item_index)
		_on_GlossaryList_item_selected(selected_item_index)


func _on_delete_glossary_file_pressed() -> void:
	var selected_items: PackedInt32Array = %GlossaryList.get_selected_items()

	if not selected_items.is_empty():
		var list: Array = ProjectSettings.get_setting('dialogic/glossary/glossary_files', [])
		var selected_item_index := selected_items[0]
		list.remove_at(selected_item_index)

		ProjectSettings.set_setting('dialogic/glossary/glossary_files', list)
		ProjectSettings.save()

		_open()


################################################################################
##					ENTRY LIST
################################################################################
func _on_EntryList_item_selected(idx: int) -> void:
	current_entry_name = %EntryList.get_item_text(idx)

	var entry_info: Dictionary = current_glossary.get_entry(current_entry_name)
	current_entry = entry_info

	%EntrySettings.show()
	%EntryName.text = current_entry_name
	%EntryCaseSensitive.button_pressed = entry_info.get('case_sensitive', %DefaultCaseSensitive.button_pressed)

	var alternative_property: Array = entry_info.get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])
	var alternatives := ", ".join(alternative_property)
	%EntryAlternatives.text = alternatives

	%EntryTitle.text = entry_info.get('title', '')
	%EntryText.text = entry_info.get('text', '')
	%EntryExtra.text = entry_info.get('extra', '')
	%EntryEnabled.button_pressed = entry_info.get('enabled', true)

	%EntryColor.color = entry_info.get('color', %DefaultColor.color)
	%EntryCustomColor.button_pressed = entry_info.has('color')
	%EntryColor.disabled = !entry_info.has('color')

	_check_entry_alternatives(alternatives)
	_check_entry_name(current_entry_name, current_entry)

func _on_add_glossary_entry_pressed() -> void:
	if !current_glossary:
		return

	var entry_count := current_glossary.entries.size() + 1
	var new_name := "New Entry " + str(entry_count)

	if new_name in current_glossary.entries.keys():
		var random_hex_number := str(randi() % 0xFFFFFF)
		new_name = new_name + " " + str(random_hex_number)

	var new_glossary := {}
	new_glossary[DialogicGlossary.NAME_PROPERTY] = new_name

	if not current_glossary.try_add_entry(new_glossary):
		print_rich("[color=red]Failed adding '" + new_name + "', exists already.[/color]")
		return

	ResourceSaver.save(current_glossary)

	%EntryList.add_item(new_name, get_theme_icon("Breakpoint", "EditorIcons"))
	var item_count: int = %EntryList.item_count - 1

	%EntryList.set_item_metadata(item_count, new_name)
	%EntryList.set_item_icon_modulate(item_count, %DefaultColor.color)
	%EntryList.select(item_count)

	_on_EntryList_item_selected(item_count)

	%EntryList.ensure_current_is_visible()
	%EntryName.grab_focus()


func _on_delete_glossary_entry_pressed() -> void:
	var selected_items: Array = %EntryList.get_selected_items()

	if not selected_items.is_empty():
		var selected_item_index: int = selected_items[0]

		if not current_glossary == null:
			current_glossary.remove_entry(current_entry_name)
			ResourceSaver.save(current_glossary)

			%EntryList.remove_item(selected_item_index)
			var entries_count: int = %EntryList.item_count

			if entries_count > 0:
				var previous_item_index := selected_item_index - 1
				%EntryList.select(previous_item_index)



func _on_entry_search_text_changed(new_text: String) -> void:
	if new_text.is_empty() or new_text.to_lower() in %EntryList.get_item_text(%EntryList.get_selected_items()[0]).to_lower():
		return

	for i: int in %EntryList.item_count:

		if new_text.is_empty() or new_text.to_lower() in %EntryList.get_item_text(i).to_lower():
			%EntryList.select(i)
			_on_EntryList_item_selected(i)
			%EntryList.ensure_current_is_visible()


################################################################################
##					ENTRY EDITOR
################################################################################
func hide_entry_editor() -> void:
	%EntrySettings.hide()


func _update_alias_entries(old_alias_value_key: String, new_alias_value_key: String) -> void:
	for entry_key: String in current_glossary.entries.keys():

		var entry_value: Variant = current_glossary.entries.get(entry_key)

		if not entry_value is String:
			continue

		if not entry_value == old_alias_value_key:
			continue

		current_glossary.entries[entry_key] = new_alias_value_key


## Checks if the [param entry_name] is already used as a key for another entry
## and returns true if it doesn't.
## The [param entry] will be used to check if found entry uses the same
## reference in memory.
func _check_entry_name(entry_name: String, entry: Dictionary) -> bool:
	var selected_item: int = %EntryList.get_selected_items()[0]
	var raised_error: bool = false

	var entry_assigned: Variant = current_glossary.entries.get(entry_name, {})

	# Alternative entry uses the entry name already.
	if entry_assigned is String:
		raised_error = true

	if entry_assigned is Dictionary and not entry_assigned.is_empty():
		var entry_name_assigned: String = entry_assigned.get(DialogicGlossary.NAME_PROPERTY, "")

		# Another entry uses the entry name already.
		if not entry_name_assigned == entry_name:
			raised_error = true

		# Not the same memory reference.
		if not entry == entry_assigned:
			raised_error = true

	if raised_error:
		%EntryList.set_item_custom_bg_color(selected_item,
				get_theme_color("warning_color", "Editor").darkened(0.8))
		%EntryName.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
		%EntryName.right_icon = get_theme_icon("StatusError", "EditorIcons")

		return false

	else:
		%EntryName.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
		%EntryName.add_theme_color_override("caret_color", get_theme_color("font_color", "Editor"))
		%EntryName.right_icon = null
		%EntryList.set_item_custom_bg_color(
			selected_item,
			Color.TRANSPARENT
		)

	return true


func _on_entry_name_text_changed(new_name: String) -> void:
	new_name = new_name.strip_edges()

	if current_entry_name != new_name:
		var selected_item: int = %EntryList.get_selected_items()[0]

		if not _check_entry_name(new_name, current_entry):
			return

		print_rich("[color=green]Renaming entry '" + current_entry_name + "'' to '" + new_name + "'[/color]")

		_update_alias_entries(current_entry_name, new_name)

		current_glossary.replace_entry_key(current_entry_name, new_name)

		%EntryList.set_item_text(selected_item, new_name)
		%EntryList.set_item_metadata(selected_item, new_name)
		ResourceSaver.save(current_glossary)
		current_entry_name = new_name


func _on_entry_case_sensitive_toggled(button_pressed: bool) -> void:
	current_glossary.get_entry(current_entry_name)['case_sensitive'] = button_pressed
	ResourceSaver.save(current_glossary)


## Checks if the [param new_alternatives] has any alternatives that are already
## used as a key for another entry and returns true if it doesn't.
func _can_change_alternative(new_alternatives: String) -> bool:
	for alternative: String in new_alternatives.split(',', false):
		var stripped_alternative := alternative.strip_edges()

		var value: Variant = current_glossary.entries.get(stripped_alternative, null)

		if value == null:
			continue

		if value is String:
			value = current_glossary.entries.get(value, null)

		var value_name: String = value[DialogicGlossary.NAME_PROPERTY]

		if not current_entry_name == value_name:
			return false

	return true


## Checks if [entry_alternatives] has any alternatives that are already
## used by any entry and returns true if it doesn't.
## If false, it will set the alternatives text field to a warning color and
## set an icon.
## If true, the alternatives text field will be set to the default color and
## the icon will be removed.
func _check_entry_alternatives(entry_alternatives: String) -> bool:

	if not _can_change_alternative(entry_alternatives):
		%EntryAlternatives.add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"))
		%EntryAlternatives.right_icon = get_theme_icon("StatusError", "EditorIcons")
		return false

	else:
		%EntryAlternatives.add_theme_color_override("font_color", get_theme_color("font_color", "Editor"))
		%EntryAlternatives.right_icon = null

	return true


## The [param new_alternatives] is a passed as a string of comma separated
## values form the Dialogic editor.
##
## Saves the glossary resource file.
func _on_entry_alternatives_text_changed(new_alternatives: String) -> void:
	var current_alternatives: Array = current_glossary.get_entry(current_entry_name).get(DialogicGlossary.ALTERNATIVE_PROPERTY, [])

	if not _check_entry_alternatives(new_alternatives):
		return

	for current_alternative: String in current_alternatives:
		current_glossary._remove_entry_alias(current_alternative)

	var alternatives := []

	for new_alternative: String in new_alternatives.split(',', false):
		var stripped_alternative := new_alternative.strip_edges()
		alternatives.append(stripped_alternative)
		current_glossary._add_entry_key_alias(current_entry_name, stripped_alternative)

	current_glossary.get_entry(current_entry_name)[DialogicGlossary.ALTERNATIVE_PROPERTY] = alternatives
	ResourceSaver.save(current_glossary)


func _on_entry_title_text_changed(new_text:String) -> void:
	current_glossary.get_entry(current_entry_name)['title'] = new_text
	ResourceSaver.save(current_glossary)


func _on_entry_text_text_changed() -> void:
	current_glossary.get_entry(current_entry_name)['text'] = %EntryText.text
	ResourceSaver.save(current_glossary)


func _on_entry_extra_text_changed() -> void:
	current_glossary.get_entry(current_entry_name)['extra'] = %EntryExtra.text
	ResourceSaver.save(current_glossary)


func _on_entry_enabled_toggled(button_pressed:bool) -> void:
	current_glossary.get_entry(current_entry_name)['enabled'] = button_pressed
	ResourceSaver.save(current_glossary)


func _on_entry_custom_color_toggled(button_pressed:bool) -> void:
	%EntryColor.disabled = !button_pressed

	if !button_pressed:
		current_glossary.get_entry(current_entry_name).erase('color')
		%EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], %DefaultColor.color)
	else:
		current_glossary.get_entry(current_entry_name)['color'] = %EntryColor.color
		%EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], %EntryColor.color)


func _on_entry_color_color_changed(color:Color) -> void:
	current_glossary.get_entry(current_entry_name)['color'] = color
	%EntryList.set_item_icon_modulate(%EntryList.get_selected_items()[0], color)
	ResourceSaver.save(current_glossary)
