@tool
extends AcceptDialog

const HTerrainTextureSet = preload("../../../hterrain_texture_set.gd")
const HT_Logger = preload("../../../util/logger.gd")
const HT_EditorUtil = preload("../../util/editor_util.gd")
const HT_Errors = preload("../../../util/errors.gd")
const HT_TextureSetEditor = preload("./texture_set_editor.gd")
const HT_Result = preload("../../util/result.gd")
const HT_Util = preload("../../../util/util.gd")
const HT_PackedTextureUtil = preload("../../packed_textures/packed_texture_util.gd")
const ResourceImporterTexture_Unexposed = preload("../../util/resource_importer_texture.gd")
const ResourceImporterTextureLayered_Unexposed = preload(
	"../../util/resource_importer_texture_layered.gd")

const HT_NormalMapPreviewShader = preload("../display_normal.gdshader")

const COMPRESS_RAW = 0
const COMPRESS_LOSSLESS = 1
const COMPRESS_LOSSY = 1
const COMPRESS_VRAM = 2
const COMPRESS_COUNT = 3

const _compress_names = ["Raw", "Lossless", "Lossy", "VRAM"]

# Indexed by HTerrainTextureSet.SRC_TYPE_* constants
const _smart_pick_file_keywords = [
	["albedo", "color", "col", "diffuse", "diff"],
	["bump", "height", "depth", "displacement", "disp"],
	["normal", "norm", "nrm", "normalgl", "nor_gl"],
	["roughness", "rough", "rgh"]
]

signal import_finished

@onready var _texture_editors = [
	$Import/HS/VB2/HB/Albedo,
	$Import/HS/VB2/HB/Bump,
	$Import/HS/VB2/HB/Normal,
	$Import/HS/VB2/HB/Roughness
]

@onready var _slots_list : ItemList = $Import/HS/VB/SlotsList

# TODO Some shortcuts to import options were disabled in the GUI because of Godot issues.
# If users want to customize that, they need to do it on the files directly.
#
# There is no script API in Godot to choose the import settings of a generated file.
# They always start with the defaults, and the only implemented case is for the import dock.
# It appeared possible to reverse-engineer and write a .import file as done in HTerrainData,
# however when I tried this with custom importers, Godot stopped importing after scan(),
# and the resources could not load. However, selecting them each and clicking "Reimport"
# did import them fine. Unfortunately, this short-circuits the workflow.
# Since I have no idea what's going on with this reverse-engineering, I had to drop those options.
# Godot needs an API to import specific files and choose settings before the first import.
#
# Godot 4: now we'll really need it, let's enable and we'll see if it works
# when we can test the workflow...
const _WRITE_IMPORT_FILES = true

@onready var _import_mode_selector : OptionButton = $Import/GC/ImportModeSelector
@onready var _compression_selector : OptionButton = $Import/GC/CompressionSelector
@onready var _resolution_spinbox : SpinBox = $Import/GC/ResolutionSpinBox
@onready var _mipmaps_checkbox : CheckBox = $Import/GC/MipmapsCheckbox
@onready var _add_slot_button : Button = $Import/HS/VB/HB/AddSlotButton
@onready var _remove_slot_button : Button = $Import/HS/VB/HB/RemoveSlotButton
@onready var _import_directory_line_edit : LineEdit = $Import/HB2/ImportDirectoryLineEdit
@onready var _normalmap_flip_checkbox : CheckBox = $Import/HS/VB2/HB/Normal/NormalMapFlipY

var _texture_set : HTerrainTextureSet
var _undo_redo_manager : EditorUndoRedoManager
var _logger = HT_Logger.get_for(self)

# This is normally an `EditorFileDialog`. I can't type-hint this one properly,
# because when I test this UI in isolation, I can't use `EditorFileDialog`.
var _load_texture_dialog : ConfirmationDialog
var _load_texture_type : int = -1
var _error_popup : AcceptDialog
var _info_popup : AcceptDialog
var _delete_confirmation_popup : ConfirmationDialog
var _open_dir_dialog : ConfirmationDialog
var _editor_file_system : EditorFileSystem
var _normalmap_material : ShaderMaterial

