/* ScummVM - Scumm Interpreter
 * Copyright (C) 2004 The ScummVM project
 *
 * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

// Expression parsing module, and string handling functions

// EXPR_ParseArgs() lifted wholesale from SDL win32 initialization code by Sam Lantinga

#include "saga/saga.h"
#include "saga/cvar_mod.h"
#include "saga/expr.h"

namespace Saga {

static const char *EXPR_ErrMsg[] = {
	"Invalid error state.",
	"No Error",
	"Memory allocation failed",
	"Illegal variable name",
	"Expected \'=\' or \'(\' in expression",
	"Expected \'(\' in function call",
	"Illegal \'(\', identifier is not function",
	"Expected a value to assign",
	"Unterminated string literal",
	"Unmatched parenthesis in function call",
	"Error reading value string",
	"Expected a number or boolean",
	"Unknown variable or function"
};

enum EXPR_Errors {
	EXERR_ASSERT,
	EXERR_NONE,
	EXERR_MEM,
	EXERR_ILLEGAL,
	EXERR_EXPR,
	EXERR_FUNC,
	EXERR_NOTFUNC,
	EXERR_RVALUE,
	EXERR_LITERAL,
	EXERR_PAREN,
	EXERR_STRING,
	EXERR_NUMBER,
	EXERR_NOTFOUND
};

static enum EXPR_Errors EXPR_ErrorState;

// Returns the appropriate expression parser error string given an error code.
int EXPR_GetError(const char **err_str) {
	*err_str = EXPR_ErrMsg[EXPR_ErrorState];
	return EXPR_ErrorState;
}

// Parses an interactive expression.
// Sets 'expr_cvar' to the cvar/cfunction identifier input by the user, and
// 'rvalue' to the corresponding rvalue ( in an expression ) or argument string
// ( in a function call ).
//
// Memory pointed to by rvalue after return must be explicitly freed by the
// caller.
int EXPR_Parse(const char **exp_pp, int *len, R_CVAR_P *expr_cvar, char **rvalue) {
	int i;
	int in_char;
	int equ_offset = 0;
	int rvalue_offset;

	char *lvalue_str;
	int lvalue_len;
	char *rvalue_str;
	int rvalue_len;

	const char *scan_p;
	int scan_len;
	const char *expr_p;
	int expr_len;
	int test_char = '\0';
	int have_func = 0;

	R_CVAR_P lvalue_cvar;

	expr_p = *exp_pp;
	expr_len = strlen(*exp_pp);
	scan_p = *exp_pp;
	scan_len = expr_len;

	//*lvalue = NULL;
	*rvalue = NULL;

	EXPR_ErrorState = EXERR_ASSERT;

	for (i = 0; i <= scan_len; i++, scan_p++) {
		in_char = *scan_p;
		if ((i == 0) && isdigit(in_char)) {
			// First character of a valid identifier cannot be a digit
			EXPR_ErrorState = EXERR_ILLEGAL;
			return R_FAILURE;
		}

		// If we reach a character that isn't valid in an identifier...
		if ((!isalnum(in_char)) && ((in_char != '_'))) {

			// then eat remaining whitespace, if any
			equ_offset = strspn(scan_p, R_EXPR_WHITESPACE);
			test_char = scan_p[equ_offset];
			// and test for the only valid characters after an identifier
			if ((test_char != '=') && (test_char != '\0') && (test_char != '(')) {
				if ((equ_offset == 0) && ((scan_p - expr_p) != expr_len)) {
					EXPR_ErrorState = EXERR_ILLEGAL;
				} else {
					EXPR_ErrorState = EXERR_EXPR;
				}
				return R_FAILURE;
			}
			break;
		}
	}

	lvalue_len = (scan_p - expr_p);
	lvalue_str = (char *)malloc(lvalue_len + 1);

	if (lvalue_str == NULL) {
		EXPR_ErrorState = EXERR_MEM;
		return R_FAILURE;
	}

	strncpy(lvalue_str, expr_p, lvalue_len);
	lvalue_str[lvalue_len] = 0;

	// We now have the lvalue, so attempt to find it
	lvalue_cvar = CVAR_Find(lvalue_str);
	if (lvalue_cvar == NULL) {
		EXPR_ErrorState = EXERR_NOTFOUND;
		return R_FAILURE;
	}
	if (lvalue_str) {
		free(lvalue_str);
		lvalue_str = NULL;
	}

	// Skip parsed character, if any
	scan_p += equ_offset + 1;
	scan_len = (scan_p - expr_p);

	// Check if the 'cvar' is really a function
	have_func = CVAR_IsFunc(lvalue_cvar);

	if (test_char == '(') {
		if (have_func) {
			rvalue_str = EXPR_ReadString(&scan_p, &rvalue_len, ')');
			if (rvalue_str != NULL) {
				// Successfully read string
				//CON_Print("Read function parameters \"%s\".", rvalue_str);
				*expr_cvar = lvalue_cvar;
				*rvalue = rvalue_str;

				scan_len = (scan_p - expr_p);

				*exp_pp = scan_p;
				*len -= scan_len;

				EXPR_ErrorState = EXERR_NONE;
				return R_SUCCESS;
			} else {
				EXPR_ErrorState = EXERR_PAREN;
				return R_FAILURE;
			}
		} else {
			EXPR_ErrorState = EXERR_NOTFUNC;
			return R_FAILURE;
		}
	}

	// Eat more whitespace
	rvalue_offset = strspn(scan_p, R_EXPR_WHITESPACE);

	if (rvalue_offset + i == expr_len) {
		// Only found single lvalue
		*expr_cvar = lvalue_cvar;
		*exp_pp = scan_p;
		*len -= scan_len;
		return R_SUCCESS;
	}

	scan_p += rvalue_offset;
	scan_len = (scan_p - expr_p) + 1;

	in_char = *scan_p;
	in_char = toupper(in_char);

	switch (in_char) {
	case '\"':
		scan_p++;
		scan_len--;
		rvalue_str = EXPR_ReadString(&scan_p, &rvalue_len, '\"');
		if (rvalue_str != NULL) {
			// Successfully read string
			break;
		} else {
			EXPR_ErrorState = EXERR_LITERAL;
			return R_FAILURE;
		}
		break;

#if 0
	case 'Y': // Y[es]
	case 'T': // T[rue]
		break;
	case 'N': // N[o]
	case 'F': // F[alse]
		break;
#endif
	default:

		if (isdigit(in_char) || (in_char == '-') || (in_char == '+')) {
			rvalue_str = EXPR_ReadString(&scan_p, &rvalue_len, 0);
			if (rvalue_str != NULL) {
				// Successfully read string
				break;
			} else {
				EXPR_ErrorState = EXERR_STRING;
				return R_FAILURE;
			}
		} else {
			EXPR_ErrorState = EXERR_NUMBER;
			return R_FAILURE;
		}
		break;
	}

	*expr_cvar = lvalue_cvar;
	*rvalue = rvalue_str;

	scan_len = (scan_p - expr_p);

	*exp_pp = scan_p;
	*len -= scan_len;

	EXPR_ErrorState = EXERR_NONE;
	return R_SUCCESS;

}

// Reads in a string of characters from '*string_p' until 'term_char' is 
// encountered. If 'term_char' == 0, the function reads characters until
// whitespace is encountered. 
// Upon reading a string, the function modifies *string_p and len based on 
// the number of characters read.
char *EXPR_ReadString(const char **string_p, int *len, int term_char) {
	int string_len;
	char *str_p = NULL;
	char *term_p;
	const char *scan_p;
	int in_char;

	if (term_char > 0) {
		term_p = (char *)strchr(*string_p, term_char);
		if (term_p == NULL) {
			return NULL;
		}

		string_len = (int)(term_p - *string_p);
		str_p = (char *)malloc(string_len + 1);

		if (str_p == NULL) {
			return NULL;
		}

		strncpy(str_p, *string_p, string_len);
		str_p[string_len] = 0;

		*string_p += (string_len + 1);	/* Add 1 for terminating char */
		*len -= (string_len + 1);
	} else {
		scan_p = *string_p;
		string_len = 0;

		while (scan_p) {
			in_char = *scan_p++;
			if (!isspace(in_char)) {
				string_len++;
			} else if (string_len) {
				str_p = (char *)malloc(string_len + 1);
				if (str_p == NULL) {
					return NULL;
				}

				strncpy(str_p, *string_p, string_len);
				str_p[string_len] = 0;
				*string_p += string_len;
				*len -= string_len;
				break;
			} else {
				return NULL;
			}
		}

	}

	return str_p;
}

