/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/jacl/jacl.h"
#include "glk/jacl/language.h"
#include "glk/jacl/types.h"
#include "glk/jacl/prototypes.h"

namespace Glk {
namespace JACL {

#ifdef GLK
extern uint                     status_width, status_height;
extern winid_t                  statuswin;
#endif

#ifdef __NDS__
extern int                      screen_width;
extern int                      screen_depth;
#endif

extern struct object_type       *object[];
extern struct integer_type      *integer_table;
extern struct integer_type      *integer[];
extern struct cinteger_type     *cinteger_table;
extern struct attribute_type    *attribute_table;
extern struct string_type       *string_table;
extern struct string_type       *cstring_table;
extern struct function_type     *function_table;
extern struct function_type     *executing_function;
extern struct command_type      *completion_list;
extern struct word_type         *grammar_table;
extern struct synonym_type      *synonym_table;
extern struct filter_type       *filter_table;

extern char                     function_name[];
extern char                     temp_buffer[];
extern char                     error_buffer[];
extern char                     integer_buffer[16];

#ifndef GLK
#ifndef __NDS__
extern char                     game_url[];
extern char                     user_id[];
#endif
#endif

extern int                      noun[];
extern int                      quoted[];
extern int                      percented[];
extern const char               *word[];

extern int                      resolved_attribute;

extern int                      objects;
extern int                      integers;
extern int                      player;
extern int                      oec;
extern int                      *object_element_address;
extern int                      *object_backup_address;

extern int                      value_resolved;

char                            macro_function[84];
int                             value_has_been_resolved;

int *container_resolve(const char *container_name) {
	container_name = arg_text_of(container_name);

	/* IN JACL, A 'CONTAINER' IS ANYTHING THAT CAN STORE AN INTEGER */
	struct integer_type *resolved_integer;

	if ((resolved_integer = integer_resolve(container_name)) != nullptr)
		return (&resolved_integer->value);
	else if (object_element_resolve(container_name))
		return (object_element_address);
	else if (!strcmp(container_name, "noun1"))
		return (&noun[0]);
	else if (!strcmp(container_name, "noun2"))
		return (&noun[1]);
	else if (!strcmp(container_name, "noun3"))
		return (&noun[2]);
	else if (!strcmp(container_name, "noun4"))
		return (&noun[3]);
	else if (!strcmp(container_name, "player"))
		return (&player);
	else if (!strcmp(container_name, "here"))
		return (&object[player]->PARENT);
	else
		return ((int *) nullptr);
}

const char *var_text_of_word(int wordnumber) {
	const char *value;

	if (percented[wordnumber] == FALSE) {
		return (word[wordnumber]);
	} else {
		value_has_been_resolved = TRUE;
		value = arg_text_of(word[wordnumber]);
		while (value_has_been_resolved && percented[wordnumber]) {
			value = arg_text_of(value);
			percented[wordnumber]--;
		}

		return (value);
	}
}

const char *arg_text_of_word(int wordnumber) {
	const char *value;

	if (quoted[wordnumber] == 1) {
		return (word[wordnumber]);
	} else {
		value_has_been_resolved = TRUE;
		value = arg_text_of(word[wordnumber]);
		while (value_has_been_resolved && percented[wordnumber]) {
			value = arg_text_of(value);
			percented[wordnumber]--;
		}

		return (value);
	}
}

const char *text_of_word(int wordnumber) {
	const char *value;

	if (quoted[wordnumber] == 1) {
		return (word[wordnumber]);
	} else {
		value_has_been_resolved = TRUE;
		value = text_of(word[wordnumber]);
		while (value_has_been_resolved && percented[wordnumber]) {
			value = text_of(value);
			percented[wordnumber]--;
		}

		return (value);
	}
}

const char *text_of(const char *string) {
	struct integer_type *resolved_integer;
	struct cinteger_type *resolved_cinteger;
	struct string_type *resolved_string;
	struct string_type *resolved_cstring;
	char            *return_string;

	int             index;

	/* CHECK IF THE SUPPLIED STRING IS THE NAME OF A STRING CONSTANT,
	 * IF NOT, RETURN THE STRING LITERAL */
	if ((return_string = macro_resolve(string)) != nullptr) {
		value_has_been_resolved = FALSE;
		return (return_string);
	} else if ((resolved_integer = integer_resolve(string)) != nullptr) {
		value_has_been_resolved = FALSE;
		integer_buffer[0] = 0;
		Common::sprintf_s(integer_buffer, "%d", resolved_integer->value);
		return (integer_buffer);
	} else if ((resolved_cinteger = cinteger_resolve(string)) != nullptr) {
		value_has_been_resolved = FALSE;
		integer_buffer[0] = 0;
		Common::sprintf_s(integer_buffer, "%d", resolved_cinteger->value);
		return (integer_buffer);
	} else if (object_element_resolve(string)) {
		value_has_been_resolved = FALSE;
		integer_buffer[0] = 0;
		Common::sprintf_s(integer_buffer, "%d", oec);
		return (integer_buffer);
	} else if ((index = object_resolve(string)) != -1) {
		value_has_been_resolved = FALSE;
		if (index < 1 || index > objects) {
			badptrrun(string, index);
			return ("");
		} else {
			return (object[index]->label);
		}
	} else if ((resolved_string = string_resolve(string)) != nullptr) {
		return (resolved_string->value);
	} else if ((resolved_cstring = cstring_resolve(string)) != nullptr) {
		return (resolved_cstring->value);
	} else if (function_resolve(string) != nullptr) {
		value_has_been_resolved = FALSE;
		Common::sprintf_s(integer_buffer, "%d", execute(string));
		return (integer_buffer);
#ifndef GLK
#ifndef __NDS__
	} else if (!strcmp(string, "$url")) {
		value_has_been_resolved = FALSE;
		return (game_url);
	} else if (!strcmp(string, "$user_id")) {
		value_has_been_resolved = FALSE;
		return (user_id);
#endif
#endif
	} else {
		value_has_been_resolved = FALSE;
		return (string);
	}
}

const char *arg_text_of(const char *string) {
	struct string_type *resolved_string;
	struct string_type *resolved_cstring;
	char               *macro_text;

	/* CHECK IF THE SUPPLIED STRING IS THE NAME OF A STRING CONSTANT,
	 * IF NOT, RETURN THE STRING LITERAL */
	if ((macro_text = macro_resolve(string)) != nullptr) {
		value_has_been_resolved = FALSE;
		return (macro_text);
	} else if ((resolved_string = string_resolve(string)) != nullptr) {
		return (resolved_string->value);
	} else if ((resolved_cstring = cstring_resolve(string)) != nullptr) {
		value_has_been_resolved = FALSE;
		return (resolved_cstring->value);
	} else {
		value_has_been_resolved = FALSE;
		return (string);
	}
}

int validate(const char *string) {
	int             index,
	                count;

	if (string == nullptr) {
		return (FALSE);
	}

	/* CHECK IF THE SUPPLIED STRING IS A VALID INTEGER */
	count = strlen(string);

	/* LOOP OVER THE WHOLE STRING MAKING SURE THAT EACH CHARACTER IS EITHER
	 * A DIGIT OR A MINUS SIGN */
	for (index = 0; index < count; index++) {
		if (!Common::isDigit((int) * (string + index)) && string[index] != '-') {
			//printf ("'%c' is not a digit\n", *(string + index));
			return (FALSE);
		}
	}

	return (TRUE);
}

long value_of(const char *value, int run_time) {
	long            compare;

	value_resolved = TRUE;

	value = arg_text_of(value);

	/* RETURN THE INTEGER VALUE OF A STRING */
	struct integer_type *resolved_integer;
	struct cinteger_type *resolved_cinteger;

	if (!strcmp(value, "**held")) {
		return (FALSE);
	} else if (!strcmp(value, "**here")) {
		return (FALSE);
	} else if (!strcmp(value, "**anywhere")) {
		return (FALSE);
	} else if (!strcmp(value, "**present")) {
		return (FALSE);
	} else if (!strcmp(value, "*held")) {
		return (FALSE);
	} else if (!strcmp(value, "*here")) {
		return (FALSE);
	} else if (!strcmp(value, "*anywhere")) {
		return (FALSE);
	} else if (!strcmp(value, "*present")) {
		return (FALSE);
	} else if (!strcmp(value, "random")) {
		return random_number();
#ifdef GLK
	} else if (!strcmp(value, "status_height")) {
		g_vm->glk_window_get_size(statuswin, &status_width, &status_height);
		return status_height;
	} else if (!strcmp(value, "status_width")) {
		g_vm->glk_window_get_size(statuswin, &status_width, &status_height);
		return status_width;
#else
#ifdef __NDS__
	} else if (!strcmp(value, "status_height")) {
		return screen_depth;
	} else if (!strcmp(value, "status_width")) {
		return screen_width;
#else
	} else if (!strcmp(value, "status_height")) {
		value_resolved = FALSE;
		return -1;
	} else if (!strcmp(value, "status_width")) {
		value_resolved = FALSE;
		return -1;
#endif
#endif
	} else if (!strcmp(value, "unixtime")) {
		return g_system->getMillis() / 1000;
	} else if (validate(value)) {
		return (atoi(value));
	} else if ((resolved_cinteger = cinteger_resolve(value)) != nullptr) {
		return (resolved_cinteger->value);
	} else if ((resolved_integer = integer_resolve(value)) != nullptr) {
		return (resolved_integer->value);
	} else if (function_resolve(value) != nullptr) {
		return (execute(value));
	} else if (object_element_resolve(value)) {
		return (oec);
	} else if ((compare = attribute_resolve(value))) {
		resolved_attribute = SYSTEM_ATTRIBUTE;
		return (compare);
	} else if ((compare = user_attribute_resolve(value))) {
		resolved_attribute = USER_ATTRIBUTE;
		return (compare);
	} else if ((compare = object_resolve(value)) != -1) {
		return (compare);
	} else if (*value == '@') {
		return (count_resolve(value));
	} else {
		if (run_time) {
			unkvarrun(value);
		}
		value_resolved = FALSE;
		return (-1);
	}
}

struct integer_type *integer_resolve(const char *name) {
	int             index,
	                iterator,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, name, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '[') {
			/* THIS MAY STILL BE AN OBJECT ELEMENT IF A CLOSING ] */
			/* IS FOUND BEFORE AN OPENING ( */
			expression[index] = 0;
			delimiter = index + 1;
			/* LOOK FOR THE CLOSING ], BUT IF YOU FIND A ( FIRST */
			/* THEN THIS EXPRESSION IS NOT AN ARRAY */
			for (iterator = counter; iterator > 0; iterator--) {
				if (expression[iterator] == ']') {
					expression[iterator] = 0;
					break;
				} else if (expression[iterator] == '(') {
					/* NOT A VARIABLE ARRAY */
					return (FALSE);
				}
			}
			break;
		} else if (expression[index] == '<') {
			/* HIT A < BEFORE A [ THEREFORE */
			/* IS A FUNCTION CALL, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == '(') {
			/* HIT A ( BEFORE A [ THEREFORE */
			/* IS AN OBJECT ELEMENT, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == ' ')
			return (nullptr);
	}

	// NO DELIMITER FOUND, TRY AS UNINDEXED VARIABLE
	if (delimiter == 0) {
		return (integer_resolve_indexed(name, 0));
	}

	// NO STRING BEFORE DELIMITER
	if (delimiter == 1) {
		return (nullptr);
	}

	counter = value_of(&expression[delimiter], TRUE);

	if (counter > -1) {
		return (integer_resolve_indexed(expression, counter));
	} else {
		/* INDEX OUT OF RANGE */
		return (nullptr);
	}
}

struct integer_type *integer_resolve_indexed(const char *name, int index) {
	struct integer_type *pointer = integer_table;

