/* 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/hugo/hugo.h"

namespace Glk {
namespace Hugo {

#if defined (DEBUGGER)

int Hugo::CheckObjectRange(int obj) {
	if (runtime_warnings) {
		return CheckinRange((unsigned)obj, (unsigned)objects, "object");
	} else
		return true;
}

#endif

int Hugo::Child(int obj) {
	int c;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	defseg = objtable;

	c = PeekWord(2 + obj*object_size + object_size - 4);

	defseg = gameseg;

	return c;
}

int Hugo::Children(int obj) {
	int count = 0;
	int nextobj;

	if (obj<0 || obj>=objects) return 0;

	nextobj = Child(obj);
	while (nextobj)
		{count++;
		nextobj = Sibling(nextobj);}
	return count;
}

int Hugo::Elder(int obj) {
	int lastobj;
	int p, cp;

	if (obj<0 || obj>=objects) return 0;

	p = Parent(obj);
	cp = Child(p);

	if (p==0 || cp==obj)
		return 0;

	lastobj = cp;
	while (Sibling(lastobj) != obj)
		lastobj = Sibling(lastobj);

	return lastobj;
}

unsigned long Hugo::GetAttributes(int obj, int attribute_set) {
	unsigned long a;

	defseg = objtable;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	a = (unsigned long)PeekWord(2 + obj*object_size + attribute_set*4)
		+ (unsigned long)PeekWord(2 + obj*object_size + attribute_set*4 + 2)*65536L;

	defseg = gameseg;

	return a;
}

int Hugo::GetProp(int obj, int p, int n, char s) {
	char objonly,			/* no verbroutine given in before, etc. */
		isadditive = 0,		/* before, after, etc.			*/
		gotone = 0,             /* when a match has been made           */
		getpropaddress = 0;     /* when getting &object.property        */
	int i;
	int tempself,
		objtype,		/* i.e., what we're matching to		*/
		flag = 0;
	int g = 0;
	int templocals[MAXLOCALS];
	int temp_stack_depth;
	char tempinexpr = inexpr;
	unsigned int pa,		/* property address			*/
		offset = 0;
	long inprop,			/* code position in complex property	*/
		returnaddr;
#if defined (DEBUGGER)
	long orig_inprop;
	int tempdbnest;

	/* Don't check a possible non-existent display object (-1) */
	if (obj!=-1 || display_object!=-1) CheckObjectRange(obj);
#endif
	/* This way either -1 (the non-existent display object) or a too-high
	   object will fail
	*/
	if (obj<0 || obj>=objects) return 0;

	/* The display object, which is automatically created by the compiler,
	   did not exist pre-v2.4
	*/
	if ((obj==display_object) && game_version>=24)
	{
		/* There are no actual default "properties" per se on the
		   display object--but reading them returns certain data about
		   the current state of display affairs
		*/

		/* no display.<prop> #2, etc. */
		if (n==1 && p<=pointer_y)
		{
			if (p==screenwidth)
#if defined (GLK) && defined (ACTUAL_LINELENGTH)
				g = ACTUAL_LINELENGTH();
#else
				g = SCREENWIDTH/FIXEDCHARWIDTH;
#endif
			else if (p==screenheight)
/* ACTUAL_SCREENHEIGHT can be set to a non-portable function if
   SCREENHEIGHT and SCREENHEIGHT have been set to large values in
   order to force the non-portable layer to handle wrapping and
   scrolling (as in the Glk port).
*/
#if defined (ACTUAL_SCREENHEIGHT)
				g = ACTUAL_SCREENHEIGHT();
#else
				g = SCREENHEIGHT/FIXEDLINEHEIGHT;
#endif
			else if (p==linelength)
/* ACTUAL_LINELENGTH functions similarly to ACTUAL_SCREENWIDTH,
   above.
*/
#if defined (ACTUAL_LINELENGTH)
				g = ACTUAL_LINELENGTH();
#else
				g = physical_windowwidth/FIXEDCHARWIDTH;
#endif
			else if (p==windowlines)
				g = physical_windowheight/FIXEDLINEHEIGHT;
			else if (p==cursor_column)
				g = (currentpos+1+hugo_textwidth(pbuffer))/FIXEDCHARWIDTH;
			else if (p==cursor_row)
				g = currentline;
			else if (p==hasgraphics)
				g = hugo_hasgraphics();
			else if (p==title_caption)
				g = FindWord(game_title);
			else if (p==hasvideo)
#if !defined (COMPILE_V25)
				g = hugo_hasvideo();
#else
				g = 0;
#endif
			else if (p==needs_repaint)
				g = display_needs_repaint;
			else if (p==pointer_x)
				g = display_pointer_x;
			else if (p==pointer_y)
				g = display_pointer_y;
			else
				g = 0;

			return g;
		}
	}

	/* To avoid prematurely getting an address in &obj.prop.prop */
	if (getaddress && MEM(codeptr)!=DECIMAL_T)
		getpropaddress = true;

	tempself = var[self];
	if (!s) var[self] = obj;

	temp_stack_depth = stack_depth;


