@tool
class_name DialogicNode_PortraitContainer
extends Control

## Node that defines a position for dialogic portraits and how to display portraits at that position.

enum PositionModes {
	POSITION, ## This container can be joined/moved to with the Character Event
	SPEAKER,  ## This container is joined/left automatically based on the speaker.
	}

@export var mode := PositionModes.POSITION

@export_subgroup('Mode: Position')
## The position this node corresponds to.
@export var container_ids: PackedStringArray = ["1"]


@export_subgroup('Mode: Speaker')
## Can be used to use a different portrait.
## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
@export var portrait_prefix := ''

@export_subgroup('Portrait Placement')
enum SizeModes {
	KEEP, ## The height and width of the container have no effect, only the origin.
	FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
	FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
	FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
	}
## Defines how to affect the scale of the portrait
@export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT :
	set(mode):
		size_mode = mode
		_update_debug_portrait_transform()

## If true, portraits will be mirrored in this position.
@export var mirrored := false :
	set(mirror):
		mirrored = mirror
		_update_debug_portrait_scene()


@export_group('Origin', 'origin')
enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
## The portrait will be placed relative to this point in the container.
@export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER :
	set(anchor):
		origin_anchor = anchor
		_update_debug_origin()

## An offset to apply to the origin. Rarely useful.
@export var origin_offset := Vector2() :
	set(offset):
		origin_offset = offset
		_update_debug_origin()

enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
## Usually you want to rotate or scale around the portrait origin.
## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
@export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
@export var pivot_value := Vector2()

@export_group('Debug', 'debug')
## A character that will be displayed in the editor, useful for getting the right size.
@export var debug_character: DialogicCharacter = null:
	set(character):
		debug_character = character
		_update_debug_portrait_scene()
@export var debug_character_portrait := "":
	set(portrait):
		debug_character_portrait = portrait
		_update_debug_portrait_scene()

var debug_character_holder_node: Node2D = null
var debug_character_scene_node: Node = null
var debug_origin: Sprite2D = null
var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
# Used if no debug character is specified
var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))

var ignore_resize := false


func _ready() -> void:
	match mode:
		PositionModes.POSITION:
			add_to_group('dialogic_portrait_con_position')
		PositionModes.SPEAKER:
			add_to_group('dialogic_portrait_con_speaker')

	if Engine.is_editor_hint():
		resized.connect(_update_debug_origin)

		if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
			default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')

		debug_origin = Sprite2D.new()
		add_child(debug_origin)
		debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")

		_update_debug_origin()
		_update_debug_portrait_scene()
	else:
		resized.connect(update_portrait_transforms)


################################################################################
##						MAIN METHODS
################################################################################

func update_portrait_transforms() -> void:
	if ignore_resize:
		return

	match pivot_mode:
		PivotModes.AT_ORIGIN:
			pivot_offset = _get_origin_position()
		PivotModes.PERCENTAGE:
			pivot_offset = size*pivot_value
		PivotModes.PIXELS:
			pivot_offset = pivot_value

	for child in get_children():
		DialogicUtil.autoload().Portraits._update_character_transform(child)


## Returns a Rect2 with the position as the position and the scale as the size.
func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
	var transform := Rect2()
	transform.position = _get_origin_position()

	# Mode that ignores the containers size
	if size_mode == SizeModes.KEEP:
		transform.size = Vector2(1,1) * character_scale

	# Mode that makes sure neither height nor width go out of container
	elif size_mode == SizeModes.FIT_IGNORE_SCALE:
		if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
			transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
		else:
			transform.size = Vector2(1,1) * size.y/portrait_rect.size.y

	# Mode that stretches the portrait to fill the whole container
	elif size_mode == SizeModes.FIT_STRETCH:
		transform.size = size/portrait_rect.size

	# Mode that size the character so 100% size fills the height
	elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
		transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale

	return transform


## Returns the current origin position
func _get_origin_position(rect_size = null) -> Vector2:
	if rect_size == null:
		rect_size = size
	return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset


func is_container(id:Variant) -> bool:
	return str(id) in container_ids

#region DEBUG METHODS
################################################################################
### USE THIS TO DEBUG THE POSITIONS
#func _draw():
	#draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
	#draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
#
#func _process(delta:float) -> void:
	#queue_redraw()


## Loads the debug_character with the debug_character_portrait
## Creates a holder node and applies mirror
func _update_debug_portrait_scene() -> void:
	if !Engine.is_editor_hint():
		return
	if is_instance_valid(debug_character_holder_node):
		for child in get_children():
			if child != debug_origin:
				child.free()

	# Get character
	var character := _get_debug_character()
	if not character is DialogicCharacter or character.portraits.is_empty():
		return

	# Determine portrait
	var debug_portrait := debug_character_portrait
	if debug_portrait.is_empty():
		debug_portrait = character.default_portrait
	if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
		if portrait_prefix+debug_portrait in character.portraits:
			debug_portrait = portrait_prefix+debug_portrait
	if not debug_portrait in character.portraits:
		debug_portrait = character.default_portrait

	var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)

	# Determine scene
	var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene)
	if portrait_scene_path.is_empty():
		portrait_scene_path = default_portrait_scene

	debug_character_scene_node = load(portrait_scene_path).instantiate()

	if !is_instance_valid(debug_character_scene_node):
		return

	# Load portrait
	DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
	debug_character_scene_node._update_portrait(character, debug_portrait)

	# Add character node
	if !is_instance_valid(debug_character_holder_node):
		debug_character_holder_node = Node2D.new()
		add_child(debug_character_holder_node)

	# Add portrait node
	debug_character_holder_node.add_child(debug_character_scene_node)
	move_child(debug_character_holder_node, 0)
	debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))

	_update_debug_portrait_transform()


## Set's the size and position of the holder and scene node
## according to the size_mode
func _update_debug_portrait_transform() -> void:
	if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
		return
	var character := _get_debug_character()
	var portrait_info := character.get_portrait_info(debug_character_portrait)
	var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
	debug_character_holder_node.position = transform.position
	debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset

	debug_character_holder_node.scale = transform.size


## Updates the debug origins position. Also calls _update_debug_portrait_transform()
func _update_debug_origin() -> void:
	if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
		return
	debug_origin.position = _get_origin_position()
	_update_debug_portrait_transform()



## Returns the debug character or the default debug character
func _get_debug_character() -> DialogicCharacter:
	return debug_character if debug_character != null else default_debug_character

#endregion