	if (pointer == nullptr)
		return (nullptr);

	do {
		if (!strcmp(name, pointer->name)) {
			if (index == 0) {
				return (pointer);
			} else {
				/* THIS VARIABLE DOES MATCH, BUT WERE NOT AT THE
				 * RIGHT INDEX YET SO MOVE ON */
				pointer = pointer->next_integer;
				index--;
			}
		} else
			pointer = pointer->next_integer;
	} while (pointer != nullptr);

	/* IF index != 0, INDEX OUT OF RANGE, OTHERWISE NOT VARIABLE */
	return (nullptr);
}

struct cinteger_type *cinteger_resolve(const char *name) {
	int             index,
	                iterator,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, name, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '[') {
			/* THIS MAY STILL BE AN OBJECT ELEMENT IF A CLOSING ] */
			/* IS FOUND BEFORE AN OPENING ( */
			expression[index] = 0;
			delimiter = index + 1;
			/* LOOK FOR THE CLOSING ], BUT IF YOU FIND A ( FIRST */
			/* THEN THIS EXPRESSION IS NOT AN ARRAY */
			for (iterator = counter; iterator > 0; iterator--) {
				if (expression[iterator] == ']') {
					expression[iterator] = 0;
					break;
				} else if (expression[iterator] == '(') {
					/* NOT A CONSTANT ARRAY */
					return (FALSE);
				}
			}
			break;
		} else if (expression[index] == '<') {
			/* HIT A < BEFORE A [ THEREFORE */
			/* IS A FUNCTION CALL, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == '(') {
			/* HIT A ( BEFORE A [ THEREFORE */
			/* IS AN OBJECT ELEMENT, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == ' ')
			return (nullptr);
	}

	// NO DELIMITER FOUND, TRY AS UNINDEXED CONSTANT
	if (delimiter == 0) {
		return (cinteger_resolve_indexed(name, 0));
	}

	// NO STRING BEFORE DELIMITER
	if (delimiter == 1) {
		return (nullptr);
	}

	counter = value_of(&expression[delimiter], TRUE);

	if (counter > -1) {
		return (cinteger_resolve_indexed(expression, counter));
	} else {
		/* INDEX OUT OF RANGE */
		return (nullptr);
	}
}