GetNextProp:

	pa = PropAddr(obj, p, offset);

	defseg = proptable;

	/* If the object doesn't have property p, see if there's a
	   default value.
	*/
	if (!pa)
	{
		if (offset) goto NoMorePropMatches;

		if (getpropaddress)             /* if an &value */
			g = 0;
		else
			g = PeekWord(p * 2 + 2);
	}

	else
	{
		/* Property is a value... */
		if (Peek(pa+1) < PROP_ROUTINE)
		{
			if (getaddress || (int)Peek(pa+1) < n || n<=0)
			{
#if defined (DEBUGGER)
				if (n!=1)
					CheckinRange(n, (int)Peek(pa+1), "property element");
#endif
				g = 0;
			}
			else
				g = PeekWord(pa + n * 2);
		}

		/* ...or a property routine */
		else
		{
			/* Check if this is an additive property */
			defseg = proptable;
			if (Peek(2 + Peek(0)*2 + p)&ADDITIVE_FLAG)
				isadditive = true;

			/* If an &value, return the address of the
			   property routine.
			*/
			if (getpropaddress)
			{
				g = PeekWord(pa+2);
				goto NoMorePropMatches;
			}
			else
			{
#if defined (DEBUGGER)
				if (debug_eval)
				{
					debug_eval_error = true;
					DebugMessageBox("Expression Error",
						"Property routine illegal in watch/assignment");
					defseg = gameseg;
					return 0;
				}
#endif
				/* If not a complex property such as
				   before or after:
				*/
				if ((game_version>=22 && (Peek(2 + Peek(0)*2 + p)&COMPLEX_FLAG)==0) || (game_version<22 && p!=before && p!=after))
				{
					ret = 1;
					returnaddr = codeptr;

					/* Check to see if this is a valid tail-recursive return... */
					if (tail_recursion==TAIL_RECURSION_PROPERTY && MEM(codeptr)==EOL_T)
					{
						PassLocals(0);
						tail_recursion_addr = (long)PeekWord(pa+2)*address_scale;
						return 0;
					}
					/* ...but if we're not immediately followed by and end-of-line marker,
					   or another property value, cancel the pending tail-recursion
					*/
					else if (MEM(codeptr)!=DECIMAL_T)
					{
						tail_recursion = 0;
					}

					for (i=0; i<MAXLOCALS; i++)
						templocals[i] = var[MAXGLOBALS+i];

					PassLocals(0);

					SetStackFrame(stack_depth+1, RUNROUTINE_BLOCK, 0, 0);
#if defined (DEBUGGER)
					tempdbnest = dbnest;
					DebugRunRoutine((long)PeekWord(pa+2)*address_scale);
					dbnest = tempdbnest;
#else
					RunRoutine((long)PeekWord(pa+2)*address_scale);
#endif

					retflag = 0;
					codeptr = returnaddr;
					g = ret;
					ret = 0;
				}

		                /* Complex property: */
				else
				{
					for (i=0; i<MAXLOCALS; i++)
						templocals[i] = var[MAXGLOBALS+i];

					inprop = (long)PeekWord(pa + 2)*address_scale;
#ifdef DEBUGGER
					orig_inprop = inprop;
#endif
					defseg = gameseg;
					while (Peek(inprop)!=CLOSE_BRACE_T)
					{
						returnaddr = codeptr;

						codeptr = inprop;
						objonly = false;
						objtype = GetValue();
						inprop = codeptr;
						codeptr = returnaddr;

						flag = 0;

						/* If only an object (or other variable) is
						   given, and no verbroutine
						*/
						if (Peek(inprop)==JUMP_T)
						{
							objonly = true;
							if (!gotone && obj==objtype) flag = 1;
						}

						/* Otherwise, one or more verbroutines are
						   specified
						*/
						else
						{
							while (Peek(inprop)!=JUMP_T)
							{
								if (PeekWord(inprop+1)==(unsigned int)var[verbroutine] ||

									/* This is necessary because of the awkward way the pre-v2.2
									   differentiated non-verbroutine blocks, i.e., with Parse
									*/
									((game_version<22) && PeekWord(inprop+1)==(unsigned int)parseaddr && !gotone))
								{
									if (obj==objtype) flag = 1;
								}
								inprop += 3;
							}
						}

						if (flag==1)
						{
							gotone = true;
							ret = 1;
							returnaddr = codeptr;

							SetStackFrame(stack_depth, RUNROUTINE_BLOCK, 0, 0);

							PassLocals(0);
#if defined (DEBUGGER)
							/* Prevent premature stopping */
							if (debugger_step_over && !debugger_finish)
								debugger_run = true;

							if (IsBreakpoint(orig_inprop))
								complex_prop_breakpoint = true;

							sprintf(debug_line, "Calling:  %s.%s", objectname[obj], propertyname[p]);
							trace_complex_prop_routine = true;

							tempdbnest = dbnest;
							DebugRunRoutine(inprop+3);
							dbnest = tempdbnest;
#else
							RunRoutine(inprop+3);
#endif
							retflag = 0;
							codeptr = returnaddr;
							g = ret;
							ret = 0;
						}

						/* The following used to read "if (!flag || objonly..."
						   meaning that any non-verbroutine related routines
						   would fall through regardless of whether they returned
						   true or false.  I don't recall the rationale for this,
						   and have therefore removed it.
						*/
						if (!flag || (objonly && !g) || ((game_version<22) && PeekWord(inprop-2)==(unsigned int)parseaddr))
														inprop = (long)PeekWord(inprop+1)*address_scale;
						else break;
					}
				}

				for (i=0; i<MAXLOCALS; i++)
					var[MAXGLOBALS+i] = templocals[i];

				if (isadditive && !g)
				{
					offset = pa + 4;
					gotone = false;
					goto GetNextProp;
				}
			}
		}
	}