var _import_mode := HTerrainTextureSet.MODE_TEXTURES

class HT_TextureSetImportEditorSlot:
	# Array of strings.
	# Can be either path to images, hexadecimal colors starting with #, or empty string for "null".
	var texture_paths := []
	var flip_normalmap_y := false
	
	func _init():
		for i in HTerrainTextureSet.SRC_TYPE_COUNT:
			texture_paths.append("")

# Array of HT_TextureSetImportEditorSlot
var _slots_data := []

var _import_settings := {
	"mipmaps": true,
	"compression": COMPRESS_VRAM,
	"resolution": 512
}


func _init():
	get_ok_button().hide()
	
	# Default data
	_slots_data.clear()
	for i in 4:
		_slots_data.append(HT_TextureSetImportEditorSlot.new())


func _ready():
	if HT_Util.is_in_edited_scene(self):
		return

	for src_type in len(_texture_editors):
		var ed = _texture_editors[src_type]
		var typename = HTerrainTextureSet.get_source_texture_type_name(src_type)
		ed.set_label(typename.capitalize())
		ed.load_pressed.connect(_on_texture_load_pressed.bind(src_type))
		ed.clear_pressed.connect(_on_texture_clear_pressed.bind(src_type))
	
	for import_mode in HTerrainTextureSet.MODE_COUNT:
		var n = HTerrainTextureSet.get_import_mode_name(import_mode)
		_import_mode_selector.add_item(n, import_mode)

	for compress_mode in COMPRESS_COUNT:
		var n = _compress_names[compress_mode]
		_compression_selector.add_item(n, compress_mode)
	
	_normalmap_material = ShaderMaterial.new()
	_normalmap_material.shader = HT_NormalMapPreviewShader
	_texture_editors[HTerrainTextureSet.SRC_TYPE_NORMAL].set_material(_normalmap_material)


func setup_dialogs(parent: Node):
	var d = HT_EditorUtil.create_open_image_dialog()
	d.file_selected.connect(_on_LoadTextureDialog_file_selected)
	_load_texture_dialog = d
	add_child(d)
	
	d = AcceptDialog.new()
	d.title = "Import error"
	_error_popup = d
	add_child(_error_popup)

	d = AcceptDialog.new()
	d.title = "Info"
	_info_popup = d
	add_child(_info_popup)
	
	d = ConfirmationDialog.new()
	d.confirmed.connect(_on_delete_confirmation_popup_confirmed)
	_delete_confirmation_popup = d
	add_child(_delete_confirmation_popup)
	
	d = HT_EditorUtil.create_open_dir_dialog()
	d.title = "Choose import directory"
	d.dir_selected.connect(_on_OpenDirDialog_dir_selected)
	_open_dir_dialog = d
	add_child(_open_dir_dialog)
	
	_update_ui_from_data()


func _notification(what: int):
	if what == NOTIFICATION_EXIT_TREE:
		# Have to check for null in all of them,
		# because otherwise it breaks in the scene editor...
		if _load_texture_dialog != null:
			_load_texture_dialog.queue_free()
		if _error_popup != null:
			_error_popup.queue_free()
		if _delete_confirmation_popup != null:
			_delete_confirmation_popup.queue_free()
		if _open_dir_dialog != null:
			_open_dir_dialog.queue_free()
		if _info_popup != null:
			_info_popup.queue_free()


# TODO Is it still necessary for an import tab?
func set_undo_redo(ur: EditorUndoRedoManager):
	_undo_redo_manager = ur


func set_editor_file_system(efs: EditorFileSystem):
	_editor_file_system = efs


