/***************************************************************************

  gbx_exec_push.c

  (c) 2000-2017 BenoƮt Minisini <g4mba5@gmail.com>

  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, 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 "gb_common.h"
#include "gb_limit.h"
#include "gbx_exec.h"
#include "gb_pcode.h"
#include "gbx_api.h"

#include "gbx_string.h"
#include "gbx_c_array.h"
#include "gbx_c_collection.h"
#include "gbx_api.h"
#include "gbx_struct.h"
#include "gbx_local.h"

void EXEC_push_unknown(void)
{
	static void *jump[] = {
		&&_PUSH_GENERIC,               // 0
		&&_PUSH_CONSTANT,              // 1
		&&_PUSH_VARIABLE,              // 2
		&&_PUSH_STATIC_VARIABLE,       // 3
		&&_PUSH_PROPERTY,              // 4
		&&_PUSH_METHOD,                // 5
		&&_PUSH_STATIC_METHOD,         // 6
		&&_PUSH_VARIABLE_AUTO,         // 7
		&&_PUSH_PROPERTY_AUTO,         // 8
		&&_PUSH_METHOD_AUTO,           // 9
		&&_PUSH_EXTERN,                // 10
		&&_PUSH_STRUCT_FIELD,          // 11
		&&_PUSH_CONST_STRING,          // 12
		};

	const char *name;
	int index;
	CLASS_DESC *desc;
	CLASS *class;
	OBJECT *object;
	void *ref;
	char *addr;
	bool defined;
	VALUE *val;

	// EXEC_object can change *PC by calling EXEC_push_unknown() recursively
	// So don't store *PC anywhere

	defined = EXEC_object(&SP[-1], &class, &object);

	goto *jump[*PC & 0xF];


_PUSH_GENERIC:

	name = CP->load->unknown[PC[1]];

	// The first time we access a symbol, we must not be virtual to find it
	val = &SP[-1];
	if (defined && object && !VALUE_is_super(val))
		index = CLASS_find_symbol(val->_object.class, name);
	else
		index = CLASS_find_symbol(class, name);

	if (index == NO_SYMBOL)
	{
		//index = CLASS_find_symbol(class, name);

		if (class->special[SPEC_UNKNOWN] == NO_SYMBOL)
		{
			if (defined && object && !VALUE_is_super(val))
				class = val->_object.class;
			THROW(E_NSYMBOL, CLASS_get_name(class), name);
		}

		if (class->unknown_static && object)
			THROW(E_STATIC, CLASS_get_name(class), name);
		else if (!class->unknown_static && object == NULL)
		{
			if (!class->auto_create)
				THROW(E_DYNAMIC, CLASS_get_name(class), name);
			object = EXEC_auto_create(class, TRUE);
		}

		index = CLASS_find_symbol(class, name);
		if (index != NO_SYMBOL)
			THROW(E_NSYMBOL, CLASS_get_name(val->_object.class), name);
			
		if (class->special[SPEC_PROPERTY] != NO_SYMBOL)
		{
			EXEC_unknown_name = name;
			if (!EXEC_special(SPEC_PROPERTY, class, class->property_static ? NULL : object, 0, FALSE))
			{
				VALUE_conv_boolean(&SP[-1]);
				SP--;
				if (SP->_boolean.value)
				{
					//SP--; Keep the object on the stack so that it is automatically freed if an error is raised
					EXEC_special(SPEC_UNKNOWN, class, class->unknown_static ? NULL : object, 0, FALSE);
					VALUE_conv_variant(&SP[-1]);
					SP--;
					SP[-1] = *SP;
					OBJECT_UNREF(object);
					goto _FIN;
				}
			}
		}

		goto _PUSH_UNKNOWN_METHOD;
	}

	desc = class->table[index].desc;

	switch (CLASS_DESC_get_type(desc))
	{
		case CD_CONSTANT:

			if (TYPE_is_string(desc->constant.type))
			{
				if (defined)
				{
					*PC |= 12;
					PC[1] = index;
				}

				goto _PUSH_CONST_STRING_2;
			}
			else
			{
				if (defined)
				{
					if ((PC[-1] & 0xF800) == C_PUSH_CLASS)
					{
						if (desc->constant.type == T_BOOLEAN)
						{
							PC[-1] = C_PUSH_MISC | (desc->constant.value._integer ? CPM_TRUE : CPM_FALSE);
							PC[0] = C_NOP;
							PC[1] = C_NOP;
							goto _PUSH_CONSTANT_2;
						}
						else if (desc->constant.type == T_BYTE || desc->constant.type == T_SHORT)
						{
							PC[-1] = C_PUSH_INTEGER;
							PC[0] = desc->constant.value._integer;
							PC[1] = (CODE_CONV << 8) | desc->constant.type;
							goto _PUSH_CONSTANT_2;
						}
						else if (desc->constant.type == T_INTEGER)
						{
							PC[-1] = C_PUSH_LONG;
							PC[0] = desc->constant.value._integer & 0xFFFF;
							PC[1] = desc->constant.value._integer >> 16;
							goto _PUSH_CONSTANT_2;
						}
					}

					*PC |= 1;

					PC[1] = index;
				}

				goto _PUSH_CONSTANT_2;
			}

		case CD_VARIABLE:

			if (object == NULL)
			{
				if (!class->auto_create)
					THROW(E_DYNAMIC, CLASS_get_name(class), name);
				object = EXEC_auto_create(class, TRUE);
				*PC |= 7;
			}
			else
			{
				if (defined) *PC |= 2;
			}

			if (defined)
				PC[1] = index;

			goto _PUSH_VARIABLE_2;

		case CD_STRUCT_FIELD:

			if (object == NULL)
				THROW(E_DYNAMIC, CLASS_get_name(class), name);

			if (defined)
			{
				*PC |= 11;
				PC[1] = index;
			}

			goto _PUSH_STRUCT_FIELD_2;

		case CD_STATIC_VARIABLE:

			if (object)
				THROW(E_STATIC, CLASS_get_name(class), name);

			if (defined) *PC |= 3;

			if (defined)
				PC[1] = index;

			goto _PUSH_STATIC_VARIABLE_2;

		case CD_PROPERTY:
		case CD_PROPERTY_READ:

			if (object == NULL)
			{
				if (!class->auto_create)
					THROW(E_DYNAMIC, CLASS_get_name(class), name);
				object = EXEC_auto_create(class, TRUE);
				*PC |= 8;
			}
			else
			{
				if (defined) *PC |= 4;
			}

			if (defined)
				PC[1] = index;

			goto _PUSH_PROPERTY_2;

		case CD_STATIC_PROPERTY:
		case CD_STATIC_PROPERTY_READ:

			if (object)
				THROW(E_STATIC, CLASS_get_name(class), name);

			if (defined) *PC |= 4;

			if (defined)
				PC[1] = index;

			goto _PUSH_PROPERTY_2;

		case CD_METHOD:

			if (object == NULL)
			{
				if (!class->auto_create)
					THROW(E_DYNAMIC, CLASS_get_name(class), name);
				object = EXEC_auto_create(class, TRUE);
				*PC |= 9;
			}
			else
			{
				if (defined) *PC |= 5;
			}

			if (defined)
				PC[1] = index;

			goto _PUSH_METHOD_2;

		case CD_STATIC_METHOD:

			if (object)
			{
				OBJECT_UNREF(object);
				object = NULL;
			}

			if (defined) *PC |= 6;

			if (defined)
				PC[1] = index;

			goto _PUSH_METHOD_2;

		case CD_EXTERN:

			if (object)
			{
				OBJECT_UNREF(object);
				object = NULL;
			}

			if (defined) *PC |= 10;

			if (defined)
				PC[1] = index;

			goto _PUSH_EXTERN_2;

		default:

			THROW(E_NSYMBOL, CLASS_get_name(class), name);
	}


_PUSH_CONSTANT:

	desc = class->table[PC[1]].desc;

_PUSH_CONSTANT_2:

	VALUE_read(&SP[-1], (void *)&desc->constant.value, desc->constant.type);
	goto _FIN_DEFINED;


_PUSH_CONST_STRING:

	desc = class->table[PC[1]].desc;

_PUSH_CONST_STRING_2:

	SP--;
	SP->type = T_CSTRING;

	if (!desc->constant.value._string)
	{
		SP->_string.addr = NULL;
		SP->_string.len = 0;
	}
	else
	{
		if (desc->constant.translate)
			SP->_string.addr = (char *)LOCAL_gettext(desc->constant.value._string);
		else
			SP->_string.addr = (char *)desc->constant.value._string;

		SP->_string.len = strlen(SP->_string.addr);
	}

	SP->_string.start = 0;
	SP++;
	goto _FIN_DEFINED_NO_BORROW;


_PUSH_VARIABLE_AUTO:

	object = EXEC_auto_create(class, TRUE);

_PUSH_VARIABLE:

	desc = class->table[PC[1]].desc;

_PUSH_VARIABLE_2:

	addr = (char *)object + desc->variable.offset;
	ref = object;
	goto _READ_VARIABLE;


_PUSH_STATIC_VARIABLE:

	desc = class->table[PC[1]].desc;

_PUSH_STATIC_VARIABLE_2:

	addr = (char *)desc->variable.class->stat + desc->variable.offset;
	ref = desc->variable.class;
	goto _READ_VARIABLE;


_PUSH_STRUCT_FIELD:

	desc = class->table[PC[1]].desc;

_PUSH_STRUCT_FIELD_2:

	if (((CSTRUCT *)object)->ref)
		addr = (char *)((CSTATICSTRUCT *)object)->addr + desc->variable.offset;
	else
		addr = (char *)object + sizeof(CSTRUCT) + desc->variable.offset;

	ref = object;
	goto _READ_VARIABLE;


_READ_VARIABLE:

	VALUE_class_read(desc->variable.class, &SP[-1], (void *)addr, desc->variable.ctype, ref);
	goto _FIN_DEFINED;


_PUSH_PROPERTY_AUTO:

	object = EXEC_auto_create(class, TRUE);

_PUSH_PROPERTY:

	desc = class->table[PC[1]].desc;

_PUSH_PROPERTY_2:

	if (desc->property.native)
	{
		if (EXEC_call_native(desc->property.read, object, desc->property.type, NULL))
			PROPAGATE();

		//SP[-1] = TEMP;
		VALUE_copy(&SP[-1], &TEMP);
		goto _FIN_DEFINED;
	}
	else
	{
		EXEC.class = desc->property.class;
		EXEC.object = object;
		EXEC.nparam = 0;
		EXEC.native = FALSE;
		EXEC.index = (int)(intptr_t)desc->property.read;

		EXEC_function_keep();

		//SP[-1] = *RP;
		VALUE_copy(&SP[-1], RP);
		RP->type = T_VOID;
		goto _FIN_DEFINED_NO_BORROW;
	}


_PUSH_STATIC_METHOD:

	if (object)
	{
		OBJECT_UNREF(object);
		object = NULL;
	}

	goto _PUSH_METHOD;

_PUSH_METHOD_AUTO:

	object = EXEC_auto_create(class, TRUE);

_PUSH_METHOD:

	index = PC[1];
	desc = class->table[index].desc;

_PUSH_METHOD_2:

	//printf("PUSH_METHOD: %d %s\n", index, desc->method.name);

	SP--;
	SP->type = T_FUNCTION;
	SP->_function.class = class;
	SP->_function.object = object;
	/*SP->_function.function = (int)&desc->method;*/

	if (FUNCTION_is_native(&desc->method))
	{
		if (desc->method.subr)
			SP->_function.kind = FUNCTION_SUBR;
		else
			SP->_function.kind = FUNCTION_NATIVE;
	}
	else
		SP->_function.kind =  FUNCTION_PUBLIC;

	SP->_function.index = index;

	SP->_function.defined = defined;

	SP++;

	goto _FIN;