NoMorePropMatches:

	defseg = gameseg;

	var[self] = tempself;
	inexpr = tempinexpr;
	stack_depth = temp_stack_depth;

	return g;
}

int Hugo::GrandParent(int obj) {
	int nextobj;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	defseg = objtable;
	while ((nextobj = PeekWord(2 + obj*object_size + object_size-8)) != 0)
		obj = nextobj;
	defseg = gameseg;

	return obj;
}

void Hugo::MoveObj(int obj, int p) {
	int oldparent, prevobj, s;
	unsigned int objaddr, parentaddr, lastobjaddr;

	if (obj==p) return;
	if (obj<0 || obj>=objects) return;

	oldparent = Parent(obj);
	/* if (oldparent==p) return; */

	objaddr = 2 + obj*object_size;

	/* First, detach the object from its old parent and siblings... */

	prevobj = Elder(obj);
	s = Sibling(obj);
	defseg = objtable;
	if (prevobj)                                    /* sibling */
		PokeWord(2 + prevobj*object_size + object_size-6, s);
	else                                            /* child */
		PokeWord(2 + oldparent*object_size + object_size-4, s);


	/* Then move it to the new parent... */

	defseg = objtable;
	PokeWord(objaddr + object_size-8, p);   /* new parent 		*/
	PokeWord(objaddr + object_size-6, 0);   /* erase old sibling 	*/

	/* Only operate on the new parent if it isn't object 0 */
	if (p!=0)
	{

		/* Object is sole child, or... */
		if (Child(p)==0)
		{
			parentaddr = 2 + p*object_size;
			defseg = objtable;
			PokeWord(parentaddr + object_size-4, obj);
		}

		/* ...object is next sibling. */
		else
		{
			lastobjaddr = 2 + Youngest(p)*object_size;
			defseg = objtable;
			PokeWord(lastobjaddr + object_size-6, obj);
		}
	}
}

