//
//
#include "LuaAISEXP.h"

#include "localization/localize.h"
#include "parse/parselo.h"
#include "parse/sexp/LuaSEXP.h"
#include "hud/hudsquadmsg.h"

using namespace luacpp;

namespace sexp {
static const SCP_unordered_set<int> allowed_oswpt_parameters{ OPF_SHIP, OPF_WING, OPF_SHIP_POINT, OPF_SHIP_WING, OPF_SHIP_WING_WHOLETEAM, OPF_SHIP_WING_SHIPONTEAM_POINT, OPF_SHIP_WING_POINT, OPF_SHIP_WING_POINT_OR_NONE, OPF_POINT };

LuaAISEXP::LuaAISEXP(const SCP_string& name) : LuaSEXP(name) {
	_return_type = OPR_AI_GOAL;
	_category = OP_CATEGORY_AI;
	_subcategory = OP_SUBCATEGORY_NONE;
}

void LuaAISEXP::initialize() {
};

int LuaAISEXP::getMinimumArguments() const {
	return _arg_type == -1 ? 1 : 2;
};

int LuaAISEXP::getMaximumArguments() const {
	if (LuaSEXP::getMaximumArguments() == INT_MAX)
		return INT_MAX;
	return getMinimumArguments() + LuaSEXP::getMaximumArguments();
};

int LuaAISEXP::getArgumentType(int argnum) const {
	const int minArgs = getMinimumArguments();
	if (argnum < minArgs) {
		return argnum == 0 && _arg_type != -1 ? _arg_type : OPF_POSITIVE;
	}
	else {
		return LuaSEXP::getArgumentType(argnum - minArgs);
	}
};

int LuaAISEXP::execute(int /*node*/, int /*parent_node*/)
{
	UNREACHABLE("Tried to execute AI Lua SEXP %s! AI-Goal SEXPs should never be run.", _name.c_str());
	return SEXP_CANT_EVAL;
}

bool LuaAISEXP::parseCheckEndOfDescription() {
	// Since we're stuffing strings, this is the best way to "unstuff" a string
	// if we determine we're finished with the description - and we also want
	// to preserve whitespace (which the parser eats) while building the description
	pause_parse();

	// look for any token that can follow $Description
	auto possible_tokens =
	{
		"$Operator:",
		"#End",
		"+HUD String:",
		"$Target Parameter:",
		"$Player Order:",
		"$Additional Parameter:",
		"$Minimum Additional Arguments:",
		"$Maximum Additional Arguments:"
	};

	bool found = false;
	for (auto token : possible_tokens)
	{
		if (optional_string(token))
		{
			found = true;
			break;
		}
	}

	unpause_parse();
	return found;
}

void LuaAISEXP::parseTable() {
	SCP_stringstream help_text;
	help_text << _name << "\r\n";

	required_string("$Description:");

	SCP_string description, extra;
	stuff_string(description, F_NAME);
	while (!parseCheckEndOfDescription()) {
		while (skip_eoln()) {
			description += "\r\n";
		}
		stuff_string(extra, F_NAME);
		description += extra;
	}
	help_text << "\t" << description << "\r\n\r\n";

	if (optional_string("+HUD String:")) {
		SCP_string hudTextParse;
		stuff_string(hudTextParse, F_NAME);
		hudText = vm_strdup(hudTextParse.c_str());
	}

	int paramNum = 1;
	if (optional_string("$Target Parameter:")) {
		needsTarget = true;
		required_string("+Description:");

		SCP_string param_desc;
		stuff_string(param_desc, F_NAME);

		help_text << "\t1";
		help_text << ": " << param_desc << "\r\n";

		required_string("+Type:");
		SCP_string type_str;
		stuff_string(type_str, F_NAME);

		auto type = LuaSEXP::get_parameter_type(type_str);
		if (type.second < 0 || allowed_oswpt_parameters.count(type.second) <= 0) {
			error_display(0, "Parameter type '%s' is not a valid target type!", type_str.c_str());
			type = LuaSEXP::get_parameter_type("ship");
		}

		_arg_type = type.second;

		paramNum++;
	}

	help_text << "\t" << paramNum;
	help_text << ": Goal priority (number between 0 and 200. Player orders have a priority of 90-100).\r\n";


	if (optional_string("$Player Order:")) {
		playerOrder = std::make_unique<player_order_lua>();
		auto &order = *playerOrder;
		if (_arg_type != OPF_SHIP && _arg_type != -1) {
			error_display(1, "Player orders must have either no target or a ship-type target parameter!");
		}

		if (optional_string("+General Order:")) {
			stuff_boolean(&order.generalOrder);

			order.category = XSTR("General", 1807);
			if (optional_string("+Category:")) {
				stuff_string(order.category, F_NAME);

				if (order.category.length() <= 0) {
					error_display(1, "Order category name must be longer than 0 characters!");
				}

				// Now get a list of all lua categories to add. Meow.
				SCP_vector<SCP_string> lua_cat_list = ai_lua_get_general_order_categories();

				// If we have too many categories then we have an oopsie!
				if ((int)lua_cat_list.size() > (MAX_MENU_ITEMS - NUM_COMM_ORDER_TYPES)) {
					Warning(LOCATION, "Too many defined Lua General Order Categories! Setting order to first available: %s", lua_cat_list[0].c_str());

					order.category = lua_cat_list[0];
				}

				// General orders explicitely do not show up in FRED as sexps because they
				// they are only meant for the comms board
				_return_type = OPR_NONE;
				_category = OP_CATEGORY_NONE;
				_subcategory = OP_SUBCATEGORY_NONE;
			}
		}

		required_string("+Display String:");
		stuff_string(order.displayText, F_NAME);

		order.parseText = order.displayText;
		if (optional_string("+Parse String:")) {
			stuff_string(order.parseText, F_NAME);
		}

		if (optional_string("+Target Restrictions:")) {
			int result = optional_string_one_of(9, "Allies", "All", "Own Team", "Hostiles", "Same Wing", "Player Wing", "Capitals", "Allied Capitals", "Enemy Capitals", "Not Self");
			if (result == -1) {
				error_display(0, "Unknown target restriction for player order %s. Assuming \"All\".", order.displayText.c_str());
				order.targetRestrictions = player_order_lua::target_restrictions::TARGET_ALL;
			}
			else {
				order.targetRestrictions = static_cast<player_order_lua::target_restrictions>(result);
			}
		}

		if (optional_string("+Ship Restrictions:")) {
			int result = optional_string_one_of(4, "Any", "Wing", "On Player Wing", "Player Wing");
			if (result == -1) {
				error_display(0, "Unknown ship restriction for player order %s. Assuming \"	Any\".", order.displayText.c_str());
				order.shipRestrictions = player_order_lua::ship_restrictions::ANY;
			}
			else {
				order.shipRestrictions = static_cast<player_order_lua::ship_restrictions>(result);
			}
		}

		if (optional_string("+Acknowledge Message:")) {
			SCP_string message;
			stuff_string(message, F_NAME);
			int result = get_builtin_message_type(message.c_str());

			if (result == MESSAGE_NONE) {
				error_display(0, "Unknown acknowledge message for player order %s. Assuming \"Yes\".", order.displayText.c_str());
				order.ai_message = MESSAGE_YESSIR;
			}
			else {
				order.ai_message = result;
			}
		}

	}

	if (optional_string("$Minimum Additional Arguments:")) {
		stuff_int(&_min_args);

		if (_min_args < 0) {
			error_display(0, "Minimum argument number must be at least 0! Got %d.", _min_args);
			_min_args = 0;
		}
	}
	else {
		_min_args = 0;
	}

	if (optional_string("$Maximum Additional Arguments:")) {
		stuff_int(&_max_args);

		if (_max_args < _min_args) {
			error_display(0, "Maximum argument number must be at least %d, the number of minimum arguments! Got %d.", _min_args, _max_args);
			_max_args = _min_args;
		}
	}
	else {
		_max_args = INT_MAX;
	}

	bool variable_arg_part = false;
	while (optional_string("$Additional Parameter:")) {
		required_string("+Description:");

		SCP_string param_desc;
		stuff_string(param_desc, F_NAME);

		if (variable_arg_part) {
			help_text << "\t" << _varargs_type_pattern.size() + 1;
		}
		else {
			help_text << "\t" << ++paramNum;
		}
		help_text << ": " << param_desc << "\r\n";

		required_string("+Type:");
		SCP_string type_str;
		stuff_string(type_str, F_NAME);

		auto type = get_parameter_type(type_str);
		if (type.second < 0) {
			error_display(0, "Unknown parameter type '%s'!", type_str.c_str());
			type = get_parameter_type("string");
		}

		if (variable_arg_part) {
			_varargs_type_pattern.push_back(std::move(type));
		}
		else {
			_argument_types.push_back(std::move(type));
		}

		if (optional_string("$Repeat")) {
			if (!variable_arg_part) {
				help_text << "Rest: (The following pattern repeats)\r\n";
				variable_arg_part = true;
			}
			else {
				error_display(0, "A second $Repeat has been encountered! Only one may appear in the parameter list.");
			}
		}
	}

	if (!variable_arg_part) {
		if (_max_args != INT_MAX && _max_args != (int)_argument_types.size()) {
			error_display(1, "Maximum Additional Argument count does not match number of specified arguments!");
		}
		_max_args = (int)_argument_types.size();
	}

	_help_text = help_text.str();
	lcl_replace_stuff(_help_text, true);
}

void LuaAISEXP::setActionEnter(const luacpp::LuaFunction& action) {
	_actionEnter = action;
}
luacpp::LuaFunction LuaAISEXP::getActionEnter() const {
	return _actionEnter;
}

void LuaAISEXP::setAchievable(const luacpp::LuaFunction& action) {
	_achievable = action;
}
luacpp::LuaFunction LuaAISEXP::getAchievable() const {
	return _achievable;
}

void LuaAISEXP::setTargetRestrict(const luacpp::LuaFunction& action) {
	_targetRestrict = action;
}
luacpp::LuaFunction LuaAISEXP::getTargetRestrict() const {
	return _targetRestrict;
}

void LuaAISEXP::registerAIMode(int sexp_id) const {
	ai_lua_add_mode(sexp_id, ai_mode_lua{ *this, needsTarget, hudText });
}

void LuaAISEXP::maybeRegisterPlayerOrder(int sexp_id) const {
	if (playerOrder == nullptr)
		return;
	ai_lua_add_order(sexp_id, *playerOrder);
}

}
