/**
 * bt_evaluate_expression.cpp
 * =============================================================================
 * Copyright 2021-2024 Serhii Snitsaruk
 * Copyright 2024 Wilson E. Alvarez
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 * =============================================================================
 */

#include "bt_evaluate_expression.h"

#include "../../../util/limbo_compat.h"
#include "../../../util/limbo_utility.h"

#ifdef LIMBOAI_GDEXTENSION
#include "godot_cpp/classes/global_constants.hpp"
#endif // LIMBOAI_GDEXTENSION

//**** Setters / Getters

void BTEvaluateExpression::set_expression_string(const String &p_expression_string) {
	expression_string = p_expression_string;
	emit_changed();
}

void BTEvaluateExpression::set_node_param(Ref<BBNode> p_object) {
	node_param = p_object;
	emit_changed();
	if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid() &&
			!node_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
		node_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
	}
}

void BTEvaluateExpression::set_input_include_delta(bool p_input_include_delta) {
	if (input_include_delta != p_input_include_delta) {
		processed_input_values.resize(input_values.size() + int(p_input_include_delta));
	}
	input_include_delta = p_input_include_delta;
	emit_changed();
}

void BTEvaluateExpression::set_input_names(const PackedStringArray &p_input_names) {
	input_names = p_input_names;
	emit_changed();
}

void BTEvaluateExpression::set_input_values(const TypedArray<BBVariant> &p_input_values) {
	if (input_values.size() != p_input_values.size()) {
		processed_input_values.resize(p_input_values.size() + int(input_include_delta));
	}
	input_values = p_input_values;
	emit_changed();
}

void BTEvaluateExpression::set_result_var(const StringName &p_result_var) {
	result_var = p_result_var;
	emit_changed();
}

//**** Task Implementation

PackedStringArray BTEvaluateExpression::get_configuration_warnings() {
	PackedStringArray warnings = BTAction::get_configuration_warnings();
	if (expression_string.is_empty()) {
		warnings.append("Expression string is not set.");
	}
	if (node_param.is_null()) {
		warnings.append("Node parameter is not set.");
	} else if (node_param->get_value_source() == BBParam::SAVED_VALUE && node_param->get_saved_value() == Variant()) {
		warnings.append("Path to node is not set.");
	} else if (node_param->get_value_source() == BBParam::BLACKBOARD_VAR && node_param->get_variable() == String()) {
		warnings.append("Node blackboard variable is not set.");
	}
	return warnings;
}

void BTEvaluateExpression::_setup() {
	parse();
	ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression->get_error_text());
}

Error BTEvaluateExpression::parse() {
	PackedStringArray processed_input_names;
	processed_input_names.resize(input_names.size() + int(input_include_delta));
	String *processed_input_names_ptr = processed_input_names.ptrw();
	if (input_include_delta) {
		processed_input_names_ptr[0] = "delta";
	}
	for (int i = 0; i < input_names.size(); ++i) {
		processed_input_names_ptr[i + int(input_include_delta)] = input_names[i];
	}

	is_parsed = expression->parse(expression_string, processed_input_names);
	return is_parsed;
}

String BTEvaluateExpression::_generate_name() {
	return vformat("EvaluateExpression %s  node: %s  %s",
			!expression_string.is_empty() ? expression_string : "???",
			node_param.is_valid() && !node_param->to_string().is_empty() ? node_param->to_string() : "???",
			result_var == StringName() ? "" : LimboUtility::get_singleton()->decorate_output_var(result_var));
}

BT::Status BTEvaluateExpression::_tick(double p_delta) {
	ERR_FAIL_COND_V_MSG(expression_string.is_empty(), FAILURE, "BTEvaluateExpression: Expression String is not set.");
	ERR_FAIL_COND_V_MSG(node_param.is_null(), FAILURE, "BTEvaluateExpression: Node parameter is not set.");
	Object *obj = node_param->get_value(get_scene_root(), get_blackboard());
	ERR_FAIL_COND_V_MSG(obj == nullptr, FAILURE, "BTEvaluateExpression: Failed to get object: " + node_param->to_string());
	ERR_FAIL_COND_V_MSG(is_parsed != Error::OK, FAILURE, "BTEvaluateExpression: Failed to parse expression: " + expression->get_error_text());

	if (input_include_delta) {
		processed_input_values[0] = p_delta;
	}
	for (int i = 0; i < input_values.size(); ++i) {
		const Ref<BBVariant> &bb_variant = input_values[i];
		processed_input_values[i + int(input_include_delta)] = bb_variant->get_value(get_scene_root(), get_blackboard());
	}

	Variant result = expression->execute(processed_input_values, obj, false);
	ERR_FAIL_COND_V_MSG(expression->has_execute_failed(), FAILURE, "BTEvaluateExpression: Failed to execute: " + expression->get_error_text());

	if (result_var != StringName()) {
		get_blackboard()->set_var(result_var, result);
	}

	return SUCCESS;
}

//**** Godot

void BTEvaluateExpression::_bind_methods() {
	ClassDB::bind_method(D_METHOD("parse"), &BTEvaluateExpression::parse);
	ClassDB::bind_method(D_METHOD("set_expression_string", "expression_string"), &BTEvaluateExpression::set_expression_string);
	ClassDB::bind_method(D_METHOD("get_expression_string"), &BTEvaluateExpression::get_expression_string);
	ClassDB::bind_method(D_METHOD("set_node_param", "param"), &BTEvaluateExpression::set_node_param);
	ClassDB::bind_method(D_METHOD("get_node_param"), &BTEvaluateExpression::get_node_param);
	ClassDB::bind_method(D_METHOD("set_input_names", "input_names"), &BTEvaluateExpression::set_input_names);
	ClassDB::bind_method(D_METHOD("get_input_names"), &BTEvaluateExpression::get_input_names);
	ClassDB::bind_method(D_METHOD("set_input_values", "input_values"), &BTEvaluateExpression::set_input_values);
	ClassDB::bind_method(D_METHOD("get_input_values"), &BTEvaluateExpression::get_input_values);
	ClassDB::bind_method(D_METHOD("set_input_include_delta", "input_include_delta"), &BTEvaluateExpression::set_input_include_delta);
	ClassDB::bind_method(D_METHOD("is_input_delta_included"), &BTEvaluateExpression::is_input_delta_included);
	ClassDB::bind_method(D_METHOD("set_result_var", "variable"), &BTEvaluateExpression::set_result_var);
	ClassDB::bind_method(D_METHOD("get_result_var"), &BTEvaluateExpression::get_result_var);

	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_node_param", "get_node_param");
	ADD_PROPERTY(PropertyInfo(Variant::STRING, "expression_string"), "set_expression_string", "get_expression_string");
	ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "result_var"), "set_result_var", "get_result_var");
	ADD_GROUP("Inputs", "input_");
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_include_delta"), "set_input_include_delta", "is_input_delta_included");
	ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "input_names", PROPERTY_HINT_ARRAY_TYPE, "String"), "set_input_names", "get_input_names");
	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "input_values", PROPERTY_HINT_ARRAY_TYPE, RESOURCE_TYPE_HINT("BBVariant")), "set_input_values", "get_input_values");
}

BTEvaluateExpression::BTEvaluateExpression() {
	expression.instantiate();
}