## Used to properly setup and execute commands, i.e. those that extend `ESCBaseCommand`.
##
## Despite its name, `ESCCommand` is more of a container and orchestrator than an actual command.
extends ESCStatement
class_name ESCCommand


## The name of this command.
var name: String

## Parameters for this command
var parameters: Array = []

## The token generated by the parser containing info for debugging (e.g. line number,
## script filename, etc.).
var parser_token: ESCToken = null


## A `Dictionary` containing the data needed when saving a game.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a `Dictionary` containing the data needed when saving a game. (`Dictionary`)
func exported() -> Dictionary:
	var export_dict: Dictionary = super.exported()
	export_dict.class = "ESCCommand"
	export_dict.name = name
	export_dict.parameters = parameters

	return export_dict


## `true` iff the command is able to be run.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns `true` iff the command is able to be run. (`bool`)
func is_valid() -> bool:
	if not _command_exists():
		escoria.logger.error(
			self,
			"Invalid command detected: %s - " % self.name +
				"Command implementation not found in any command directory."
		)
		return false

	return super.is_valid()


# Checks that the command exists
#
# *Returns* True if the command exists, else false.
func _command_exists() -> bool:
	for base_path in ESCProjectSettingsManager.get_setting(
			ESCProjectSettingsManager.COMMAND_DIRECTORIES
		):
		var command_path = "%s/%s.gd" % [
			base_path.trim_suffix("/"),
			self.name
		]
		if ResourceLoader.exists(command_path):
			return true

	return false


## Runs the command contained by this `ESCCommand`.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |command_params|`Variant`|an array containing the arguments|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns a `int` value. (`int`)
func run() -> int:
	var command_object = escoria.command_registry.load_command(self.name)

	_decorate_for_debugging(command_object)

	if command_object == null:
		return ESCExecution.RC_ERROR
	else:
		var argument_descriptor = command_object.configure()

		_decorate_for_debugging(argument_descriptor)

		var prepared_arguments = argument_descriptor.prepare_arguments(
			self.parameters
		)

		if command_object.validate(prepared_arguments):
			escoria.logger.debug(
				self,
				"Running command %s with parameters %s."
						% [self.name, prepared_arguments]
			)
			escoria.event_manager.add_running_command(self)
			var rc = await command_object.run(prepared_arguments)
			escoria.event_manager.running_command_finished(self)
			escoria.logger.debug(
				self,
				"[%s] Return code: %d." % [self.name, rc]
			)
			return rc
		else:
			return ESCExecution.RC_ERROR


## This function interrupts the command. If it was not started, it will not run. If it had already started, the execution will be considered as finished immediately and finish. If it had already finished, nothing will happen.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func interrupt():
	_is_interrupted = true
	var command = escoria.command_registry.is_command_or_control_pressed(self.name)
	if command.has_method("interrupt"):
		command.interrupt()

## Override of built-in _to_string function to display the statement.[br]
## [br]
## #### Parameters[br]
## [br]
## None.
## [br]
## #### Returns[br]
## [br]
## Returns a string representation of the command. (`String`)
func _to_string() -> String:
	return "Command %s with parameters: %s" % [name, str(parameters)]

## Adds additional info to the command object, i.e. for debugging.[br]
## [br]
## #### Parameters[br]
## [br]
## | Name | Type | Description | Required? |[br]
## |:-----|:-----|:------------|:----------|[br]
## |command_object|`Variant`|The command object to decorate.|yes|[br]
## [br]
## #### Returns[br]
## [br]
## Returns nothing.
func _decorate_for_debugging(command_object) -> void:
	command_object.filename = parser_token.get_filename()
	command_object.line_number = parser_token.get_line()