struct cinteger_type *cinteger_resolve_indexed(const char *name, int index) {
	struct cinteger_type *pointer = cinteger_table;

	if (pointer == nullptr)
		return (nullptr);

	do {
		if (!strcmp(name, pointer->name)) {
			if (index == 0) {
				return (pointer);
			} else {
				/* THIS VARIABLE DOES MATCH, BUT WERE NOT AT THE
				 * RIGHT INDEX YET SO MOVE ON */
				pointer = pointer->next_cinteger;
				index--;
			}
		} else
			pointer = pointer->next_cinteger;
	} while (pointer != nullptr);

	/* IF index != 0, INDEX OUT OF RANGE, OTHERWISE NOT VARIABLE */
	return (nullptr);
}

struct string_type *string_resolve(const char *name) {
	int             index,
	                iterator,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, name, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '[') {
			expression[index] = 0;
			delimiter = index + 1;
			for (iterator = counter; iterator > 0; iterator--) {
				if (expression[iterator] == ']') {
					expression[iterator] = 0;
					break;
				}
			}
			break;
		} else if (expression[index] == '<') {
			/* HIT A < BEFORE A [ THEREFORE */
			/* IS A FUNCTION CALL, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == '(') {
			/* HIT A ( BEFORE A [ THEREFORE */
			/* IS AN OBJECT ELEMENT, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == ' ')
			return (nullptr);
	}

	if (delimiter == 0) {
		/* NO DELIMITER FOUND, TRY AS UNINDEXED VARIABLE */
		return (string_resolve_indexed(name, 0));
	}

	if (delimiter == 1) {
		/* NO STRING BEFORE DELIMITER */
		return (nullptr);
	}

	counter = value_of(&expression[delimiter], TRUE);

	if (counter > -1) {
		return (string_resolve_indexed(expression, counter));
	} else
		return (nullptr);
}