_PUSH_EXTERN:

	if (object)
	{
		OBJECT_UNREF(object);
		object = NULL;
	}

	index = PC[1];
	desc = class->table[index].desc;

_PUSH_EXTERN_2:

	//printf("PUSH_METHOD: %d %s\n", index, desc->method.name);

	SP--;
	SP->type = T_FUNCTION;
	SP->_function.class = class;
	SP->_function.object = object;
	SP->_function.kind = FUNCTION_EXTERN;
	SP->_function.index = (int)desc->ext.exec;
	SP->_function.defined = defined;
	SP++;

	goto _FIN;

_PUSH_UNKNOWN_METHOD:

	SP--;
	SP->type = T_FUNCTION;
	SP->_function.class = class;
	SP->_function.object = object;
	SP->_function.kind = FUNCTION_UNKNOWN;
	SP->_function.index = PC[1];
	SP->_function.defined = FALSE;
	SP++;

	//OBJECT_REF(&object);

	goto _FIN;

_FIN_DEFINED:

	BORROW(&SP[-1]);

_FIN_DEFINED_NO_BORROW:

	if (!defined)
		VALUE_conv_variant(&SP[-1]);

	// SP[-1] was the object and it has been erased. So we must unref it manually,
	// unless we are calling a static method (see above)
	OBJECT_UNREF(object);

