@tool
extends Control

const HTerrain = preload("../../hterrain.gd")
const HTerrainData = preload("../../hterrain_data.gd")
const HTerrainDetailLayer = preload("../../hterrain_detail_layer.gd")
const HT_ImageFileCache = preload("../../util/image_file_cache.gd")
const HT_EditorUtil = preload("../util/editor_util.gd")
const HT_Logger = preload("../../util/logger.gd")

# TODO Can't preload because it causes the plugin to fail loading if assets aren't imported
const PLACEHOLDER_ICON_TEXTURE = "res://addons/zylann.hterrain/tools/icons/icon_grass.svg"
const DETAIL_LAYER_ICON_TEXTURE = \
	"res://addons/zylann.hterrain/tools/icons/icon_detail_layer_node.svg"

signal detail_selected(index)
# Emitted when the tool added or removed a detail map
signal detail_list_changed

@onready var _item_list: ItemList = $ItemList
@onready var _confirmation_dialog: ConfirmationDialog = $ConfirmationDialog

var _terrain: HTerrain = null
var _dialog_target := -1
var _undo_redo_manager: EditorUndoRedoManager
var _image_cache: HT_ImageFileCache
var _logger = HT_Logger.get_for(self)


func set_terrain(terrain):
	if _terrain == terrain:
		return
	_terrain = terrain
	_update_list()


func set_undo_redo(ur: EditorUndoRedoManager):
	assert(ur != null)
	_undo_redo_manager = ur


func set_image_cache(image_cache: HT_ImageFileCache):
	_image_cache = image_cache


func set_layer_index(i: int):
	_item_list.select(i, true)


func _update_list():
	_item_list.clear()
	
	if _terrain == null:
		return
	
	var layer_nodes = _terrain.get_detail_layers()
	var layer_nodes_by_index := {}
	for layer in layer_nodes:
		if not layer_nodes_by_index.has(layer.layer_index):
			layer_nodes_by_index[layer.layer_index] = []
		layer_nodes_by_index[layer.layer_index].append(layer.name)

	var data = _terrain.get_data()
	if data != null:
		# Display layers from what terrain data actually contains,
		# because layer nodes are just what makes them rendered and aren't much restricted.
		var layer_count = data.get_map_count(HTerrainData.CHANNEL_DETAIL)
		var placeholder_icon = HT_EditorUtil.load_texture(PLACEHOLDER_ICON_TEXTURE, _logger)
		
		for i in layer_count:
			# TODO Show a preview icon
			_item_list.add_item(str("Map ", i), placeholder_icon)
			
			if layer_nodes_by_index.has(i):
				# TODO How to keep names updated with node names?
				var names := ", ".join(PackedStringArray(layer_nodes_by_index[i]))
				if len(names) == 1:
					_item_list.set_item_tooltip(i, "Used by " + names)
				else:
					_item_list.set_item_tooltip(i, "Used by " + names)
				# Remove custom color
				# TODO Use fg version when available in Godot 3.1, I want to only highlight text
				_item_list.set_item_custom_bg_color(i, Color(0, 0, 0, 0))
			else:
				# TODO Use fg version when available in Godot 3.1, I want to only highlight text
				_item_list.set_item_custom_bg_color(i, Color(1.0, 0.2, 0.2, 0.3))
				_item_list.set_item_tooltip(i, "This map isn't used by any layer. " \
					+"Add a HTerrainDetailLayer node as child of the terrain.")


func _on_Add_pressed():
	_add_layer()


func _on_Remove_pressed():
	var selected = _item_list.get_selected_items()
	if len(selected) == 0:
		return
	_dialog_target = _item_list.get_selected_items()[0]
	_confirmation_dialog.title = "Removing detail map {0}".format([_dialog_target])
	_confirmation_dialog.popup_centered()


func _on_ConfirmationDialog_confirmed():
	_remove_layer(_dialog_target)