struct string_type *string_resolve_indexed(const char *name, int index) {
	struct string_type *pointer = string_table;

	if (pointer == nullptr)
		return (nullptr);

	do {
		if (!strcmp(name, pointer->name)) {
			if (index == 0) {
				return (pointer);
			} else {
				/* THIS STRING DOES MATCH, BUT WERE NOT AT THE
				 * RIGHT INDEX YET SO MOVE ON */
				pointer = pointer->next_string;
				index--;
			}
		} else {
			pointer = pointer->next_string;
		}
	} while (pointer != nullptr);

	return (nullptr);
}

struct string_type *cstring_resolve(const char *name) {
	int             index,
	                iterator,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, name, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '[') {
			expression[index] = 0;
			delimiter = index + 1;
			for (iterator = counter; iterator > 0; iterator--) {
				if (expression[iterator] == ']') {
					expression[iterator] = 0;
					break;
				}
			}
			break;
		} else if (expression[index] == '<') {
			/* HIT A < BEFORE A [ THEREFORE */
			/* IS A FUNCTION CALL, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == '(') {
			/* HIT A ( BEFORE A [ THEREFORE */
			/* IS AN OBJECT ELEMENT, NOT AN ARRAY */
			return (nullptr);
		} else if (expression[index] == ' ')
			return (nullptr);
	}

	if (delimiter == 0) {
		/* NO DELIMITER FOUND, TRY AS UNINDEXED VARIABLE */
		return (cstring_resolve_indexed(name, 0));
	}

	if (delimiter == 1) {
		/* NO STRING BEFORE DELIMITER */
		return (nullptr);
	}

	counter = value_of(&expression[delimiter], TRUE);

	if (counter > -1) {
		return (cstring_resolve_indexed(expression, counter));
	} else
		return (nullptr);
}

