/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2019  Warzone 2100 Project

	Warzone 2100 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.

	Warzone 2100 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 Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
%{
/*
 * ScriptVals.y
 *
 * yacc grammar for loading script variable values
 *
 */
#include "lib/framework/frame.h"
#include "lib/framework/frameresource.h"
#include "lib/framework/strres.h"

#include "lib/gamelib/gtime.h"
#include "lib/script/script.h"
#include "lib/sound/audio.h"

#include "src/scriptvals.h"
#include "lib/framework/lexer_input.h"

#if defined (WZ_CC_MSVC)
#include "scriptvals_parser.hpp"
#else
#include "scriptvals_parser.h"
#endif

#include "src/scripttabs.h"
#include "src/objects.h"
#include "src/droid.h"
#include "src/structure.h"
#include "src/message.h"
#include "src/levels.h"
#include "src/research.h"
#include "src/text.h"
#include "src/template.h"

// The current script code
static SCRIPT_CODE		*psCurrScript;

extern int scrv_lex(void);
extern void scrv_set_extra(YY_EXTRA_TYPE user_defined);
extern int scrv_lex_destroy(void);
extern int scrv_get_lineno(void);
extern char* scrv_get_text(void);

// The current script context
static SCRIPT_CONTEXT	*psCurrContext;

// the current array indexes
static ARRAY_INDEXES	sCurrArrayIndexes;

// check that an array index is valid
static bool scrvCheckArrayIndex(SDWORD base, ARRAY_INDEXES *psIndexes, UDWORD *pIndex)
{
	SDWORD	i, size;

	if (!psCurrScript || psCurrScript->psDebug == NULL)
	{
		return false;
	}

	if (base < 0 || base >= psCurrScript->numArrays)
	{
		yyerror("Array index out of range");
		return false;
	}

	if (psIndexes->dimensions != psCurrScript->psArrayInfo[base].dimensions)
	{
		yyerror("Invalid number of dimensions for array initialiser");
		return false;
	}

	for(i=0; i<psCurrScript->psArrayInfo[base].dimensions; i++)
	{
		if ((psIndexes->elements[i] < 0) ||
			(psIndexes->elements[i] >= psCurrScript->psArrayInfo[base].elements[i]))
		{
			yyerror("Invalid index for dimension %d", i);
			return false;
		}
	}

	*pIndex = 0;
	size = 1;
	for(i = psCurrScript->psArrayInfo[base].dimensions-1; i >= 0; i--)
	{
		*pIndex += psIndexes->elements[i] * size;
		size *= psCurrScript->psArrayInfo[base].elements[i];
	}

	*pIndex += psCurrScript->psArrayInfo[base].base;

	return true;
}

%}

%name-prefix="scrv_"
%defines
%error-verbose

%union {
	bool			bval;
	INTERP_TYPE		tval;
	char			*sval;
	UDWORD			vindex;
	SDWORD			ival;
	VAR_INIT		sInit;
	ARRAY_INDEXES	*arrayIndex;
}

	/* value tokens */
%token <bval> BOOLEAN_T
%token <ival> INTEGER
%token <sval> IDENT
%token <sval> QTEXT			/* Text with double quotes surrounding it */
%token <tval> TYPE
%token <vindex> VAR
%token <vindex> ARRAY

	/* keywords */
%token SCRIPT
%token STORE
%token RUN

	/* rule types */
%type <sval> script_name
%type <sInit> var_value
%type <vindex> var_entry
%type <arrayIndex> array_index
%type <arrayIndex> array_index_list

%%

val_file:		script_entry
			|	val_file script_entry
			;

