tool
extends GridContainer


enum {CEL, FRAME, ALL_FRAMES}

const palette_button = preload("res://addons/draw_gd/src/Palette/PaletteButton.tscn")

var current_palette = "Default"
var from_palette : Palette

onready var palette_from_sprite_dialog = $"../../../../PaletteFromSpriteDialog"
onready var remove_palette_warning = $"../../../../RemovePaletteWarning"

var Import = preload("res://addons/draw_gd/src/Autoload/Import.gd")

var DrawGD : Node = null

func _enter_tree() -> void:
	var n : Node = get_parent()
	while n:
		if n.name == "DrawGDSingleton":
			DrawGD = n
			break
		n = n.get_parent()
	
	_load_palettes()

	# Select default palette "Default"
	on_palette_select(current_palette)

	var add_palette_menu : PopupMenu = DrawGD.add_palette_button.get_node("PopupMenu")
	add_palette_menu.connect("id_pressed", self, "add_palette_menu_id_pressed")


func _clear_swatches() -> void:
	for child in get_children():
		if child is BaseButton and child.text != "Dummy":
			child.disconnect("pressed", self, "on_color_select")
			child.queue_free()


func on_palette_select(palette_name : String) -> void:
	_clear_swatches()
	if DrawGD.palettes.has(palette_name): # Palette exists in memory
		current_palette = palette_name
		var palette : Palette = DrawGD.palettes[palette_name]
		_display_palette(palette)


func on_new_empty_palette() -> void:
	DrawGD.new_palette_dialog.window_title = "Create a new empty palette?"
	DrawGD.new_palette_name_line_edit.text = "Custom_Palette"
	from_palette = null
	DrawGD.new_palette_dialog.popup_centered()
	DrawGD.dialog_open(true)


func on_import_palette() -> void:
	DrawGD.palette_import_file_dialog.popup_centered()
	DrawGD.dialog_open(true)


func on_palette_import_file_selected(path : String) -> void:
	var palette : Palette = null
	if path.to_lower().ends_with("json"):
		palette = Palette.new().load_from_file(path)
	elif path.to_lower().ends_with("gpl"):
		var file = File.new()
		if file.file_exists(path):
			file.open(path, File.READ)
			var text = file.get_as_text()
			file.close()
			palette = Import.import_gpl(path, text)
	elif path.to_lower().ends_with("pal"):
		var file = File.new()
		if file.file_exists(path):
			file.open(path, File.READ)
			var text = file.get_as_text()
			file.close()
			palette = Import.import_pal_palette(path, text)
	elif path.to_lower().ends_with("png") or path.to_lower().ends_with("bmp") or path.to_lower().ends_with("hdr") or path.to_lower().ends_with("jpg") or path.to_lower().ends_with("svg") or path.to_lower().ends_with("tga") or path.to_lower().ends_with("webp"):
		var image := Image.new()
		var err := image.load(path)
		if !err:
			import_image_palette(path, image)
			return

	attempt_to_import_palette(palette)


func import_image_palette(path : String, image : Image) -> void:
	var palette : Palette = null
	palette = Import.import_png_palette(path, image)
	attempt_to_import_palette(palette)


func attempt_to_import_palette(palette : Palette) -> void:
	if palette:
		palette.name = palette_name_replace(palette.name)
		DrawGD.palettes[palette.name] = palette
		DrawGD.palette_option_button.add_item(palette.name)
		var index: int = DrawGD.palette_option_button.get_item_count() - 1
		DrawGD.palette_option_button.set_item_metadata(index, palette.name)
		DrawGD.palette_option_button.select(index)
		on_palette_select(palette.name)
		save_palette(palette.name, palette.name + ".json")
	else:
		DrawGD.error_dialog.set_text("Invalid Palette file!")
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)


func _on_AddPalette_pressed() -> void:
	DrawGD.add_palette_button.get_node("PopupMenu").popup(Rect2(DrawGD.add_palette_button.rect_global_position, Vector2.ONE))


func on_new_palette_confirmed() -> void:
	var new_palette_name : String = DrawGD.new_palette_name_line_edit.text
	var result : String = create_new_palette(new_palette_name, from_palette)
	if not result.empty():
		DrawGD.error_dialog.set_text(result)
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)


func add_palette_menu_id_pressed(id : int) -> void:
	match id:
		0: # New Empty Palette
			on_new_empty_palette()
		1: # Import Palette
			on_import_palette()
		2: # Create Palette From Current Sprite
			palette_from_sprite_dialog.popup_centered()
			DrawGD.dialog_open(true)


