## An abstraction class to expose the same animation methods for both
## AnimatedSprite and AnimationPlayer.
extends Node
class_name ESCAnimationPlayer


## Signal emitted when the animation finished playing.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`Variant`|Name of the animation that completed playback.|yes|[br]
## [br]
signal animation_finished(name)


## The actual player node.
var _player_node: Node

## An AnimationPlayer typed reference to the player node (for intellisense).
var _animation_player: AnimationPlayer

## An AnimatedSprite2D typed reference to the player node (for intellisense).
var _animated_sprite: AnimatedSprite2D

## Whether the player node is of type AnimationPlayer (just for convenience).
var _is_animation_player: bool = false

## Currently running animation.
var _current_animation: String = ""


## Create a new animation player.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |node|`Node`|The actual player node.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _init(node: Node):
	_player_node = node
	if node is AnimationPlayer:
		_is_animation_player = true
		_animation_player = node
	else:
		_animated_sprite = node
	node.add_child(self)


## Ready function. Connects animation signals.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _ready() -> void:
	if _is_animation_player:
		_player_node.animation_finished.connect(_on_animation_finished)
	else:
		_player_node.animation_finished.connect(_on_animation_finished_animated_sprite)


## Return the currently playing animation.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a `String` value. (`String`)
func get_animation() -> String:
	if _is_animation_player:
		return _animation_player.assigned_animation
	else:
		return _animated_sprite.animation


## A list of all animation names.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a list of all animation names. (`PackedStringArray`)
func get_animations() -> PackedStringArray:
	if _is_animation_player:
		return _animation_player.get_animation_list()
	else:
		return _animated_sprite.sprite_frames.get_animation_names()


## Whether the animation is playing.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a `bool` value. (`bool`)
func is_playing() -> bool:
	return _player_node.is_playing()


## Stop the animation.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func stop():
	_player_node.stop()


## Play the animation.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Animation name to play.|yes|[br]
## |backwards|`bool`|Play backwards.|no|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func play(name: String, backwards: bool = false):
	if _is_animation_player and _animation_player.current_animation != "":
		_animation_player.seek(0)
	elif not _is_animation_player:
		_animated_sprite.frame = 0

	_current_animation = name

	if backwards and _is_animation_player:
		_animation_player.play_backwards(name)
	elif backwards:
		_animated_sprite.play(name, true)
	else:
		_player_node.play(name)

		# Instead of waiting for the next frame, start the animation now.
		if _is_animation_player:
			_player_node.advance(0)


## Play the given animation backwards.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Animation name to play in reverse.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func play_backwards(name: String):
	self.play(name, true)


## Check if the given animation exists.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Animation name to test for availability.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns a `bool` value. (`bool`)
func has_animation(name: String) -> bool:
	if _is_animation_player:
		return _animation_player.has_animation(name)
	else:
		return _animated_sprite.sprite_frames.has_animation(name)


## Play an animation and directly skip to the end.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Animation name to jump to the last frame of.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func seek_end(name: String):
	if _is_animation_player:
		_animation_player.current_animation = name
		_animation_player.seek(_animation_player.get_animation(name).length, true)
	else:
		_animated_sprite.animation = name
		_animated_sprite.frame = _animated_sprite.sprite_frames.get_frame_count(name)


## Get the length of the specified animation.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Animation name whose duration should be returned.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns a `float` value. (`float`)
func get_length(name: String) -> float:
	if _is_animation_player:
		return _animation_player.get_animation(name).length
	else:
		return _animated_sprite.sprite_frames.get_frame_count(name) - 1 * \
				_animated_sprite.sprite_frames.get_animation_speed(name)


## Return true if the ESCAnimationPlayer node is valid, ie. it has a valid player node.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a `bool` value. (`bool`)
func is_valid() -> bool:
	return _player_node != null and _player_node is Node


## Transport the animation_finished signal.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |name|`String`|Name of the animation that triggered the callback.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _on_animation_finished(name: String):
	if _is_animation_player and not _animation_player.get_animation(name).loop_mode != Animation.LOOP_NONE:
		_animation_player.stop(true) # param here is to keep current state and
									 # avoid resetting animation position to 0
	elif not _animated_sprite.sprite_frames.get_animation_loop(name):
		_animated_sprite.stop()
	animation_finished.emit(name)


## Special signal handler for animated sprites.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _on_animation_finished_animated_sprite():
	_on_animation_finished(_current_animation)