struct string_type *cstring_resolve_indexed(const char *name, int index) {
	struct string_type *pointer = cstring_table;

	if (pointer == nullptr)
		return (nullptr);

	do {
		if (!strcmp(name, pointer->name)) {
			if (index == 0) {
				return (pointer);
			} else {
				/* THIS STRING DOES MATCH, BUT WERE NOT AT THE
				 * RIGHT INDEX YET SO MOVE ON */
				pointer = pointer->next_string;
				index--;
			}
		} else {
			pointer = pointer->next_string;
		}
	} while (pointer != nullptr);

	return (nullptr);
}

struct function_type *function_resolve(const char *name) {
	const char      *full_name;
	char            core_name[84];
	int             index;

	struct function_type *pointer = function_table;

	if (function_table == nullptr)
		return (nullptr);

	/* STRIP ARGUMENTS OFF FIRST, THEN EXPAND RESOLVE NAME */
	index = 0;

	while (*name && index < 80) {
		if (*name == '<') {
			break;
		} else {
			core_name[index++] = *name++;
		}
	}
	core_name[index] = 0;

	/* GET A POINTER TO A STRING THAT REPRESENTS THE EXPANDED NAME OF THE FUNCTION */
	full_name = (const char *)expand_function(core_name);

	/* LOOP THROUGH ALL THE FUNCTIONS LOOKING FOR A FUNCTION THAT
	 * HAS THIS EXPANDED FULL NAME */
	do {
		if (!strcmp(full_name, pointer->name))
			return (pointer);
		else
			pointer = pointer->next_function;
	} while (pointer != nullptr);

	/* RETURN A POINTER TO THE STRUCTURE THAT ENCAPSULATES THE FUNCTION */
	return (nullptr);
}

const char *expand_function(const char *name) {
	/* THIS FUNCTION TAKES A SCOPE FUNCTION CALL SUCH AS noun1.function
	 * AND REOLVE THE ACTUAL FUNCTION NAME SUCH AS function_key */
	int             index,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, name, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '.') {
			expression[index] = 0;
			delimiter = index + 1;
			break;
		}
	}

	if (delimiter == FALSE) {
		/* THIS FUNCTION DOESN'T CONTAIN A '.', SO RETURN IT AS IS */
		return (arg_text_of(name));
	}

	/* THE ORIGINAL STRING IS NOW CUT INTO TWO STRINGS:
	 * expression.delimiter */

	index = value_of(expression, TRUE);

	if (index < 1 || index > objects) {
		return ((const char *) name);
	}

	if (cinteger_resolve(&expression[delimiter]) != nullptr ||
	        integer_resolve(&expression[delimiter]) != nullptr ||
	        object_element_resolve(&expression[delimiter])) {
		/* THE DELIMETER RESOLVES TO A CONSTANT, VARIABLE OR OBJECT
		 * ELEMENT, SO TAKE NOTE OF THAT */
		Common::sprintf_s(function_name, 81, "%ld", value_of(&expression[delimiter], TRUE));
	} else {
		Common::strcpy_s(function_name, 81, &expression[delimiter]);
	}
	Common::strcat_s(function_name, 81, "_");
	Common::strcat_s(function_name, 81, object[index]->label);

	return ((const char *) function_name);
}