func set_texture_set(texture_set: HTerrainTextureSet):
	if _texture_set == texture_set:
		# TODO What if the set was actually modified since?
		return
	_texture_set = texture_set

	_slots_data.clear()
	
	if _texture_set.get_mode() == HTerrainTextureSet.MODE_TEXTURES:
		var slots_count = _texture_set.get_slots_count()
		
		for slot_index in slots_count:
			var slot := HT_TextureSetImportEditorSlot.new()
			
			for type in HTerrainTextureSet.TYPE_COUNT:
				var texture = _texture_set.get_texture(slot_index, type)
				
				if texture == null or texture.resource_path == "":
					continue
				
				# In Godot 3 we used a custom format to store and composite source images
				# with a custom importer. It was removed during the port to Godot 4 because it
				# was too hard to maintain a custom texture importer (too much logic to replicate
				# from Godot's builtin importer, which wasn't exposed).
				# For now the import tool won't remember source images between editor sessions.
				
#				if not texture.resource_path.ends_with(".packed_tex"):
#					continue
#
#				var import_data := _parse_json_file(texture.resource_path)
#				if import_data.is_empty() or not import_data.has("src"):
#					continue
#
#				var src_types = HTerrainTextureSet.get_src_types_from_type(type)
#
#				var src_data = import_data["src"]
#				if src_data.has("rgb"):
#					slot.texture_paths[src_types[0]] = src_data["rgb"]
#				if src_data.has("a"):
#					slot.texture_paths[src_types[1]] = src_data["a"]
			
			_slots_data.append(slot)
	
	else:
		var slots_count := _texture_set.get_slots_count()
		
		for type in HTerrainTextureSet.TYPE_COUNT:
			var texture_array := _texture_set.get_texture_array(type)
			
			if texture_array == null or texture_array.resource_path == "":
				continue
			
			# In Godot 3 we used a custom format to store and composite source images
			# with a custom importer. It was removed during the port to Godot 4 because it
			# was too hard to maintain a custom texture importer (too much logic to replicate
			# from Godot's builtin importer, which wasn't exposed).
			# For now the import tool won't remember source images between editor sessions.
			
#			if not texture_array.resource_path.ends_with(".packed_texarr"):
#				continue
#
#			var import_data := _parse_json_file(texture_array.resource_path)
#			if import_data.is_empty() or not import_data.has("layers"):
#				continue
#
#			var layers_data = import_data["layers"]
#
#			for slot_index in len(layers_data):
#				var src_data = layers_data[slot_index]
#
#				var src_types = HTerrainTextureSet.get_src_types_from_type(type)
#
#				while slot_index >= len(_slots_data):
#					var slot = HT_TextureSetImportEditorSlot.new()
#					_slots_data.append(slot)
#
#				var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
#
#				if src_data.has("rgb"):
#					slot.texture_paths[src_types[0]] = src_data["rgb"]
#				if src_data.has("a"):
#					slot.texture_paths[src_types[1]] = src_data["a"]

	# TODO If the set doesn't have a file, use terrain path by default?
	if texture_set.resource_path != "":
		var dir = texture_set.resource_path.get_base_dir()
		_import_directory_line_edit.text = dir

	_update_ui_from_data()


func _parse_json_file(fpath: String) -> Dictionary:
	var f := FileAccess.open(fpath, FileAccess.READ)
	if f == null:
		var err := FileAccess.get_open_error()
		_logger.error("Could not load {0}: {1}".format([fpath, HT_Errors.get_message(err)]))
		return {}
	
	var json_text := f.get_as_text()
	var json := JSON.new()
	var json_err := json.parse(json_text)
	if json_err != OK:
		_logger.error("Failed to parse {0}: {1}".format([fpath, json.get_error_message()]))
		return {}
	
	return json.data