func create_new_palette(name : String, _from_palette : Palette) -> String: # Returns empty string, else error string
	var new_palette : Palette = Palette.new()

	# Check if new name is valid
	if name.empty():
		return tr("Error: Palette must have a valid name.")

	name = palette_name_replace(name)

	new_palette.name = name
	# Check if source palette has data
	if _from_palette:
		new_palette = _from_palette.duplicate()
		new_palette.name = name
		new_palette.editable = true

	# Add palette to DrawGD and options
	DrawGD.palettes[name] = new_palette
	DrawGD.palette_option_button.add_item(name)
	var index : int = DrawGD.palette_option_button.get_item_count() - 1
	DrawGD.palette_option_button.set_item_metadata(index, name)
	DrawGD.palette_option_button.select(index)

	save_palette(name, name + ".json")

	on_palette_select(name)
	return ""


# Checks if the palette name already exists
# If it does, add a number to its name, for example
# "Palette_Name" will become "Palette_Name (2)", "Palette_Name (3)", etc.
func palette_name_replace(name : String) -> String:
	var i := 1
	var temp_name := name
	while DrawGD.palettes.has(temp_name):
		i += 1
		temp_name = name + " (%s)" % i
	name = temp_name
	return name


func on_edit_palette() -> void:
	var palette : Palette = DrawGD.palettes[current_palette]

	var create_new_palette := true # Create new palette by default
	if palette.editable:
		create_new_palette = false # Edit if already a custom palette

	if create_new_palette:
		from_palette = DrawGD.palettes[current_palette]
		DrawGD.new_palette_dialog.window_title = "Create a new custom palette from existing default?"
		DrawGD.new_palette_name_line_edit.text = "Custom_" + current_palette
		DrawGD.new_palette_dialog.popup_centered()
		DrawGD.dialog_open(true)
	else:
		from_palette = null
		DrawGD.edit_palette_popup.open(current_palette)


func create_palette_from_sprite() -> void:
	var current_project : Project = DrawGD.current_project
	var new_palette_name : String = current_project.name
	var result : String = create_new_palette(new_palette_name, null)
	if not result.empty():
		DrawGD.error_dialog.set_text(result)
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)
		return

	var alpha_checkbox : CheckBox = palette_from_sprite_dialog.get_node("VBoxContainer/AlphaCheckBox")
	var selection_checkbox : CheckBox = palette_from_sprite_dialog.get_node("VBoxContainer/SelectionCheckBox")
	var colors_from_optionbutton : OptionButton = palette_from_sprite_dialog.get_node("VBoxContainer/HBoxContainer/ColorsFromOptionButton")

	var palette : Palette = DrawGD.palettes[current_palette]
	var pixels := []

	if selection_checkbox.pressed:
		pixels = current_project.selected_pixels.duplicate()
	else:
		for x in current_project.size.x:
			for y in current_project.size.y:
				pixels.append(Vector2(x, y))

	var cels := []
	match colors_from_optionbutton.selected:
		CEL:
			cels.append(current_project.frames[current_project.current_frame].cels[current_project.current_layer])
		FRAME:
			for cel in current_project.frames[current_project.current_frame].cels:
				cels.append(cel)
		ALL_FRAMES:
			for frame in current_project.frames:
				for cel in frame.cels:
					cels.append(cel)

	for cel in cels:
		var cel_image := Image.new()
		cel_image.copy_from(cel.image)
		cel_image.lock()
		if cel_image.is_invisible():
			continue
		for i in pixels:
			var color : Color = cel_image.get_pixelv(i)
			if color.a > 0:
				if !alpha_checkbox.pressed:
					color.a = 1
				if !palette.has_color(color):
					palette.add_color(color)
		cel_image.unlock()

	save_palette(current_palette, current_palette + ".json")
	_display_palette(palette)


func _on_PaletteOptionButton_item_selected(ID : int) -> void:
	var palette_name = DrawGD.palette_option_button.get_item_metadata(ID)
	if palette_name != null:
		on_palette_select(palette_name)


func _display_palette(palette : Palette) -> void:
	var index := 0

	for color_data in palette.colors:
		var color = color_data.color
		var new_button = palette_button.instance()

		new_button.get_child(0).modulate = color
		new_button.hint_tooltip = "#" + color_data.data.to_upper() + " " + color_data.name
		new_button.connect("pressed", self, "on_color_select", [index])
		new_button.group = $DummyBtn.group

		add_child(new_button)
		index += 1


func on_color_select(index : int) -> void:
	var color : Color = DrawGD.palettes[current_palette].get_color(index)

	if Input.is_action_just_pressed("left_mouse"):
		DrawGD.tools.assign_color(color, BUTTON_LEFT)
	elif Input.is_action_just_pressed("right_mouse"):
		DrawGD.tools.assign_color(color, BUTTON_RIGHT)