_FIN:

	PC++;
}

#if 0
void EXEC_push_array(ushort code)
{
	static const void *jump[] = { &&__PUSH_GENERIC, &&__PUSH_QUICK_ARRAY, &&__PUSH_QUICK_COLLECTION, &&__PUSH_ARRAY };

	CLASS *class;
	OBJECT *object;
	GET_NPARAM(np);
	//int dim[MAX_ARRAY_DIM];
	int i;
	void *data;
	bool defined;
	VALUE *val;
	int fast;
	//ARRAY_DESC *desc;

	val = &SP[-np];
	np--;

	goto *jump[((unsigned char)code) >> 6];

__PUSH_GENERIC:

	defined = EXEC_object(val, &class, &object);

	fast = 3;

	if (defined)
	{
		if (class->quick_array == CQA_ARRAY)
			fast = 1;
		else if (class->quick_array == CQA_COLLECTION)
			fast = 2;
		else
		{
			// Check the symbol existance, but *not virtually*
			if (object && !VALUE_is_super(val))
			{
				CLASS *nvclass = val->_object.class;

				if (nvclass->special[SPEC_GET] == NO_SYMBOL)
					THROW(E_NARRAY, CLASS_get_name(nvclass));
			}
		}
	}

	*PC |= fast << 6;

	goto __PUSH_ARRAY_2;

/*__PUSH_STATIC_ARRAY:

	for (i = 1; i <= np; i++)
	{
		VALUE_conv_integer(&val[i];
		dim[i - 1] = val[i]._integer.value;
	}

	SP = val;

	desc = (ARRAY_DESC *)SP->_array.class->load->array[SP->_array.index];
	data = ARRAY_get_address(desc, SP->_array.addr, np, dim);

	VALUE_read(SP, data, CLASS_ctype_to_type(SP->_array.class, desc->type));

	PUSH();
	return;*/

__PUSH_QUICK_ARRAY:

	// Optimization test

	//EXEC_object_fast(val, &class, &object);
	EXEC_object_array(val, class, object);

	VALUE_conv_integer(&val[1]);

	if (np == 1)
	{
		data = CARRAY_get_data((CARRAY *)object, val[1]._integer.value);
	}
	else
	{
		for (i = 2; i <= np; i++)
			VALUE_conv_integer(&val[i]);

		data = CARRAY_get_data_multi((CARRAY *)object, (GB_INTEGER *)&val[1], np);
	}

	if (!data)
		PROPAGATE();

	//VALUE_read(val, data, ((CARRAY *)object)->type);
	if (TYPE_is_object(((CARRAY *)object)->type))
		fast = T_OBJECT;
	else
		fast = ((CARRAY *)object)->type;

	VALUE_read_inline_type(val, data, fast);

	goto __PUSH_QUICK_END;

__PUSH_QUICK_COLLECTION:

	EXEC_object_fast(val, &class, &object);

	VALUE_conv_string(&val[1]);
	//fprintf(stderr, "GB_CollectionGet: %p '%.*s'\n", val[1]._string.addr, val[1]._string.len, val[1]._string.addr + val[1]._string.start);
	GB_CollectionGet((GB_COLLECTION)object, val[1]._string.addr + val[1]._string.start, val[1]._string.len, (GB_VARIANT *)val);

	RELEASE_STRING(&val[1]);

__PUSH_QUICK_END:

	SP = val;
	PUSH();
	OBJECT_UNREF(object);
	return;

__PUSH_ARRAY:

	defined = EXEC_object(val, &class, &object);

__PUSH_ARRAY_2:

	if (EXEC_special(SPEC_GET, class, object, np, FALSE))
		THROW(E_NARRAY, CLASS_get_name(class));

	OBJECT_UNREF(object);
	SP--;
	//SP[-1] = SP[0];
	VALUE_copy(&SP[-1], &SP[0]);

	if (!defined)
		VALUE_conv_variant(&SP[-1]);
}
#endif