char *macro_resolve(const char *testString) {
	int             index,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	strncpy(expression, testString, 80);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '{' || expression[index] == '}') {
			expression[index] = 0;
			if (!delimiter)
				delimiter = index + 1;
		}
	}

	if (delimiter == FALSE)
		return (nullptr);

	if (*expression != 0) {
		index = value_of(expression, TRUE);
	} else {
		index = 0;
	}

	if (!strcmp(&expression[delimiter], "list")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (list_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "plain")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (plain_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "long")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (long_output(index));
		}
	} else if (!strcmp(&expression[delimiter], "sub")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (sub_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "obj")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (obj_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "that")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (that_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "it")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (it_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "doesnt")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (doesnt_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "does")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (does_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "isnt")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (isnt_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "is")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (is_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "the")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (sentence_output(index, FALSE));
		}
	} else if (!strcmp(&expression[delimiter], "s")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			if (object[index]->attributes & PLURAL) {
				temp_buffer[0] = '\0';
			} else {
				temp_buffer[0] = 's';
				temp_buffer[1] = '\0';
			}
			return (temp_buffer);
		}
	} else if (!strcmp(&expression[delimiter], "names")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (object_names(index, temp_buffer));
		}
	} else if (!strcmp(&expression[delimiter], "label")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (object[index]->label);
		}
	} else if (!strcmp(&expression[delimiter], "List")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (list_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Plain")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (plain_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Sub")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (sub_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Obj")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (obj_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "That")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (that_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "It")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (it_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Doesnt")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (doesnt_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Does")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (does_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Isnt")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (isnt_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "Is")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (is_output(index, TRUE));
		}
	} else if (!strcmp(&expression[delimiter], "The")) {
		if (index < 1 || index > objects) {
			badptrrun(expression, index);
			return (nullptr);
		} else {
			return (sentence_output(index, TRUE));
		}
	} else {
		Common::strcpy_s(macro_function, "+macro_");
		Common::strcat_s(macro_function, &expression[delimiter]);
		Common::strcat_s(macro_function, "<");
		Common::sprintf_s(temp_buffer, 1024, "%d", index);
		Common::strcat_s(macro_function, temp_buffer);

		// BUILD THE FUNCTION NAME AND PASS THE OBJECT AS
		// THE ONLY ARGUMENT
		if (execute(macro_function)) {
			return (string_resolve("return_value")->value);
		}
	}

	return (nullptr);
}

int count_resolve(const char *testString) {
	struct function_type    *resolved_function = nullptr;

	if (*(testString + 1) == 0) {
		// @ ON ITS OWN, SO RETURN THE CALL COUNT OF THE CURRENTLY EXECUTING
		// FUNCTION
		return (executing_function->call_count);
	} else if ((resolved_function = function_resolve(testString + 1)) != nullptr) {
		return (resolved_function->call_count);
	} else {
		return array_length_resolve(testString);
	}
}

int array_length_resolve(const char *testString) {
	int             counter = 0;
	const char      *array_name = &testString[1];

	struct integer_type *integer_pointer = integer_table;
	struct cinteger_type *cinteger_pointer = cinteger_table;
	struct string_type *string_pointer = string_table;
	struct string_type *cstring_pointer = cstring_table;

	if (integer_pointer != nullptr) {
		do {
			if (!strcmp(array_name, integer_pointer->name)) {
				counter++;
			}
			integer_pointer = integer_pointer->next_integer;
		} while (integer_pointer != nullptr);
	}

	/* IF ONE OR MORE INTEGERS WITH THIS NAME WERE FOUND
	   RETURN THE COUNT */
	if (counter)
		return (counter);

	if (string_pointer != nullptr) {
		do {
			if (!strcmp(array_name, string_pointer->name)) {
				counter++;
			}
			string_pointer = string_pointer->next_string;
		} while (string_pointer != nullptr);
	}

	/* IF ONE OR MORE STRINGS WITH THIS NAME WERE FOUND
	   RETURN THE COUNT */
	if (counter)
		return (counter);

	if (cinteger_pointer != nullptr) {
		do {
			if (!strcmp(array_name, cinteger_pointer->name)) {
				counter++;
			}
			cinteger_pointer = cinteger_pointer->next_cinteger;
		} while (cinteger_pointer != nullptr);
	}

	/* IF ONE OR MORE INTEGER CONSTANTS WITH THIS NAME WERE FOUND
	   RETURN THE COUNT */
	if (counter)
		return (counter);

	if (cstring_pointer != nullptr) {
		do {
			if (!strcmp(array_name, cstring_pointer->name)) {
				counter++;
			}
			cstring_pointer = cstring_pointer->next_string;
		} while (cstring_pointer != nullptr);
	}

	/* IF ONE OR MORE STRING CONSTANTS WITH THIS NAME WERE FOUND
	   RETURN THE COUNT */
	if (counter)
		return (counter);

	/* NO VARIABLES OR STRINGS FOUND */
	return (0);
}