// Parses the string 'cmd_str' into argc/argv format, returning argc.
// The resulting argv pointers point into the 'cmd_str' string, so any argv
// entries should not be used after cmd_str is deallocated.
//
// Memory pointed to by expr_argv must be explicitly freed by the caller.
int EXPR_GetArgs(char *cmd_str, char ***expr_argv) {
	int expr_argc;

	expr_argc = EXPR_ParseArgs(cmd_str, NULL);
	*expr_argv = (char **)malloc((expr_argc + 1) * sizeof(**expr_argv));

	if (expr_argv == NULL) {
		return R_FAILURE;
	}

	EXPR_ParseArgs(cmd_str, *expr_argv);

	return expr_argc;
}

int EXPR_ParseArgs(char *cmd_str, char **argv) {
	char *bufp;
	int argc;

	argc = 0;
	for (bufp = cmd_str; *bufp;) {
		// Skip leading whitespace
		while (isspace(*bufp)) {
			++bufp;
		}
		// Skip over argument
		if (*bufp == '"') {
			++bufp;
			if (*bufp) {
				if (argv) {
					argv[argc] = bufp;
				}
				++argc;
			}
			// Skip over word
			while (*bufp && (*bufp != '"')) {
				++bufp;
			}
		} else {
			if (*bufp) {
				if (argv) {
					argv[argc] = bufp;
				}
				++argc;
			}
			// Skip over word
			while (*bufp && !isspace(*bufp)) {
				++bufp;
			}
		}
		if (*bufp) {
			if (argv) {
				*bufp = '\0';
			}
			++bufp;
		}
	}
	if (argv) {
		argv[argc] = NULL;
	}
	return (argc);
}

} // End of namespace Saga