func _update_ui_from_data():
	var prev_selected_items := _slots_list.get_selected_items()
	
	_slots_list.clear()
	
	for slot_index in len(_slots_data):
		_slots_list.add_item("Texture {0}".format([slot_index]))
	
	_resolution_spinbox.value = _import_settings.resolution
	_mipmaps_checkbox.button_pressed = _import_settings.mipmaps
	_set_selected_id(_compression_selector, _import_settings.compression)
	_set_selected_id(_import_mode_selector, _import_mode)
	
	var has_slots : bool = _slots_list.get_item_count() > 0
	
	for ed in _texture_editors:
		ed.set_enabled(has_slots)
	_normalmap_flip_checkbox.disabled = not has_slots
	
	if has_slots:
		if len(prev_selected_items) > 0:
			var i : int = prev_selected_items[0]
			if i >= _slots_list.get_item_count():
				i = _slots_list.get_item_count() - 1
			_select_slot(i)
		else:
			_select_slot(0)
	else:
		for type in HTerrainTextureSet.SRC_TYPE_COUNT:
			_set_ui_slot_texture_from_path("", type)
	
	var max_slots := HTerrainTextureSet.get_max_slots_for_mode(_import_mode)
	_add_slot_button.disabled = (len(_slots_data) >= max_slots)
	_remove_slot_button.disabled = (len(_slots_data) == 0)


static func _set_selected_id(ob: OptionButton, id: int):
	for i in ob.get_item_count():
		if ob.get_item_id(i) == id:
			ob.selected = i
			break


func _select_slot(slot_index: int):
	assert(slot_index >= 0)
	assert(slot_index < len(_slots_data))
	var slot = _slots_data[slot_index]
	
	for type in HTerrainTextureSet.SRC_TYPE_COUNT:
		var im_path : String = slot.texture_paths[type]
		_set_ui_slot_texture_from_path(im_path, type)

	_slots_list.select(slot_index)
	
	_normalmap_flip_checkbox.button_pressed = slot.flip_normalmap_y
	_normalmap_material.set_shader_parameter("u_flip_y", slot.flip_normalmap_y)


func _set_ui_slot_texture_from_path(im_path: String, type: int):
	var ed = _texture_editors[type]

	if im_path == "":
		ed.set_texture(null)
		ed.set_texture_tooltip("<empty>")
		return
	
	var im : Image
	
	if im_path.begins_with("#") and im_path.find(".") == -1:
		# The path is actually a preset for a uniform color.
		# This is a feature of packed texture descriptor files.
		# Make a small placeholder image.
		var color := Color(im_path)
		im = Image.create(4, 4, false, Image.FORMAT_RGBA8)
		im.fill(color)
		
	else:
		# Regular path
		im = Image.new()
		var err := im.load(ProjectSettings.globalize_path(im_path))
		if err != OK:
			_logger.error(str("Unable to load image from ", im_path))
			# TODO Different icon for images that can't load?
			ed.set_texture(null)
			ed.set_texture_tooltip("<empty>")
			return

	var tex := ImageTexture.create_from_image(im)
	ed.set_texture(tex)
	ed.set_texture_tooltip(im_path)


func _set_source_image(fpath: String, type: int):
	_set_ui_slot_texture_from_path(fpath, type)

	var slot_index : int = _slots_list.get_selected_items()[0]
	#var prev_path = _texture_set.get_source_image_path(slot_index, type)
	
	var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
	slot.texture_paths[type] = fpath


func _set_import_property(key: String, value):
	var prev_value = _import_settings[key]
	# This is needed, notably because CheckBox emits a signal too when we set it from code...
	if prev_value == value:
		return
		
	_import_settings[key] = value


func _on_texture_load_pressed(type: int):
	_load_texture_type = type
	_load_texture_dialog.popup_centered_ratio()


func _on_LoadTextureDialog_file_selected(fpath: String):
	_set_source_image(fpath, _load_texture_type)
	
	if _load_texture_type == HTerrainTextureSet.SRC_TYPE_ALBEDO:
		_smart_pick_files(fpath)