int object_element_resolve(const char *testString) {
	int             index,
	                iterator,
	                counter;
	int             delimiter = 0;
	char            expression[84];

	struct integer_type *resolved_integer;
	struct cinteger_type *resolved_cinteger;

	strncpy(expression, testString, 80);

	//sprintf(temp_buffer, "incoming = %s^", testString);
	//write_text (temp_buffer);

	counter = strlen(expression);

	for (index = 0; index < counter; index++) {
		if (expression[index] == '(') {
			expression[index] = 0;
			delimiter = index + 1;
			for (iterator = counter; iterator > 0; iterator--) {
				if (expression[iterator] == ')') {
					expression[iterator] = 0;
					break;
				}
			}
			break;
		} else if (expression[index] == '<') {
			/* HIT A < BEFORE A [ THEREFORE */
			/* IS A FUNCTION CALL, NOT AN ARRAY */
			return (FALSE);
		} else if (expression[index] == '[') {
			/* HIT A [ BEFORE A ( THEREFORE */
			/* THIS EXPRESSION IS AN ARRAY, NOT AN OBJECT ELEMENT */
			/* UNLESS A CLOSING ] IS FOUND BEFORE THE OPENING ( */
			/* ie. COULD BE AN array[index](element) FORMAT */
			/* SEARCH FORWARD... */
			for (; index < counter; index++) {
				if (expression[index] == ']') {
					/* BREAK OUT AND KEEP LOOKING FOR A ( */
					break;
				} else if (expression[index] == '(') {
					/* THIS EXPRESSION IS DEFINITELY AN ARRAY WITH AN */
					/* OBJECT ELEMENT AS THE INDEX */
					return (FALSE);
				}
			}
		} else if (expression[index] == ' ')
			return (FALSE);
	}

	// NO DELIMITER FOUND OR NO STRING BEFORE DELIMITER
	if (delimiter == FALSE || delimiter == 1)
		return (FALSE);

	index = object_resolve(expression);

	if (index == -1) {
		//sprintf(temp_buffer, "expression %s is not an object^", expression);
		//write_text(temp_buffer);

		// COULDN'T BE RESOLVED AS AN OBJECT, TRY AS A VARIABLE
		if ((resolved_integer = integer_resolve(expression)) != nullptr) {
			index = resolved_integer->value;
		} else if ((resolved_cinteger = cinteger_resolve(expression)) != nullptr) {
			index = resolved_cinteger->value;
		}
	}

	if (index < 1 || index > objects) {
		badptrrun(expression, index);
		return (FALSE);
	}

	counter = value_of(&expression[delimiter], TRUE);

	if (counter < 0 || counter > 15) {
		Common::sprintf_s(error_buffer, 1024,
		        "ERROR: In function \"%s\", element \"%s\" out of range (%d).^",
		        executing_function->name, &expression[delimiter], counter);
		write_text(error_buffer);
		return (FALSE);
	} else {
		oec = object[index]->integer[counter];
		object_element_address = &object[index]->integer[counter];
		return (TRUE);
	}
}

int object_resolve(const char *object_string) {
	int             index;

	if (!strcmp(object_string, "noun1"))
		return (noun[0]);
	else if (!strcmp(object_string, "noun2"))
		return (noun[1]);
	else if (!strcmp(object_string, "noun3"))
		return (noun[2]);
	else if (!strcmp(object_string, "noun4"))
		return (noun[3]);
	else if (!strcmp(object_string, "player"))
		return (player);
	else if (!strcmp(object_string, "here"))
		return (HERE);
	else if (!strcmp(object_string, "self") ||
	         !strcmp(object_string, "this")) {
		if (executing_function != nullptr && executing_function->self == 0) {
			Common::sprintf_s(error_buffer, 1024,
			        "ERROR: Reference to 'self' from global function \"%s\".^",
			        executing_function->name);
			write_text(error_buffer);
		} else
			return (executing_function->self);
	} else {
		for (index = 1; index <= objects; index++) {
			if (!strcmp(object_string, object[index]->label))
				return (index);
		}
	}

	return (-1);
}