const char *Hugo::Name(int obj) {
	int p;

	p = GetProp(obj, 0, 1, 0);

	if (p)
		return GetWord((unsigned int)p);
	else
		return nullptr;
}

int Hugo::Parent(int obj) {
	int p;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	defseg = objtable;

	p = PeekWord(2 + obj*object_size + object_size-8);

	defseg = gameseg;

	return p;
}

unsigned int Hugo::PropAddr(int obj, int p, unsigned int offset) {
	unsigned char c;
	int proplen;
	unsigned int ptr;

#if defined (DEBUGGER)
	/* Don't check any non-existent display object (-1) */
	if (p!=-1) CheckinRange(p, properties, "property");
	CheckObjectRange(obj);
#endif
	/* This way either -1 (the non-existent display object) or a too-high
	   object will fail
	*/
	if (obj<0 || obj>=objects) return 0;

	defseg = objtable;

	/* Position in the property table...

	   i.e., ptr = PeekWord(2 + obj*object_size + (object_size-2));
	*/
	ptr = PeekWord(object_size*(obj+1));

	/* ...unless a position has already been given */
	if (offset) ptr = offset;

	defseg = proptable;

	c = Peek(ptr);
	while (c != PROP_END && c != (unsigned char)p)
	{
		proplen = Peek(ptr + 1);

		/* Property routine address is 1 word */
		if (proplen==PROP_ROUTINE) proplen = 1;

		ptr += proplen * 2 + 2;
		c = Peek(ptr);
	}

	defseg = gameseg;

	if (c==PROP_END)
		return 0;
	else
		return ptr;
}

void Hugo::PutAttributes(int obj, unsigned long a, int attribute_set) {
	unsigned int lword, hword;

	hword = (unsigned int)(a/65536L);
	lword = (unsigned int)(a%65536L);

	defseg = objtable;

	PokeWord(2 + obj*object_size + attribute_set*4, lword);
	PokeWord(2 + obj*object_size + attribute_set*4 + 2, hword);

	defseg = gameseg;
}

void Hugo::SetAttribute(int obj, int attr, int c) {
	unsigned long a, mask;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return;
#endif
	if (obj<0 || obj>=objects) return;

	a = GetAttributes(obj, attr/32);

	mask = 1L<<(long)(attr%32);

	if (c==1)
		a = a | mask;
	else
	{
		if (a & mask)
			a = a ^ mask;
	}

	PutAttributes(obj, a, attr/32);
}

int Hugo::Sibling(int obj) {
	int s;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	defseg = objtable;

	s = PeekWord(2+ obj*object_size + object_size-6);

	defseg = gameseg;

	return s;
}

int Hugo::TestAttribute(int obj, int attr, int nattr) {
	unsigned long a, mask, ta;

#if defined (DEBUGGER)
	if (!CheckObjectRange(obj)) return 0;
#endif
	if (obj<0 || obj>=objects) return 0;

	a = GetAttributes(obj, attr/32);

	mask = 1L<<(attr%32);

	ta = a & mask;
	if (ta) ta = 1;

	if (nattr) ta = ta ^ 1;

	return (int)ta;
}

int Hugo::Youngest(int obj) {
	int nextobj;

	if (Child(obj)==0) return 0;

	nextobj = Child(obj);

	while (Sibling(nextobj))
		nextobj = Sibling(nextobj);

	return nextobj;
}

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