# Attempts to load source images of other types by looking at how the albedo file was named
func _smart_pick_files(albedo_fpath: String):
	var albedo_words = _smart_pick_file_keywords[HTerrainTextureSet.SRC_TYPE_ALBEDO]
	
	var albedo_fname := albedo_fpath.get_file()
	var albedo_fname_lower = albedo_fname.to_lower()
	var fname_pattern = ""
	
	for albedo_word in albedo_words:
		var i = albedo_fname_lower.find(albedo_word, 0)
		if i != -1:
			fname_pattern = \
				albedo_fname.substr(0, i) + "{0}" + albedo_fname.substr(i + len(albedo_word))
			break
	
	if fname_pattern == "":
		return
	
	var dirpath := albedo_fpath.get_base_dir()
	var fnames := _get_files_in_directory(dirpath, _logger)
	
	var types := [
		HTerrainTextureSet.SRC_TYPE_BUMP,
		HTerrainTextureSet.SRC_TYPE_NORMAL,
		HTerrainTextureSet.SRC_TYPE_ROUGHNESS
	]
	
	var slot_index : int = _slots_list.get_selected_items()[0]
	
	for type in types:
		var slot = _slots_data[slot_index]
		if slot.texture_paths[type] != "":
			# Already set, don't overwrite unwantedly
			continue
		
		var keywords = _smart_pick_file_keywords[type]
		
		for key in keywords:
			var expected_fname = fname_pattern.format([key])
			
			var found := false
			
			for i in len(fnames):
				var fname : String = fnames[i]
				
				# TODO We should probably ignore extensions?
				if fname.to_lower() == expected_fname.to_lower():
					var fpath = dirpath.path_join(fname)
					_set_source_image(fpath, type)
					found = true
					break
			
			if found:
				break


static func _get_files_in_directory(dirpath: String, logger) -> Array:
	var dir := DirAccess.open(dirpath)
	var err := DirAccess.get_open_error()
	if err != OK:
		logger.error("Could not open directory {0}: {1}" \
			.format([dirpath, HT_Errors.get_message(err)]))
		return []
	
	dir.include_hidden = false
	dir.include_navigational = false

	err = dir.list_dir_begin()
	if err != OK:
		logger.error("Could not probe directory {0}: {1}" \
			.format([dirpath, HT_Errors.get_message(err)]))
		return []
	
	var files := []
	var fname := dir.get_next()
	while fname != "":
		if not dir.current_is_dir():
			files.append(fname)
		fname = dir.get_next()
	
	return files


func _on_texture_clear_pressed(type: int):
	_set_source_image("", type)


func _on_SlotsList_item_selected(index: int):
	_select_slot(index)


func _on_ImportModeSelector_item_selected(index: int):
	var mode : int = _import_mode_selector.get_item_id(index)
	if mode != _import_mode:
		#_set_import_property("mode", mode)
		_import_mode = mode
		_update_ui_from_data()


func _on_CompressionSelector_item_selected(index: int):
	var compression : int = _compression_selector.get_item_id(index)
	_set_import_property("compression", compression)


func _on_MipmapsCheckbox_toggled(button_pressed: bool):
	_set_import_property("mipmaps", button_pressed)


func _on_ResolutionSpinBox_value_changed(value):
	_set_import_property("resolution", int(value))


func _on_TextureArrayPrefixLineEdit_text_changed(new_text: String):
	_set_import_property("output_prefix", new_text)


func _on_AddSlotButton_pressed():
	var i := len(_slots_data)
	_slots_data.append(HT_TextureSetImportEditorSlot.new())
	_update_ui_from_data()
	_select_slot(i)


func _on_RemoveSlotButton_pressed():
	if _slots_list.get_item_count() == 0:
		return
	var selected_item = _slots_list.get_selected_items()[0]
	_delete_confirmation_popup.title = "Delete slot {0}".format([selected_item])
	_delete_confirmation_popup.dialog_text = "Delete import slot {0}?".format([selected_item])
	_delete_confirmation_popup.popup_centered()