script_entry:	script_name RUN
				{
					if (!eventNewContext(psCurrScript, CR_RELEASE, &psCurrContext))
					{
						yyerror("Couldn't create context");
						YYABORT;
					}
					if (!scrvAddContext($1, psCurrContext, SCRV_EXEC))
					{
						yyerror("Couldn't store context");
						YYABORT;
					}
				}
							'{' var_init_list '}'
				{
					if (!eventRunContext(psCurrContext, gameTime/SCR_TICKRATE))
					{
						YYABORT;
					}
				}
			|	script_name STORE QTEXT
				{
					if (!eventNewContext(psCurrScript, CR_NORELEASE, &psCurrContext))
					{
						yyerror("Couldn't create context");
						YYABORT;
					}
					if (!scrvAddContext($3, psCurrContext, SCRV_NOEXEC))
					{
						yyerror("Couldn't store context");
						YYABORT;
					}
				}
										 '{' var_init_list '}'
			;

script_name:	SCRIPT QTEXT
				{

					int namelen,extpos;
					char *stringname;

					stringname=$2;

					namelen=strlen( stringname);
					extpos=namelen-3;
					if (strncmp(&stringname[extpos],"blo",3)==0)
					{
						if (resPresent("BLO",stringname)==true)
						{
							psCurrScript = (SCRIPT_CODE*)resGetData("BLO",stringname);
						}
						else
						{
							// change extension to "slo"
							stringname[extpos]='s';
							psCurrScript = (SCRIPT_CODE*)resGetData("SCRIPT",stringname);
						}
					}
					else if (strncmp(&stringname[extpos],"slo",3)==0)
					{
						if (resPresent("SCRIPT",stringname)==true)
						{
							psCurrScript = (SCRIPT_CODE*)resGetData("SCRIPT",stringname);
						}
					}

					if (!psCurrScript)
					{
						yyerror("Script file %s not found", stringname);
						YYABORT;
					}

					$$ = $2;
				}
			;

var_init_list:	/* NULL token */
			|	var_init
			|	var_init_list var_init
			;