#if 0
// JIT needs it
void EXEC_push_array(ushort code)
{
	CLASS *class;
	OBJECT *object;
	GET_NPARAM(np);
	bool defined;
	VALUE *val;

	val = &SP[-np];
	np--;
	defined = EXEC_object(val, &class, &object);

	if (EXEC_special(SPEC_GET, class, object, np, FALSE))
		THROW(E_NARRAY, CLASS_get_name(class));

	OBJECT_UNREF(object);
	SP--;
	//SP[-1] = SP[0];
	VALUE_copy(&SP[-1], &SP[0]);

	if (!defined)
		VALUE_conv_variant(&SP[-1]);
}
#endif

int EXEC_push_unknown_event(bool unknown)
{
	int index;
	CLASS_DESC *desc;
	const char *name;

	if (unknown)
	{
		name = CP->load->unknown[PC[1]];
		// The ':' is already in the name, thanks to the compiler.
		index = CLASS_find_symbol(CP, name);
		if (index == NO_SYMBOL)
			THROW(E_DYNAMIC, CLASS_get_name(CP), name);

		desc = CP->table[index].desc;
		if (CLASS_DESC_get_type(desc) != CD_EVENT)
			THROW(E_DYNAMIC, CLASS_get_name(CP), name);

		PC[1] = index;
		PC[0] &= ~1;
	}
	else
	{
		desc = CP->table[PC[1]].desc;
	}

	index = desc->event.index;

	//if (desc->event.class->parent)
	//	index += desc->event.class->parent->n_event;

	return index;
}