func _load_palettes() -> void:
	DrawGD.directory_module.ensure_xdg_user_dirs_exist()
	var search_locations = DrawGD.directory_module.get_palette_search_path_in_order()
	var priority_ordered_files := get_palette_priority_file_map(search_locations)

	# Iterate backwards, so any palettes defined in default files
	# get overwritten by those of the same name in user files
	search_locations.invert()
	priority_ordered_files.invert()
	for i in range(len(search_locations)):
		var base_directory : String = search_locations[i]
		var palette_files : Array = priority_ordered_files[i]
		for file_name in palette_files:
			var palette : Palette = Palette.new().load_from_file(base_directory.plus_file(file_name))
			if palette:
				DrawGD.palettes[palette.name] = palette
				DrawGD.palette_option_button.add_item(palette.name)
				var index: int = DrawGD.palette_option_button.get_item_count() - 1
				DrawGD.palette_option_button.set_item_metadata(index, palette.name)
				if palette.name == "Default":
					# You need these two lines because when you remove a palette
					# Then this just won't work and _on_PaletteOptionButton_item_selected
					# method won't fire.
					DrawGD.palette_option_button.selected = index
					on_palette_select("Default")
					DrawGD.palette_option_button.select(index)

	if not "Default" in DrawGD.palettes && DrawGD.palettes.size() > 0:
		DrawGD.palette_container._on_PaletteOptionButton_item_selected(0)


# Get the palette files in a single directory.
# if it does not exist, return []
func get_palette_files(path : String ) -> Array:
	var dir := Directory.new()
	var results = []

	if not dir.dir_exists(path):
		return []

	dir.open(path)
	dir.list_dir_begin()

	while true:
		var file_name = dir.get_next()
		if file_name == "":
			break
		elif (not file_name.begins_with(".")) && file_name.to_lower().ends_with("json") && not dir.current_is_dir():
			results.append(file_name)

	dir.list_dir_end()
	return results


# This returns an array of arrays, with priorities.
# In particular, it takes an array of paths to look for
# arrays in, in order of file and palette override priority
# such that the files in the first directory override the
# second, third, etc. ^.^
# It returns an array of arrays, where each output array
# corresponds to the given input array at the same index, and
# contains the (relative to the given directory) palette files
# to load, excluding all ones already existing in higher-priority
# directories. nya
# in particular, this also means you can run backwards on the result
# so that palettes with the given palette name in the higher priority
# directories override those set in lower priority directories :)
func get_palette_priority_file_map(looking_paths: Array) -> Array:
	var final_list := []
	# Holds pattern files already found
	var working_file_set : Dictionary = {}
	for search_directory in looking_paths:
		var to_add_files := []
		var files = get_palette_files(search_directory)
		# files to check
		for maybe_to_add in files:
			if not maybe_to_add in working_file_set:
				to_add_files.append(maybe_to_add)
				working_file_set[maybe_to_add] = true

		final_list.append(to_add_files)
	return final_list


# Locate the highest priority palette by the given relative filename
# If none is found in the directories, then do nothing and return
# null
func get_best_palette_file_location(looking_paths: Array, fname: String):  # -> String:
	var priority_fmap : Array = get_palette_priority_file_map(looking_paths)
	for i in range(len(looking_paths)):
		var base_path : String = looking_paths[i]
		var the_files : Array = priority_fmap[i]
		if the_files.has(fname):
			return base_path.plus_file(fname)

	return null


func remove_palette(palette_name : String) -> void:
	# Don't allow user to remove palette if there is no one left
	if DrawGD.palettes.size() < 2:
		DrawGD.error_dialog.set_text("You can't remove more palettes!")
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)
		return
	# Don't allow user to try to remove not existing palettes
	if not palette_name in DrawGD.palettes:
		DrawGD.error_dialog.set_text("Cannot remove the palette, because it doesn't exist!")
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)
		return
	DrawGD.directory_module.ensure_xdg_user_dirs_exist()
	var palette = DrawGD.palettes[palette_name]
	var result = palette.remove_file()
	# Inform user if pallete hasn't been removed from disk because of an error
	if result != OK:
		DrawGD.error_dialog.set_text(tr("An error occured while removing the palette! Error code: %s") % str(result))
		DrawGD.error_dialog.popup_centered()
		DrawGD.dialog_open(true)
	# Remove palette in the program anyway, because if you don't do it
	# then Pixelorama will crash
	DrawGD.palettes.erase(palette_name)
	DrawGD.palette_option_button.clear()
	current_palette = "Default"
	_load_palettes()


func save_palette(palette_name : String, filename : String) -> void:
	DrawGD.directory_module.ensure_xdg_user_dirs_exist()
	var palette = DrawGD.palettes[palette_name]
	var palettes_write_path: String = DrawGD.directory_module.get_palette_write_path()
	palette.save_to_file(palettes_write_path.plus_file(filename))


func _on_NewPaletteDialog_popup_hide() -> void:
	DrawGD.dialog_open(false)


func _on_RemovePalette_pressed() -> void:
	remove_palette_warning.popup_centered()
	DrawGD.dialog_open(true)


func _on_PaletteFromSpriteDialog_confirmed() -> void:
	create_palette_from_sprite()


func _on_PaletteFromSpriteDialog_popup_hide() -> void:
	DrawGD.dialog_open(false)


func _on_RemovePaletteWarning_confirmed() -> void:
	remove_palette(current_palette)


func _on_RemovePaletteWarning_popup_hide() -> void:
	DrawGD.dialog_open(false)