long attribute_resolve(const char *attribute) {
	long            bit_mask;

	if (!strcmp(attribute, "VISITED"))
		return (VISITED);
	else if (!strcmp(attribute, "DARK"))
		return (DARK);
	else if (!strcmp(attribute, "ON_WATER"))
		return (ON_WATER);
	else if (!strcmp(attribute, "UNDER_WATER"))
		return (UNDER_WATER);
	else if (!strcmp(attribute, "WITHOUT_AIR"))
		return (WITHOUT_AIR);
	else if (!strcmp(attribute, "OUTDOORS"))
		return (OUTDOORS);
	else if (!strcmp(attribute, "MID_AIR"))
		return (MID_AIR);
	else if (!strcmp(attribute, "TIGHT_ROPE"))
		return (TIGHT_ROPE);
	else if (!strcmp(attribute, "POLLUTED"))
		return (POLLUTED);
	else if (!strcmp(attribute, "SOLVED"))
		return (SOLVED);
	else if (!strcmp(attribute, "MID_WATER"))
		return (MID_WATER);
	else if (!strcmp(attribute, "DARKNESS")) {
		bit_mask = DARKNESS;
		if (check_light(HERE)) {
			bit_mask = ~bit_mask;
			object[HERE]->attributes = object[HERE]->attributes & bit_mask;
		} else {
			object[HERE]->attributes = object[HERE]->attributes | bit_mask;
		}
		return (DARKNESS);
	} else if (!strcmp(attribute, "MAPPED"))
		return (MAPPED);
	else if (!strcmp(attribute, "KNOWN"))
		return (KNOWN);
	else if (!strcmp(attribute, "CLOSED"))
		return (CLOSED);
	else if (!strcmp(attribute, "LOCKED"))
		return (LOCKED);
	else if (!strcmp(attribute, "DEAD"))
		return (DEAD);
	else if (!strcmp(attribute, "IGNITABLE"))
		return (IGNITABLE);
	else if (!strcmp(attribute, "WORN"))
		return (WORN);
	else if (!strcmp(attribute, "CONCEALING"))
		return (CONCEALING);
	else if (!strcmp(attribute, "LUMINOUS"))
		return (LUMINOUS);
	else if (!strcmp(attribute, "WEARABLE"))
		return (WEARABLE);
	else if (!strcmp(attribute, "CLOSABLE"))
		return (CLOSABLE);
	else if (!strcmp(attribute, "LOCKABLE"))
		return (LOCKABLE);
	else if (!strcmp(attribute, "ANIMATE"))
		return (ANIMATE);
	else if (!strcmp(attribute, "LIQUID"))
		return (LIQUID);
	else if (!strcmp(attribute, "CONTAINER"))
		return (CONTAINER);
	else if (!strcmp(attribute, "SURFACE"))
		return (SURFACE);
	else if (!strcmp(attribute, "PLURAL"))
		return (PLURAL);
	else if (!strcmp(attribute, "FLAMMABLE"))
		return (FLAMMABLE);
	else if (!strcmp(attribute, "BURNING"))
		return (BURNING);
	else if (!strcmp(attribute, "LOCATION"))
		return (LOCATION);
	else if (!strcmp(attribute, "ON"))
		return (ON);
	else if (!strcmp(attribute, "DAMAGED"))
		return (DAMAGED);
	else if (!strcmp(attribute, "FEMALE"))
		return (FEMALE);
	else if (!strcmp(attribute, "POSSESSIVE"))
		return (POSSESSIVE);
	else if (!strcmp(attribute, "OUT_OF_REACH"))
		return (OUT_OF_REACH);
	else if (!strcmp(attribute, "TOUCHED"))
		return (TOUCHED);
	else if (!strcmp(attribute, "SCORED"))
		return (SCORED);
	else if (!strcmp(attribute, "SITTING"))
		return (SITTING);
	else if (!strcmp(attribute, "NPC"))
		return (NPC);
	else if (!strcmp(attribute, "DONE"))
		return (DONE);
	else if (!strcmp(attribute, "GAS"))
		return (MAPPED);
	else if (!strcmp(attribute, "NO_TAB"))
		return (NO_TAB);
	else if (!strcmp(attribute, "NOT_IMPORTANT"))
		return (NOT_IMPORTANT);
	else
		return (0);
}

long user_attribute_resolve(const char *name) {
	struct attribute_type *pointer = attribute_table;

	if (pointer == nullptr)
		return (0);

	do {
		if (!strcmp(name, pointer->name)) {
			return (pointer->value);
		} else
			pointer = pointer->next_attribute;
	} while (pointer != nullptr);

	/* ATTRIBUTE NOT FOUND */
	return (0);
}

} // End of namespace JACL
} // End of namespace Glk