func _add_layer():
	assert(_terrain != null)
	assert(_terrain.get_data() != null)
	assert(_undo_redo_manager != null)
	var terrain_data: HTerrainData = _terrain.get_data()

	# First, create node and map image	
	var node := HTerrainDetailLayer.new()
	# TODO Workarounds for https://github.com/godotengine/godot/issues/21410
	var detail_layer_icon := HT_EditorUtil.load_texture(DETAIL_LAYER_ICON_TEXTURE, _logger)
	node.set_meta("_editor_icon", detail_layer_icon)
	node.name = "HTerrainDetailLayer"
	var map_index := terrain_data._edit_add_map(HTerrainData.CHANNEL_DETAIL)
	var map_image := terrain_data.get_image(HTerrainData.CHANNEL_DETAIL, map_index)
	var map_image_cache_id := _image_cache.save_image(map_image)
	node.layer_index = map_index
	
	# var max_seed := 0
	# for dl in _terrain.get_detail_layers():
	# 	max_seed = maxi(dl.fixed_seed, max_seed)
	# node.fixed_seed = max_seed + 1
	
	var undo_redo := _undo_redo_manager.get_history_undo_redo(
		_undo_redo_manager.get_object_history_id(_terrain))
	
	# Then, create an action
	undo_redo.create_action("Add Detail Layer {0}".format([map_index]))
	
	undo_redo.add_do_method(terrain_data._edit_insert_map_from_image_cache.bind(
		HTerrainData.CHANNEL_DETAIL, map_index, _image_cache, map_image_cache_id))
	undo_redo.add_do_method(_terrain.add_child.bind(node, true))
	undo_redo.add_do_property(node, "owner", get_tree().edited_scene_root)
	undo_redo.add_do_method(self._update_list)
	undo_redo.add_do_reference(node)
	
	undo_redo.add_undo_method(_terrain.remove_child.bind(node))
	undo_redo.add_undo_method(
		terrain_data._edit_remove_map.bind(HTerrainData.CHANNEL_DETAIL, map_index))
	undo_redo.add_undo_method(self._update_list)
	
	# Yet another instance of this hack, to prevent UndoRedo from running some of the functions,
	# which we had to run already
	terrain_data._edit_set_disable_apply_undo(true)
	undo_redo.commit_action()
	terrain_data._edit_set_disable_apply_undo(false)
	
	#_update_list()
	detail_list_changed.emit()
	
	var index := node.layer_index
	_item_list.select(index)
	# select() doesn't trigger the signal
	detail_selected.emit(index)


func _remove_layer(map_index: int):
	var terrain_data: HTerrainData = _terrain.get_data()
	
	# First, cache image data
	var image := terrain_data.get_image(HTerrainData.CHANNEL_DETAIL, map_index)
	var image_id := _image_cache.save_image(image)
	var nodes = _terrain.get_detail_layers()
	var using_nodes := []
	# Nodes using this map will be removed from the tree
	for node in nodes:
		if node.layer_index == map_index:
			using_nodes.append(node)
	
	var undo_redo := _undo_redo_manager.get_history_undo_redo(
		_undo_redo_manager.get_object_history_id(_terrain))

	undo_redo.create_action("Remove Detail Layer {0}".format([map_index]))
	
	undo_redo.add_do_method(
		terrain_data._edit_remove_map.bind(HTerrainData.CHANNEL_DETAIL, map_index))
	for node in using_nodes:
		undo_redo.add_do_method(_terrain.remove_child.bind(node))
	undo_redo.add_do_method(self._update_list)
	
	undo_redo.add_undo_method(terrain_data._edit_insert_map_from_image_cache.bind(
		HTerrainData.CHANNEL_DETAIL, map_index, _image_cache, image_id))
	for node in using_nodes:
		undo_redo.add_undo_method(_terrain.add_child.bind(node))
		undo_redo.add_undo_property(node, "owner", get_tree().edited_scene_root)
		undo_redo.add_undo_reference(node)
	undo_redo.add_undo_method(self._update_list)
	
	undo_redo.commit_action()
	
	#_update_list()
	detail_list_changed.emit()


func _on_ItemList_item_selected(index):
	detail_selected.emit(index)