func _on_delete_confirmation_popup_confirmed():
	var selected_item : int = _slots_list.get_selected_items()[0]
	_slots_data.remove_at(selected_item)
	_update_ui_from_data()


func _on_CancelButton_pressed():
	hide()


func _on_BrowseImportDirectory_pressed():
	_open_dir_dialog.popup_centered_ratio()


func _on_ImportDirectoryLineEdit_text_changed(new_text: String):
	pass


func _on_OpenDirDialog_dir_selected(dir_path: String):
	_import_directory_line_edit.text = dir_path


func _show_error(message: String):
	_error_popup.dialog_text = message
	_error_popup.popup_centered()


func _on_NormalMapFlipY_toggled(button_pressed: bool):
	var slot_index : int = _slots_list.get_selected_items()[0]
	var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
	slot.flip_normalmap_y = button_pressed
	_normalmap_material.set_shader_parameter("u_flip_y", slot.flip_normalmap_y)


# class ButtonDisabler:
# 	var _button : Button

# 	func _init(b: Button):
# 		_button = b
# 		_button.disabled = true

# 	func _notification(what: int):
# 		if what == NOTIFICATION_PREDELETE:
# 			_button.disabled = false


func _get_undo_redo_for_texture_set() -> UndoRedo:
	return _undo_redo_manager.get_history_undo_redo(
		_undo_redo_manager.get_object_history_id(_texture_set))


func _on_ImportButton_pressed():
	if _texture_set == null:
		_show_error("No HTerrainTextureSet selected.")
		return
	
	var import_dir := _import_directory_line_edit.text.strip_edges()

	var prefix := ""
	if _texture_set.resource_path != "":
		prefix = _texture_set.resource_path.get_file().get_basename() + "_"
	
	var files_data_result : HT_Result
	if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
		files_data_result = _generate_packed_images(import_dir, prefix)
	else:
		files_data_result = _generate_packed_texarray_images(import_dir, prefix)

	if not files_data_result.success:
		_show_error(files_data_result.get_message())
		return

	var files_data : Array = files_data_result.value
	
	if len(files_data) == 0:
		_show_error("There are no files to save.\nYou must setup at least one slot of textures.")
		return

	for fd in files_data:
		var dir_path : String = fd.path.get_base_dir()
		if not DirAccess.dir_exists_absolute(dir_path):
			_show_error("The directory {0} could not be found.".format([dir_path]))
			return

	if _WRITE_IMPORT_FILES:
		for fd in files_data:
			var import_fpath = fd.path + ".import"
			if not HT_Util.write_import_file(fd.import_file_data, import_fpath, _logger):
				_show_error("Failed to write file {0}: {1}".format([import_fpath]))
				return

	if _editor_file_system == null:
		_show_error("EditorFileSystem is not setup, can't trigger import system.")
		return

	# Notify Godot's file system that new files were created
	for fd in files_data:
		_editor_file_system.update_file(fd.path)
	
	# Import new files
	var files_paths := PackedStringArray()
	for fd in files_data:
		files_paths.append(fd.path)
	_editor_file_system.reimport_files(files_paths)
	
	var failed_resource_paths := []

	# Using UndoRedo is mandatory for Godot to consider the resource as modified...
	# ...yet if files get deleted, that won't be undoable anyways, but whatever :shrug:
	var ur := _get_undo_redo_for_texture_set()

	# Check imported textures
	if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
		for fd in files_data:
			var texture : Texture2D = load(fd.path)
			if texture == null:
				failed_resource_paths.append(fd.path)
				continue
			fd.texture = texture

	else:
		for fd in files_data:
			var texture_array : TextureLayered = load(fd.path)
			if texture_array == null:
				failed_resource_paths.append(fd.path)
				continue
			fd.texture_array = texture_array

	if len(failed_resource_paths) > 0:
		var failed_list := "\n".join(PackedStringArray(failed_resource_paths))
		_show_error("Some resources failed to load:\n" + failed_list)
		return

	# All is OK, commit action to modify the texture set with imported textures

	if _import_mode == HTerrainTextureSet.MODE_TEXTURES:
		ur.create_action("HTerrainTextureSet: import textures")

		HT_TextureSetEditor.backup_for_undo(_texture_set, ur)

		ur.add_do_method(_texture_set.clear)
		ur.add_do_method(_texture_set.set_mode.bind(_import_mode))

		for i in len(_slots_data):
			ur.add_do_method(_texture_set.insert_slot.bind(-1))
		for fd in files_data:
			ur.add_do_method(_texture_set.set_texture.bind(fd.slot_index, fd.type, fd.texture))

	else:
		ur.create_action("HTerrainTextureSet: import texture arrays")

		HT_TextureSetEditor.backup_for_undo(_texture_set, ur)

		ur.add_do_method(_texture_set.clear)
		ur.add_do_method(_texture_set.set_mode.bind(_import_mode))

		for fd in files_data:
			ur.add_do_method(_texture_set.set_texture_array.bind(fd.type, fd.texture_array))

	ur.commit_action()
	
	_logger.debug("Done importing")

	_info_popup.dialog_text = "Importing complete!"
	_info_popup.popup_centered()

	import_finished.emit()


