/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "glk/hugo/hugo.h"

namespace Glk {
namespace Hugo {

void Hugo::RunSet(int gotvalue) {
	char inc = 0;                   /* increment/decrement */
	char temparrexpr, propval = 0;
	int a = 0, t = 0, obj = 0;
	int newl = 0;                   /* new length */
	int newp = 0;			/* new property val */
	unsigned int element = 0;		/* of an array */

	unsigned short n, m, v;		/* must be 16 bits */

	inobj = 0;

	if (gotvalue!=-1)
	{
		obj = gotvalue;
		t = SetCompound(t);
		goto StoreVal;
	}

	t = MEM(codeptr);

	switch (t)
	{
		case OBJECTNUM_T:
		{
			codeptr++;
			obj = PeekWord(codeptr);
			codeptr += 2;
			t = SetCompound(t);
			break;
		}

		case VAR_T:
		{
			a = MEM(codeptr + 1);

			/* Check for ++, --, +=, etc. */
			inc = IsIncrement(codeptr+2);

			if (MEM(codeptr + 2)==EQUALS_T || inc)
			{
				if (a < MAXGLOBALS) SaveUndo(VAR_T, a, var[a], 0, 0);

				/* anonymous function */
				if (!inc && MEM(codeptr+3)==EOL_T)
				{
					var[a] = GetAnonymousFunction(codeptr+4);
					return;
				}

				if (inc)
				{
					var[a] = (Increment(var[a], inc)) + incdec;

					/* backward-compatibility tweak */
					if ((game_version<23) && MEM(codeptr)!=CLOSE_BRACE_T) codeptr--;

					codeptr++;      /* eol */
				}
				else
				{
					codeptr += 3;
					inexpr = 1;
					SetupExpr();
					inexpr = 0;
					v = EvalExpr(0);
					var[a] = v;
				}

				/* If a global variable */
				if (a < MAXGLOBALS)
				{
					if (a==wordcount) words = var[wordcount];
				}

				return;
			}

			obj = var[a];
			codeptr += 2;
			t = SetCompound(t);

			break;
		}

		case WORD_T:			/* "word" */
		{
			codeptr += 2;           /* skip "[" */
			n = GetValue();
#if defined (DEBUGGER)
			if ((debug_eval) && debug_eval_error)
				return;
#endif
			codeptr += 2;           /* skip "] =" */
			inexpr = 1;
			SetupExpr();
			inexpr = 0;

GetNextWord:
			if (n >= MAXWORDS) n = MAXWORDS-1;
			SaveUndo(WORD_T, n, wd[n], 0, 0);
			wd[n] = EvalExpr(0);

			if (MEM(codeptr)==COMMA_T)
			{
				codeptr++;
				n++;
				goto GetNextWord;
			}

			/* Have to (rather unfortunately) rebuild the entire
			   input buffer and word array here
			*/
			strcpy(buffer, "");
			t = 0;
			for (a=1; a<=(int)MAXWORDS; a++)
			{
				if ((unsigned short)wd[a]!=UNKNOWN_WORD)
					strcpy(buffer+t, GetWord(wd[a]));
				else
					itoa(parsed_number, buffer+t, 10);
			        word[a] = buffer + t;
				t+=strlen(word[a])+1;
			}

			if (n>(unsigned)var[wordcount])
				var[wordcount] = n;

			return;
		}

		case ARRAYDATA_T:
		case ARRAY_T:
		{
			char af_flag = false;
			/* array[n]... */
			if (t==ARRAYDATA_T)
			{
				m = PeekWord(codeptr + 1);
				codeptr += 4;   /* "[" */
				n = GetValue();
#if defined (DEBUGGER)
				if ((debug_eval) && debug_eval_error)
					return;
#endif
				codeptr++;      /* "]" */
			}

			/* ...or array val[n] */
			else
			{
				codeptr++;
				m = GetValue();
#if defined (DEBUGGER)
				if ((debug_eval) && debug_eval_error)
					return;
#endif
				codeptr++;      /* "[" */
				n = GetValue();
#if defined (DEBUGGER)
				if ((debug_eval) && debug_eval_error)
					return;
#endif
				codeptr++;      /* "]" */
			}

			if (game_version>=22)
			{
				/* Convert to word value */
				m*=2;

				if (game_version>=23)
					/* Space for array length */
					a = 2;
			}

#if defined (DEBUGGER)
			CheckinRange(m+a+n*2, debug_workspace, "array data");
#endif
			/* Check for ++, --, +=, etc. */
			inc = IsIncrement(codeptr);
			if (inc)
			{
				defseg = arraytable;
				v = PeekWord(m+a+n*2);
				defseg = gameseg;
				v = (Increment(v, inc)) + incdec;

				codeptr++;      /* eol */

				element = m+a+n*2;

				goto WriteArrayValue;
			}

			if (MEM(codeptr)==EQUALS_T)
			{
				codeptr++;

				do
				{
					element = m+a+n*2;

					temparrexpr = arrexpr;
					arrexpr = true;

					/* anonymous function */
					if (!inc && MEM(codeptr)==EOL_T)
					{
						v = GetAnonymousFunction(codeptr+1);
						af_flag = true;
					}
					else
					{
						v = GetValue();
					}
#if defined (DEBUGGER)
					if ((debug_eval) && debug_eval_error)
						return;
#endif
					if (arrexpr==false && MEM(codeptr-1)==76)
						codeptr--;
					arrexpr = temparrexpr;

					if (!af_flag && (MEM(codeptr)==COMMA_T || MEM(codeptr)==CLOSE_BRACKET_T))
						codeptr++;
WriteArrayValue:
					defseg = arraytable;

					/* Make sure the value to be written is within range */
					if ((element>0) && (element < (unsigned)(dicttable-arraytable)*16))
					{
						SaveUndo(ARRAYDATA_T, m+a, n, PeekWord(element), 0);

						PokeWord(element, (unsigned int)v);
					}

					defseg = gameseg;

					if (inc || af_flag) return;

					n++;
				}
				while (MEM(codeptr)!=EOL_T);

				codeptr++;
				return;
			}

			defseg = arraytable;
			obj = PeekWord((unsigned int)(m+a + n*2));
			defseg = gameseg;
			t = SetCompound(t);

			break;
		}
	}


StoreVal:

	/* Now store the evaluated expression in the appropriate place... */

	/*
		t = 1:  property
		t = 2:  attribute
		t = 3:  not attribute
		t = 4:  property reference
	*/

	n = 1;

	if (t==4)
	{
		inobj = true;
		n = GetValue();

#if defined (DEBUGGER)
		if ((debug_eval) && debug_eval_error)
			return;
#endif
		inobj = false;

LoopBack:
		if (MEM(codeptr)==IS_T || MEM(codeptr)==DECIMAL_T)
		{
			obj = GetProp(obj, set_value, n, 0);
			t = SetCompound(t);
			goto LoopBack;
		}
		/* Don't set t = 1 if it changed above before going back
		   to LoopBack:
		*/
		else if (t==4)
			t = 1;          /* Just a property */
	}
	else if (t==1)
	{
		while (MEM(codeptr)==IS_T || MEM(codeptr)==DECIMAL_T)
		{
			obj = GetProp(obj, set_value, n, 0);
			t = SetCompound(t);
		}
	}

	switch (t)
	{
		case 1:
		{
			incdec = 0;

			if (MEM(codeptr) != EQUALS_T)
			{
				/* Check for ++, --, +=, etc. */
				if (!(inc = IsIncrement(codeptr)))
				{
#if defined (DEBUGGER)
					if (debug_eval)
					{
						debug_eval_error = true;
						return;
					}
#endif
					FatalError(ILLEGAL_OP_E);
				}
				else if (MEM(codeptr)==EOL_T)
				{
					goto GetNextPropVal;
				}
			}
			else
				codeptr++;

			/* Property routine (anonymous function)... */
			if (MEM(codeptr)==EOL_T)
			{
				/* m = skipptr to the end of the property
				   routine block (i.e., the next statement
				   following it)
				*/
				m = PeekWord(codeptr + 1);

				newl = PROP_ROUTINE;
				newp =(unsigned int)(((codeptr + 4)+(codeptr + 4)%address_scale)/address_scale);
				codeptr = (long)m*address_scale;
				m = PropAddr(obj, set_value, 0);
			}

			/* ...or not */
			else
			{
GetNextPropVal:
				inexpr = false;
				temparrexpr = multiprop;
				multiprop = true;
				propval = true;
				if (!inc) newp = GetValue();
#if defined (DEBUGGER)
				if ((debug_eval) && debug_eval_error)
					return;
#endif

				if (!multiprop)
					codeptr--;
				multiprop = temparrexpr;

				m = PropAddr(obj, set_value, 0);
				if (m)
				{
					defseg = proptable;
					newl = Peek((unsigned int)m + 1);
					if (newl==PROP_ROUTINE) newl = 1;
				}

				/* Deal with setting built-in display object
				   properties
				*/
				else if ((obj==display_object) && n==1)
				{
					if (set_value==title_caption)
					{
						strncpy(game_title, GetWord(newp), MAX_GAME_TITLE);
						hugo_setgametitle(game_title);
					}
					else if (set_value==needs_repaint)
					{
						display_needs_repaint = (char)newp;
					}
				}
#if defined (DEBUGGER)
/*
				else if (runtime_warnings)
				{
					RuntimeWarning("Setting non-existent property");
				}
*/
#endif
			}

			/* Write property obj.z = newl words of newp */

			if (m && (int)n <= 0)
			{
#if defined (DEBUGGER)
				RuntimeWarning("Property element <= 0");
#endif
				if (inc) codeptr++;
			}
			else if (m && (int)n <= newl)
			{
				defseg = proptable;

#if defined (DEBUGGER)
				CheckinRange((unsigned)n, (unsigned)Peek(m+1), "property element");
#endif
				/* Check to make sure this property value is within range */
				if ((unsigned)(m+2+(n-1)*2)<(unsigned)(eventtable-proptable)*16)
				{
					SaveUndo(PROP_T, obj, (unsigned int)set_value, n, PeekWord((unsigned int)(m+2+(n-1)*2)));

					/* Save the (possibly changed) length) */
					Poke((unsigned int)m + 1, (unsigned char)newl);

					/* An assignment such as obj.prop++ or
					   obj.prop += ...
					*/
					if (inc)
					{
						PokeWord((unsigned int)(m+2+(n-1)*2), Increment(PeekWord((unsigned int)(m+2+(n-1)*2)), inc) + incdec);
						codeptr++;      /* eol */
					}

					/* A regular obj.prop = ... assignment */
					else
						PokeWord((unsigned int)(m+2+(n-1)*2), newp);
				}
			}
			else if (inc) codeptr++;	/* eol */

			defseg = gameseg;

			if (inc) return;

			if (propval && MEM(codeptr)==COMMA_T)
				{n++;
				codeptr++;
				goto GetNextPropVal;}

			if (propval) codeptr++;
			if (MEM(codeptr)==EOL_T) codeptr++;
			return;
		}

		case 2:
		case 3:
		{
ModifyAttribute:

#if defined (DEBUGGER)
			CheckinRange((unsigned int)set_value, (unsigned)attributes, "attribute");
#endif
			SaveUndo(ATTR_T, obj, (unsigned int)set_value, TestAttribute(obj, (unsigned int)set_value, 0), 0);

			SetAttribute(obj, set_value, (t==2));
			t = 2;	/* reset after 'not' */

			if (MEM(codeptr++)==EOL_T) return;

			/* Allow multiple attributes, comma-separated */
			if (MEM(codeptr)==COMMA_T)
				codeptr++;

			if (MEM(codeptr)==NOT_T)
			{
				t = 3;
				codeptr++;
			}

			set_value = GetValue();
			goto ModifyAttribute;
		}

		default:
		{
#if defined (DEBUGGER)
			if (debug_eval)
			{
				debug_eval_error = true;
				return;
			}
#endif
			/* Not any sort of variable data type */
			FatalError(ILLEGAL_OP_E);
		}
	}
}

unsigned int Hugo::GetAnonymousFunction(long addr) {
	long skipaddr;
	unsigned int af_addr;
	
	skipaddr = PeekWord(addr);
	/* The address of the anonymous function is the next address boundary,
	   calculated as:
	   (((addr+2)/address_scale+1)*address_scale)/address_scale */
	af_addr =(unsigned int)((addr+2)/address_scale+1);
	codeptr = (long)skipaddr*address_scale;
	return af_addr;
}

int Hugo::SetCompound(int t) {
	if (Peek(codeptr)==DECIMAL_T)		/* obj.property */
	{
		codeptr++;
		inobj = 1;
		set_value = GetValue();		/* the prop. # */
		inobj = 0;

		if (Peek(codeptr)==POUND_T)	/* if obj.prop #... */
		{
			codeptr++;
			return 4;
		}
		return 1;
	}

	if (Peek(codeptr)==IS_T)		/* obj is ... */
	{
		inobj = 1;
		if (Peek(codeptr+1)==NOT_T)
		{
			codeptr += 2;
                        set_value = GetValue();	/* the attr. # */
			inobj = 0;
			return 3;
		}

		codeptr++;
		set_value = GetValue();		/* the attr. # */
		inobj = 0;
		return 2;
	}

#if defined (DEBUGGER)
	if (debug_eval)
		debug_eval_error = true;
	else
#endif

	FatalError(ILLEGAL_OP_E);

	return 0;
}

} // End of namespace Hugo
} // End of namespace Glk