var_init:		var_entry TYPE var_value
				{
					INTERP_VAL		data;	/* structure to to hold all types */
					BASE_OBJECT *psObj;
					SDWORD   compIndex;

					/* set type */
					data.type = $2;

					switch ((unsigned)$2)  // Unsigned cast to suppress compiler warnings due to enum abuse.
					{
					case VAL_INT:
						data.v.ival = $3.index;	//index = integer value of the variable, not var index
						if ($3.type != IT_INDEX ||
							!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						break;
					case ST_DROID:
						if ($3.type != IT_INDEX)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						psObj = getBaseObjFromId((UDWORD)$3.index);
						if (!psObj)
						{
							yyerror("Droid id %d not found", (UDWORD)$3.index);
							YYABORT;
						}

						data.v.oval = psObj;	/* store as pointer */

						if (psObj->type != OBJ_DROID)
						{
							yyerror("Object id %d is not a droid", (UDWORD)$3.index);
							YYABORT;
						}
						else
						{
							if(!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;

					case ST_STRUCTURE:
						if ($3.type != IT_INDEX)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						psObj = getBaseObjFromId((UDWORD)$3.index);
						if (!psObj)
						{
							yyerror("Structure id %d not found", (UDWORD)$3.index);
							YYABORT;
						}

						data.v.oval = psObj;

						if (psObj->type != OBJ_STRUCTURE)
						{
							yyerror("Object id %d is not a structure", (UDWORD)$3.index);
							YYABORT;
						}
						else
						{
							if(!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;
					case ST_FEATURE:
						if ($3.type != IT_INDEX)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						psObj = getBaseObjFromId((UDWORD)$3.index);
						if (!psObj)
						{
							yyerror("Feature id %d not found", (UDWORD)$3.index);
							YYABORT;
						}

						data.v.oval = psObj;

						if (psObj->type != OBJ_FEATURE)
						{
							yyerror("Object id %d is not a feature", (UDWORD)$3.index);
							YYABORT;
						}
						else
						{
							if(!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;
					case ST_FEATURESTAT:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}

						data.v.ival = getFeatureStatFromName($3.pString);

						if (data.v.ival == -1)
						{
							yyerror("Feature Stat %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case VAL_BOOL:
						data.v.bval = $3.index;	//index = boolean value, not var index
						if ($3.type != IT_BOOL ||
							!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						break;
					case ST_BODY:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_BODY, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("body component %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_PROPULSION:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_PROPULSION, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Propulsion component %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_ECM:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_ECM, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("ECM component %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_SENSOR:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_SENSOR, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Sensor component %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_CONSTRUCT:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_CONSTRUCT, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Construct component %s not found",	$3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_REPAIR:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_REPAIRUNIT, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Repair component %s not found",	$3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_BRAIN:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_BRAIN, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Brain component %s not found",	$3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_WEAPON:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getCompFromID(COMP_WEAPON, $3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Weapon component %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_TEMPLATE:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.oval = getTemplateFromTranslatedNameNoPlayer($3.pString);	/* store pointer to the template */
						if (data.v.oval == NULL)
						{
							yyerror("Template %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_STRUCTURESTAT:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.ival = getStructStatFromName($3.pString);
						if (data.v.ival == -1)
						{
							yyerror("Structure Stat %s not found", $3.pString);
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_STRUCTUREID:
						if ($3.type != IT_INDEX)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						psObj = getBaseObjFromId((UDWORD)$3.index);
						if (!psObj)
						{
							yyerror("Structure id %d not found", (UDWORD)$3.index);
							YYABORT;
						}
						data.v.ival = $3.index;	/* store structure id */
						if (psObj->type != OBJ_STRUCTURE)
						{
							yyerror("Object id %d is not a structure", (UDWORD)$3.index);
							YYABORT;
						}
						else
						{
							if(!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;
					case ST_DROIDID:
						if ($3.type != IT_INDEX)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						psObj = getBaseObjFromId((UDWORD)$3.index);
						if (!psObj)
						{
							yyerror("Droid id %d not found", (UDWORD)$3.index);
							YYABORT;
						}
						data.v.ival = $3.index;	/* store id*/
						if (psObj->type != OBJ_DROID)
						{
							yyerror("Object id %d is not a droid", (UDWORD)$3.index);
							YYABORT;
						}
						else
						{
							if(!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;
					case ST_INTMESSAGE:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.oval = getViewData($3.pString);	/* store pointer to the intelligence message */
						if (data.v.oval == NULL)
						{
							yyerror("Message %s not found", $3.pString);
							YYABORT;
						}
						if(!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_TEXTSTRING:
					{
						const char* pString;

						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						pString = strresGetString(psStringRes, $3.pString);
						if (!pString)
						{
							yyerror("Cannot find the string for id \"%s\"", $3.pString);
							YYABORT;
						}
						data.v.sval = strdup(pString);
						if (!data.v.sval)
						{
							debug(LOG_ERROR, "Out of memory");
							abort();
							YYABORT;
						}
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					}
					case ST_LEVEL:
						{
							LEVEL_DATASET	*psLevel;

							if ($3.type != IT_STRING)
							{
								yyerror("Typemismatch for variable %d", $1);
								YYABORT;
							}
							// just check the level exists
							psLevel = levFindDataSet($3.pString);
							if (psLevel == NULL)
							{
								yyerror("Level %s not found", $3.pString);
								YYABORT;
							}
							data.v.sval = psLevel->pName; /* store string pointer */
							if (!eventSetContextVar(psCurrContext, $1, &data))
							{
								yyerror("Set Value Failed for %u", $1);
								YYABORT;
							}
						}
						break;
					case ST_SOUND:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						/* find audio id */
						compIndex = audio_GetTrackID( $3.pString );
						if (compIndex == SAMPLE_NOT_FOUND)
						{
							/* set track vals */
							compIndex = audio_SetTrackVals($3.pString, false, 100, 1800);
						}
						/* save track ID */
						data.v.ival = compIndex;
						if (!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					case ST_RESEARCH:
						if ($3.type != IT_STRING)
						{
							yyerror("Typemismatch for variable %d", $1);
							YYABORT;
						}
						data.v.oval = getResearch($3.pString);	/* store pointer */
						if (data.v.oval == NULL)
						{
							yyerror("Research %s not found", $3.pString);
							// YYABORT;
						}
						if(!eventSetContextVar(psCurrContext, $1, &data))
						{
							yyerror("Set Value Failed for %u", $1);
							YYABORT;
						}
						break;
					default:
						yyerror("Unknown type: %s", asTypeTable[$2].pIdent);
						YYABORT;
						break;
					}
				}
			;

array_index:	'[' INTEGER ']'
				{
					sCurrArrayIndexes.dimensions = 1;
					sCurrArrayIndexes.elements[0] = $2;

					$$ = &sCurrArrayIndexes;
				}
			;

array_index_list:	array_index
				{
					$$ = $1;
				}
			|		array_index_list '[' INTEGER ']'
				{
					if ($1->dimensions >= VAR_MAX_DIMENSIONS)
					{
						yyerror("Too many dimensions for array");
						YYABORT;
					}
					$1->elements[$1->dimensions] = $3;
					$1->dimensions += 1;
				}
			;


var_entry:		VAR
				{
					$$ = $1;
				}
			|	ARRAY array_index_list
				{
					UDWORD	index;

					if (!scrvCheckArrayIndex($1, $2, &index))
					{
						YYABORT;
					}

					$$ = index;
				}
			;

var_value:		BOOLEAN_T
				{
					$$.type = IT_BOOL;
					$$.index = $1;
				}
			|	INTEGER
				{
					$$.type = IT_INDEX;
					$$.index = $1;
				}
			|	QTEXT
				{
					$$.type = IT_STRING;
					$$.pString = $1;
				}
			;

%%

// Lookup a type
bool scrvLookUpType(const char *pIdent, INTERP_TYPE *pType)
{
	TYPE_SYMBOL		*psCurr;

	for(psCurr = asTypeTable; psCurr->typeID != 0; psCurr++)
	{
		if (strcmp(psCurr->pIdent, pIdent) == 0)
		{
			*pType = psCurr->typeID;
			return true;
		}
	}

	return false;
}


// Lookup a variable identifier
bool scrvLookUpVar(const char *pIdent, UDWORD *pIndex)
{
	UDWORD	i;

	if (!psCurrScript || psCurrScript->psDebug == NULL)
	{
		return false;
	}

	for(i=0; i<psCurrScript->numGlobals; i++)
	{
		if (psCurrScript->psVarDebug[i].pIdent != NULL &&
			strcmp(psCurrScript->psVarDebug[i].pIdent, pIdent) == 0)
		{
			*pIndex = i;
			return true;
		}
	}

	return false;
}


// Lookup an array identifier
bool scrvLookUpArray(const char *pIdent, UDWORD *pIndex)
{
	UDWORD	i;

	if (!psCurrScript || psCurrScript->psDebug == NULL)
	{
		return false;
	}

	for(i=0; i<psCurrScript->numArrays; i++)
	{
		if (psCurrScript->psArrayDebug[i].pIdent != NULL &&
			strcmp(psCurrScript->psArrayDebug[i].pIdent, pIdent) == 0)
		{
			*pIndex = i;
			return true;
		}
	}

	return false;
}


// Load a script value file
bool scrvLoad(PHYSFS_file* fileHandle)
{
	bool retval;
	lexerinput_t input;

	input.type = LEXINPUT_PHYSFS;
	input.input.physfsfile = fileHandle;

	scrv_set_extra(&input);

	retval = (scrv_parse() == 0);
	scrv_lex_destroy();

	return retval;
}

/* A simple error reporting routine */
void yyerror(const char* fmt, ...)
{
	char* txtBuf;
	size_t size;
	va_list	args;

	va_start(args, fmt);
	size = vsnprintf(NULL, 0, fmt, args);
	va_end(args);

	txtBuf = (char *)alloca(size + 1);

	va_start(args, fmt);
	vsprintf(txtBuf, fmt, args);
	va_end(args);

	ASSERT(false, "VLO parse error: %s at line %d, text: '%s'", txtBuf, scrv_get_lineno(), scrv_get_text());
}