class HT_PackedImageInfo:
	var path := "" # Where the packed image is saved
	var slot_index : int # Slot in texture set, when using individual textures
	var type : int # 0:Albedo+Bump, 1:Normal+Roughness
	var import_file_data := {} # Data to write into the .import file (when enabled...)
	var image : Image
	var is_default := false
	var texture : Texture2D
	var texture_array : TextureLayered


# Shared code between the two import modes
func _generate_packed_images2() -> HT_Result:
	var resolution : int = _import_settings.resolution
	var images_infos := []
	
	for type in HTerrainTextureSet.TYPE_COUNT:
		var src_types := HTerrainTextureSet.get_src_types_from_type(type)
		
		for slot_index in len(_slots_data):
			var slot : HT_TextureSetImportEditorSlot = _slots_data[slot_index]
			
			# Albedo or Normal
			var src0 : String = slot.texture_paths[src_types[0]]
			# Bump or Roughness
			var src1 : String = slot.texture_paths[src_types[1]]
			
			if src0 == "":
				if src_types[0] == HTerrainTextureSet.SRC_TYPE_ALBEDO:
					return HT_Result.new(false, 
						"Albedo texture is missing in slot {0}".format([slot_index]))
			
			var is_default := (src0 == "" and src1 == "")
			
			if src0 == "":
				src0 = HTerrainTextureSet.get_source_texture_default_color_code(src_types[0])
			if src1 == "":
				src1 = HTerrainTextureSet.get_source_texture_default_color_code(src_types[1])
				
			var pack_sources := {
				"rgb": src0,
				"a": src1
			}

			if HTerrainTextureSet.SRC_TYPE_NORMAL in src_types and slot.flip_normalmap_y:
				pack_sources["normalmap_flip_y"] = true
			
			var packed_image_result := HT_PackedTextureUtil.generate_image(
				pack_sources, resolution, _logger)
			if not packed_image_result.success:
				return packed_image_result
			var packed_image : Image = packed_image_result.value
			
			var fd := HT_PackedImageInfo.new()
			fd.slot_index = slot_index
			fd.type = type
			fd.image = packed_image
			fd.is_default = is_default
			
			images_infos.append(fd)
	
	return HT_Result.new(true).with_value(images_infos)


func _generate_packed_images(import_dir: String, prefix: String) -> HT_Result:
	var images_infos_result := _generate_packed_images2()
	if not images_infos_result.success:
		return images_infos_result
	var images_infos : Array = images_infos_result.value
	
	for info_index in len(images_infos):
		var info : HT_PackedImageInfo = images_infos[info_index]
		
		var type_name := HTerrainTextureSet.get_texture_type_name(info.type)
		var fpath := import_dir.path_join(
			str(prefix, "slot", info.slot_index, "_", type_name, ".png"))
		
		var err := info.image.save_png(fpath)
		if err != OK:
			return HT_Result.new(false, 
				"Could not save image {0}, {1}".format([fpath, HT_Errors.get_message(err)]))
		
		info.path = fpath
		info.import_file_data = {
			"remap": {
				"importer": "texture",
				"type": "CompressedTexture2D"
			},
			"deps": {
				"source_file": fpath
			},
			"params": {
				"compress/mode": ResourceImporterTexture_Unexposed.COMPRESS_VRAM_COMPRESSED,
				"compress/high_quality": false,
				"compress/lossy_quality": 0.7,
				"mipmaps/generate": true,
				"mipmaps/limit": -1,
				"roughness/mode": ResourceImporterTexture_Unexposed.ROUGHNESS_DISABLED,
				"process/fix_alpha_border": false
			}
		}
	
	return HT_Result.new(true).with_value(images_infos)


static func _assemble_texarray_images(images: Array[Image], resolution: Vector2i) -> Image:
	# Godot expects some kind of grid. Let's be lazy and do a grid with only one row.
	var atlas := Image.create(resolution.x * len(images), resolution.y, false, Image.FORMAT_RGBA8)
	for index in len(images):
		var image : Image = images[index]
		if image.get_size() != resolution:
			image.resize(resolution.x, resolution.y, Image.INTERPOLATE_BILINEAR)
		atlas.blit_rect(image, 
			Rect2i(0, 0, image.get_width(), image.get_height()),
			Vector2i(index * resolution.x, 0))
	return atlas
	

func _generate_packed_texarray_images(import_dir: String, prefix: String) -> HT_Result:
	var images_infos_result := _generate_packed_images2()
	if not images_infos_result.success:
		return images_infos_result
	var individual_images_infos : Array = images_infos_result.value

	var resolution : int = _import_settings.resolution
	
	var texarray_images_infos := []
	var slot_count := len(_slots_data)
	
	for type in HTerrainTextureSet.TYPE_COUNT:
		var texarray_images : Array[Image] = []
		texarray_images.resize(slot_count)
		
		var fully_defaulted_slots := 0
		
		for i in slot_count:
			var info : HT_PackedImageInfo = individual_images_infos[type * slot_count + i]
			if info.type == type:
				texarray_images[i] = info.image
			if info.is_default:
				fully_defaulted_slots += 1
		
		if fully_defaulted_slots == len(texarray_images):
			# No need to generate this file at all
			continue
			
		var texarray_image := _assemble_texarray_images(texarray_images, 
			Vector2i(resolution, resolution))
		
		var type_name := HTerrainTextureSet.get_texture_type_name(type)
		var fpath := import_dir.path_join(str(prefix, type_name, "_array.png"))
		
		var err := texarray_image.save_png(fpath)
		if err != OK:
			return HT_Result.new(false, 
				"Could not save image {0}, {1}".format([fpath, HT_Errors.get_message(err)]))
		
		var texarray_image_info := HT_PackedImageInfo.new()
		texarray_image_info.type = type
		texarray_image_info.path = fpath
		texarray_image_info.import_file_data = {
			"remap": {
				"importer": "2d_array_texture",
				"type": "CompressedTexture2DArray"
			},
			"deps": {
				"source_file": fpath
			},
			"params": {
				"compress/mode": ResourceImporterTextureLayered_Unexposed.COMPRESS_VRAM_COMPRESSED,
				"compress/high_quality": false,
				"compress/lossy_quality": 0.7,
				"mipmaps/generate": true,
				"mipmaps/limit": -1,
				"process/fix_alpha_border": false,
				"slices/horizontal": len(texarray_images),
				"slices/vertical": 1
			}
		}
		
		texarray_images_infos.append(texarray_image_info)

	return HT_Result.new(true).with_value(texarray_images_infos)
