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

	gbx_api.c

	(c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

	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.

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

#define __GBX_API_C

#include "gb_common.h"
#include "gb_common_case.h"
#include "gb_common_buffer.h"
#include "gb_error.h"
#include "gb_alloc.h"
#include "gb_list.h"

#include <stdarg.h>

#include "gbx_class.h"
#include "gbx_exec.h"
#include "gbx_event.h"
#include "gbx_stack.h"
#include "gbx_stream.h"
#include "gbx_library.h"
#include "gbx_watch.h"
#include "gbx_project.h"
#include "gbx_eval.h"
#include "gbx_local.h"
#include "gb_hash.h"
#include "gb_file.h"
#include "gbx_number.h"
#include "gbx_object.h"
#include "gbx_string.h"
#include "gbx_date.h"
#include "gbx_regexp.h"
#include "gbx_c_array.h"
#include "gbx_c_timer.h"
#include "gbx_component.h"
#include "gbx_c_gambas.h"
#include "gbx_c_observer.h"
#include "gbx_debug.h"
#include "gbx_c_file.h"
#include "gbx_extern.h"
#include "gbx_compare.h"
#include "gbx_subr.h"
#include "gbx_math.h"
#include "gbx_struct.h"
#include "gbx_signal.h"
#include "gbx_jit.h"
#include "gbx_split.h"
#include "gbx.h"
#include "gambas.h"
#include "gbx_api.h"

const void *const GAMBAS_Api[] =
{
	(void *)GB_VERSION,

	(void *)GB_GetInterface,

	(void *)GB_Hook,

	(void *)GB_LoadComponent,
	(void *)COMPONENT_can_load_library,
	(void *)COMPONENT_exist,
	(void *)COMPONENT_is_loaded,
	(void *)GB_CurrentComponent,
	(void *)COMPONENT_get_info,
	(void *)COMPONENT_signal,
	(void *)LIBRARY_declare_one,

	(void *)GB_Push,
	(void *)GB_GetFunction,
	(void *)GB_Call,
	(void *)GB_GetClassInterface,
	(void *)GB_GetProperty,
	(void *)GB_SetProperty,
	(void *)GB_Serialize,
	(void *)GB_UnSerialize,

	(void *)WATCH_one_loop,
	(void *)GB_Wait,
	(void *)EVENT_post,
	(void *)EVENT_post2,
	(void *)CTIMER_every,
	(void *)GB_RaiseEvent,
	(void *)GB_RaiseBegin,
	(void *)GB_RaiseEnd,
	(void *)EVENT_post_event,
	(void *)EVENT_check_post,
	(void *)GB_CanRaise,
	(void *)GB_GetEvent,
	(void *)GB_GetLastEventName,
	(void *)CTIMER_raise,
	(void *)GB_HasActiveTimer,
	(void *)GB_Stopped,
	(void *)GB_IsRaiseLocked,

	(void *)GB_NParam,
	(void *)GB_Conv,
	(void *)GB_GetUnknown,

	(void *)GB_Error,
	(void *)GB_HasError,
	(void *)GB_GetErrorMessage,
	(void *)ERROR_propagate,
	(void *)GB_Deprecated,
	(void *)GB_OnErrorBegin,
	(void *)GB_OnErrorEnd,

	(void *)GB_GetClass,
	(void *)GB_GetClassName,
	(void *)GB_ExistClass,
	(void *)GB_FindClass,
	(void *)GB_ExistClassLocal,
	(void *)GB_FindClassLocal,
	(void *)GB_GetArrayType,
	(void *)CLASS_get_array_class,
	(void *)CLASS_find_load_from,
	(void *)GB_Is,
	(void *)GB_Ref,
	(void *)GB_Unref,
	//(void *)GB_UnrefKeep,
	(void *)GB_Detach,
	(void *)GB_Attach,
	(void *)OBJECT_parent,
	(void *)GB_Create,
	(void *)GB_New,
	(void *)CLASS_auto_create,
	(void *)GB_CheckObject,
	(void *)OBJECT_is_locked,

	(void *)GB_GetEnum,
	(void *)GB_StopEnum,
	(void *)GB_BeginEnum,
	(void *)GB_EndEnum,
	(void *)GB_NextEnum,
	(void *)GB_StopAllEnum,
	(void *)GB_OnFreeEnum,

	(void *)GB_GetReturnValue,
	(void *)GB_Return,
	(void *)GB_ReturnInteger,
	(void *)GB_ReturnLong,
	(void *)GB_ReturnPointer,
	(void *)GB_ReturnBoolean,
	(void *)GB_ReturnDate,
	(void *)GB_ReturnObject,
	(void *)GB_ReturnNull,
	(void *)GB_ReturnSingle,
	(void *)GB_ReturnFloat,
	(void *)GB_ReturnVariant,
	(void *)GB_ReturnConvVariant,
	(void *)GB_ReturnBorrow,
	(void *)GB_ReturnRelease,
	(void *)GB_ReturnPtr,
	(void *)GB_ReturnSelf,

	(void *)GB_ReturnString,
	(void *)GB_ReturnVoidString,
	(void *)GB_ReturnConstString,
	(void *)GB_ReturnConstZeroString,
	(void *)GB_ReturnNewString,
	(void *)GB_ReturnNewZeroString,

	(void *)STRING_new,
	(void *)GB_NewZeroString,
	(void *)GB_TempString,
	(void *)GB_RefString,
	(void *)GB_FreeString,
	(void *)STRING_free_later,
	(void *)STRING_extend,
	(void *)STRING_add,
	(void *)STRING_add_char,
	(void *)GB_StringLength,
	(void *)GB_ToZeroString,
	(void *)REGEXP_match,
	(void *)NUMBER_from_string,
	(void *)GB_NumberToString,
	(void *)LOCAL_gettext,

	(void *)STRING_subst,
	(void *)STRING_subst_add,
	(void *)STRING_subst_add_unquote,
	(void *)STRING_make,
	(void *)GB_ConvString,
	(void *)STRING_conv_file_name,
	(void *)GB_RealFileName,

	(void *)GB_LoadFile,
	(void *)STREAM_unmap,
	(void *)GB_TempDir,
	(void *)GB_TempFile,
	(void *)GB_CopyFile,
	(void *)GB_BrowseProject,
	(void *)GB_BrowseDirectory,
	(void *)FILE_stat,

	(void *)GB_Store,
	(void *)GB_CheckString,
	(void *)GB_StoreString,
	(void *)GB_StoreObject,
	(void *)GB_StoreVariant,
	(void *)VALUE_read,
	(void *)GB_BorrowValue,
	(void *)GB_ReleaseValue,
	(void *)COMPARE_variant,

	(void *)GB_SplitDate,
	(void *)DATE_make_local,
	(void *)DATE_from_time,
	(void *)DATE_to_time,
	(void *)DATE_timer,
	(void *)DATE_from_string,

	(void *)GB_Watch,

	(void *)GB_Eval,

	(void *)GB_Alloc,
	(void *)GB_AllocZero,
	(void *)GB_Free,
	(void *)GB_Realloc,

	(void *)GB_NewArray,
	(void *)ARRAY_delete,
	(void *)GB_CountArray,
	(void *)GB_Add,
	(void *)ARRAY_insert_many,
	(void *)ARRAY_remove_many,

	(void *)GB_tolower,
	(void *)GB_toupper,
	(void *)strcasecmp,
	(void *)strncasecmp,

	(void *)GB_AppName,
	(void *)GB_AppTitle,
	(void *)GB_AppVersion,
	(void *)GB_AppPath,
	(void *)GB_AppStartupClass,

	(void *)GB_SystemCharset,
	(void *)LOCAL_get_lang,
	(void *)LOCAL_set_lang,
	(void *)GB_SystemDomainName,
	(void *)GB_IsRightToLeft,
	(void *)GB_SystemPath,
	(void *)GB_SystemHasForked,
	(void *)GB_SystemDebug,
	(void *)FILE_get_home,
	(void *)DATE_get_timezone,

	(void *)GB_ArrayNew,
	(void *)GB_ArrayCount,
	(void *)GB_ArrayAdd,
	(void *)GB_ArrayGet,
	(void *)GB_ArrayType,
	(void *)GB_ArraySetReadOnly,

	(void *)GB_CollectionNew,
	(void *)GB_CollectionCount,
	(void *)GB_CollectionSet,
	(void *)GB_CollectionGet,
	(void *)GB_CollectionEnum,
	(void *)GB_CollectionBrowse,

	(void *)GB_HashTableNew,
	(void *)HASH_TABLE_delete,
	(void *)HASH_TABLE_size,
	(void *)GB_HashTableAdd,
	(void *)GB_HashTableRemove,
	(void *)GB_HashTableGet,
	(void *)GB_HashTableEnum,
	(void *)GB_HashTableFirst,

	(void *)GB_StreamGet,
	(void *)GB_StreamSetSwapping,
	(void *)GB_StreamSetAvailableNow,
	(void *)GB_StreamBlock,
	(void *)GB_StreamRead,
	(void *)GB_StreamWrite,
	(void *)STREAM_get_readable,
	(void *)STREAM_eof,
	(void *)STREAM_handle,

	(void *)STRING_start_len,
	(void *)STRING_end,
	(void *)STRING_make,
	(void *)STRING_split,

	(void *)DEBUG_get_current_position,
	(void *)DEBUG_enter_event_loop,
	(void *)DEBUG_leave_event_loop,

	(void *)SIGNAL_register,
	(void *)SIGNAL_unregister,
	(void *)SIGNAL_must_check,

	(void *)LIST_insert,
	(void *)LIST_remove,

	NULL
};

const void *const GAMBAS_DebugApi[] =
{
	(void *)GB_DebugGetExec,
	(void *)STACK_get_frame,
	(void *)ERROR_get_message,
	(void *)ERROR_save,
	(void *)ERROR_restore,
	(void *)VALUE_to_local_string,
	(void *)LOCAL_format_date,
	(void *)LOCAL_format_number,
	(void *)DEBUG_get_value,
	(void *)DEBUG_set_value,
	(void *)CARRAY_get_value,
	(void *)DEBUG_enum_keys,
	(void *)CLASS_get_next_sorted_symbol,
	(void *)DEBUG_get_object_access_type,
	(void *)DEBUG_find_class,
	(void *)CARRAY_get_array_bounds,
	(void *)GB_DebugBreakOnError,
	(void *)DEBUG_enter_eval,
	(void *)DEBUG_leave_eval,
	(void *)GB_DebugInside,
	(void *)GB_DebugHold,
	NULL
};

const void *const GAMBAS_JitApi[] =
{
	(void *)&SP,
	(void *)&EXEC_current,
	(void *)&TEMP,
	(void *)&EXEC_super,
	(void *)JIT_debug,
	(void *)JIT_get_code,
	(void *)THROW,
	(void *)THROW_TYPE,
	(void *)JIT_get_constant,
	(void *)JIT_get_class_ref,
	(void *)EXEC_subr_table,
	(void *)STRING_char_table,
	(void *)EXEC_unborrow,
	(void *)EXEC_new,
	(void *)EXEC_push_array,
	(void *)EXEC_pop_array,
	(void *)VALUE_convert,
	(void *)EXEC_push_unknown,
	(void *)JIT_call_unknown,
	(void *)EXEC_pop_unknown,
	(void *)EXEC_enum_first,
	(void *)EXEC_enum_next,
	(void *)SYMBOL_find,
	(void *)JIT_load_class,
	(void *)JIT_load_class_without_init,
	(void *)&ERROR_current,
	(void *)&ERROR_handler,
	(void *)ERROR_reset,
	(void *)ERROR_set_last,
	(void *)EXEC_set_got_error,
	(void *)&EVENT_Last,
	(void *)EXEC_push_complex,
	(void *)EXEC_push_vargs,
	(void *)EXEC_drop_vargs,
	(void *)EXEC_quit,
	(void *)EXEC_push_unknown_event,
	(void *)EXTERN_get_addr,
	(void *)DEBUG_get_position,
	(void *)RELEASE_many,
	(void *)CSTRUCT_create_static,
	(void *)CARRAY_create_static,
	(void *)CARRAY_get_array_class,
	(void *)JIT_add_string_local,
	(void *)JIT_add_string_global,
	(void *)VALUE_class_write,
	(void *)SUBR_poke,
	(void *)OBJECT_get_addr,
	NULL
};


bool GAMBAS_DoNotRaiseEvent = FALSE;
bool GAMBAS_StopEvent = FALSE;
bool GAMBAS_RaiseEventCanPropagate = FALSE;

static bool _event_stopped = FALSE;
static int _raise_event_level = 0;

#define CATCH_ERROR \
	bool ret = FALSE; \
	TRY

#define END_CATCH_ERROR \
	CATCH \
	{ \
		ret = TRUE; \
	} \
	END_TRY \
	if (ret) EXEC_set_native_error(TRUE); \
	return ret;

#define CATCH_ERROR_INT \
	int ret = 0; \
	TRY

#define END_CATCH_ERROR_INT \
	CATCH \
	{ \
		ret = -1; \
	} \
	END_TRY \
	if (ret < 0) EXEC_set_native_error(TRUE); \
	return ret;

bool GB_GetInterface(const char *name, int version, void *iface)
{
	GB_LoadComponent(name);

	if (LIBRARY_get_interface_by_name(name, version, iface))
		ERROR_panic("Cannot find interface of library '%s'", name);

	return FALSE;
}


void *GB_Hook(int type, void *hook)
{
	void *old_hook;
	void **phook = (void **)(void *)&EXEC_Hook;

	if ((type < 0) || (type > GB_HOOK_MAX))
		return NULL;

	type--;
	old_hook = phook[type];
	if (hook)
		phook[type] = hook;

	type++;
	if (type == GB_HOOK_LOOP && !old_hook)
	{
		if (phook[GB_HOOK_WATCH])
			WATCH_transfer_watch();
		if (phook[GB_HOOK_TIMER])
			WATCH_transfer_timer();
	}
	
	return old_hook;
}


bool GB_HasActiveTimer(void)
{
	return CTIMER_active_count > 0;
}


bool GB_LoadComponent(const char *name)
{
	CATCH_ERROR
	{
		COMPONENT *comp = COMPONENT_create(name);
		COMPONENT_load(comp);
	}
	END_CATCH_ERROR
}


static void push(int nval, va_list args)
{
	TYPE type;
	GB_VARIANT_VALUE *val;

	STACK_check(nval);

	while (nval)
	{
		type = va_arg(args, int);
		SP->type = type;

		switch(type)
		{
			case T_BOOLEAN:
				SP->_integer.value = va_arg(args, int) ? -1 : 0;
				break;

			case T_INTEGER:
				SP->_integer.value = va_arg(args, int);
				break;

			case T_LONG:
				SP->_long.value = va_arg(args, int64_t);
				break;

			case T_STRING:
				SP->type = T_CSTRING;
				SP->_string.addr = va_arg(args, char *);
				SP->_string.start = 0;
				SP->_string.len = va_arg(args, int);
				if (SP->_string.len <= 0 && SP->_string.addr)
					SP->_string.len = strlen(SP->_string.addr);
				break;

			case T_FLOAT:
				SP->_float.value = va_arg(args, double);
				break;

			case T_OBJECT:
				SP->_object.object = va_arg(args, void *);
				OBJECT_REF_CHECK(SP->_object.object);
				break;

			case T_VARIANT:
				val = va_arg(args, GB_VARIANT_VALUE *);
				SP->_variant.vtype = val->type;
				SP->_variant.value.data = val->value.data;
				BORROW(SP);
				break;

			default:
				ERROR_panic("GB.Push: unknown datatype");
				break;
		}

		SP++;
		nval--;
	}
}


void GB_Push(int nval, ...)
{
	va_list args;

	va_start(args, nval);
	push(nval, args);
	va_end(args);
}

static void error(int code, CLASS *class, const char *name)
{
	GB_Error((char *)(intptr_t)code, CLASS_get_name(class), name);
}

static CLASS_DESC *get_desc(CLASS *class, const char *name)
{
	int index;

	index = CLASS_find_symbol(class, name);

	if (index == NO_SYMBOL)
	{
		error(E_NSYMBOL, class, name);
		return NULL;
	}
	else
		return class->table[index].desc;
}

void *GB_GetProperty(void *object, const char *name)
{
	CLASS_DESC *desc;
	CLASS *class;
	char type;

	//if (GB_CheckObject(object))
	//	return;

	if (OBJECT_is_class(object))
	{
		class = (CLASS *)object;
		object = NULL;
	}
	else
	{
		class = OBJECT_class(object);
	}

	desc = get_desc(class, name);

	if (!desc)
		return NULL;

	type = CLASS_DESC_get_type(desc);

	if (type == CD_PROPERTY || type == CD_PROPERTY_READ || type == CD_VARIABLE)
	{
		if (!object)
		{
			error(E_DYNAMIC, class, name);
			return NULL;
		}
	}
	else if (type == CD_STATIC_PROPERTY || type == CD_STATIC_PROPERTY_READ || type == CD_STATIC_VARIABLE || type == CD_CONSTANT)
	{
		if (object)
		{
			error(E_STATIC, class, name);
			return NULL;
		}
	}
	else if (type == CD_PROPERTY_WRITE || type == CD_STATIC_PROPERTY_WRITE)
	{
		error(E_NREAD, class, name);
		return NULL;
	}
	else
	{
		error(E_NPROPERTY, class, name);
		return NULL;
	}

	if (type == CD_VARIABLE)
		VALUE_read(&TEMP, (char *)object + desc->variable.offset, desc->property.type);
	else if (type == CD_STATIC_VARIABLE)
		VALUE_read(&TEMP, (char *)class->stat + desc->variable.offset, desc->property.type);
	else if (type == CD_CONSTANT)
		VALUE_read(&TEMP, (void *)&desc->constant.value, desc->constant.type);
	else
	{
		if (desc->property.native)
		{
			if (EXEC_call_native(desc->property.read, object, desc->property.type, 0))
			{
				EXEC_set_native_error(TRUE);
				return NULL;
			}
		}
		else
		{
			EXEC.class = desc->property.class;
			EXEC.object = object;
			EXEC.nparam = 0;
			EXEC.native = FALSE;
			EXEC.index = (int)(intptr_t)desc->property.read;
			//EXEC.func = &class->load->func[(long)desc->property.read];

			EXEC_function_keep();
			
			EXEC_move_ret_to_temp();
		}
	}

	return &TEMP;
}

bool GB_SetProperty(void *object, const char *name, GB_VALUE *val)
{
	VALUE *value = (VALUE *)val;
	CLASS_DESC *desc;
	CLASS *class;
	char type;

	if (OBJECT_is_class(object))
	{
		class = (CLASS *)object;
		object = NULL;
	}
	else
	{
		class = OBJECT_class(object);
	}

	desc = get_desc(class, name);

	if (!desc)
		return TRUE;

	type = CLASS_DESC_get_type(desc);

	if (type == CD_PROPERTY || type == CD_VARIABLE || type == CD_PROPERTY_WRITE)
	{
		if (!object)
		{
			if (!class->auto_create)
			{
				error(E_DYNAMIC, class, name);
				return TRUE;
			}

			object = EXEC_auto_create(class, TRUE);
		}
	}
	else if (type == CD_STATIC_PROPERTY || type == CD_STATIC_VARIABLE || type == CD_STATIC_PROPERTY_WRITE)
	{
		if (object)
		{
			error(E_STATIC, class, name);
			return TRUE;
		}
	}
	else if (type == CD_PROPERTY_READ  || type == CD_STATIC_PROPERTY_READ)
	{
		error(E_NWRITE, class, name);
		return TRUE;
	}
	else
	{
		error(E_NPROPERTY, class, name);
		return TRUE;
	}

	if (type == CD_VARIABLE)
		VALUE_write(value, (char *)object + desc->variable.offset, desc->variable.type);
	else if (type == CD_STATIC_VARIABLE)
		VALUE_write(value, (char *)class->stat + desc->variable.offset, desc->variable.type);
	else
	{
		if (desc->property.native)
		{
			VALUE_conv(value, desc->property.type);

			if (EXEC_call_native(desc->property.write, object, 0, value))
			{
				EXEC_set_native_error(TRUE);
				return TRUE;
			}
		}
		else
		{
			*SP = *value;
			BORROW(SP);
			SP++;

			EXEC.class = desc->property.class;
			EXEC.object = object;
			EXEC.nparam = 1;
			EXEC.native = FALSE;
			EXEC.index = (int)(intptr_t)desc->property.write;
			//EXEC.func = &class->load->func[(long)desc->property.write];

			EXEC_function();

			/*VALUE_write(value, OBJECT_get_prop_addr(object, desc), desc->property.type);*/
		}
	}

	return FALSE;
}


bool GB_CanRaise(void *object, int event_id)
{
	ushort *event_tab;
	int func_id;
	COBSERVER *obs;

	if (!object || !OBJECT_has_events(object))
		return FALSE;

	LIST_for_each(obs, OBJECT_event(object)->observer)
	{
		if (OBJECT_parent(obs) && obs->event && obs->event[event_id])
			return TRUE;
	}

	if (!OBJECT_parent(object))
		return FALSE;

	event_tab = OBJECT_event(object)->event;
	func_id = event_tab[event_id];

	return (func_id != 0);
}

bool GB_IsRaiseLocked(void *object)
{
	COBSERVER *obs;

	if (GAMBAS_DoNotRaiseEvent)
		return TRUE;
	
	if (!object || !OBJECT_has_events(object))
		return TRUE;

	LIST_for_each(obs, OBJECT_event(object)->observer)
	{
		if (OBJECT_active_parent(obs))
			return FALSE;
	}
	
	return OBJECT_active_parent(object) == NULL;
}


static int get_event_func_id(ushort *event_tab, int event_id)
{
	int func_id;

	if (!event_tab)
		return 0;

	func_id = event_tab[event_id];
	if (!func_id)
		return 0;

	return func_id;
}

static bool raise_event(OBJECT *observer, void *object, int func_id, int nparam, bool can_propagate)
{
	bool stop_event;
	CLASS *class;
	CLASS_DESC_METHOD *desc;
	void *old_last;
	bool result;
	uint stack_barrier;

	func_id--;

	if (OBJECT_is_class(observer))
	{
		class = (CLASS *)observer; //OBJECT_class(object);
		observer = NULL;
	}
	else
		class = OBJECT_class(observer);

	desc = &class->table[func_id].desc->method;

	old_last = EVENT_Last;
	EVENT_Last = object;
	//OBJECT_REF(object, "raise_event");

// 	if (arg)
// 	{
// 		EXEC_dup(nparam);
// 	}
// 	else
// 	{
// 		va_start(args, nparam);
// 		push(nparam, args);
// 		va_end(args);
// 	}

	stop_event = GAMBAS_StopEvent;
	GAMBAS_StopEvent = FALSE;

	stack_barrier = STACK_push_barrier();
	
	TRY
	{
		EXEC_public_desc(class, observer, desc, nparam);
	}
	CATCH 
	{
		// Errors cannot be propagated outside of an event handler, because 
		// the event may result from a library signal that do not support 
		// exceptions (like the glib library).
		
		if (ERROR->info.code && ERROR->info.code != E_ABORT && !(can_propagate && STACK_has_error_handler()))
		{
			ERROR_hook();

			if (FLAG.debug)
			{
				DEBUG.Main(TRUE);
				MAIN_exit(TRUE, 0);
			}
			else
			{
				if (!ERROR_print(TRUE))
					MAIN_exit(TRUE, 1);
			}
		}
		else
		{
			EVENT_Last = old_last;
			GAMBAS_StopEvent = stop_event;
			STACK_pop_barrier(stack_barrier);
			PROPAGATE();
		}
	}
	END_TRY
	
	if (RP->type == T_VOID)
		result = FALSE;
	else
		result = RP->_boolean.value != 0;

	if (GAMBAS_StopEvent)
		result = TRUE;

	EVENT_Last = old_last;
	GAMBAS_StopEvent = stop_event;
	STACK_pop_barrier(stack_barrier);

	EXEC_release_return_value();

	return result;
}

// If nparam < 0, the args are already on the stack

static GB_RAISE_HANDLER *_GB_Raise_handler = NULL;

static void error_GB_Raise(void *object, intptr_t nparam)
{
	RELEASE_MANY(SP, (int)nparam);
	OBJECT_UNREF(object);

	_raise_event_level--;

	if (_GB_Raise_handler && _GB_Raise_handler->level == _raise_event_level)
	{
		(*_GB_Raise_handler->callback)(_GB_Raise_handler->data);
		_GB_Raise_handler = _GB_Raise_handler->old;
	}
}

void GB_RaiseBegin(GB_RAISE_HANDLER *handler)
{
	handler->old = _GB_Raise_handler;
	handler->level = _raise_event_level;
	_GB_Raise_handler = handler;
}

void GB_RaiseEnd(GB_RAISE_HANDLER *handler)
{
	_GB_Raise_handler = handler->old;
}

bool GB_RaiseEvent(void *object, int event_id, int nparam, ...)
{
	OBJECT *parent;
	int func_id;
	bool result;
	va_list args;
	bool arg;
	COBSERVER *obs;
	bool can_propagate;

	can_propagate = GAMBAS_RaiseEventCanPropagate;
	GAMBAS_RaiseEventCanPropagate = FALSE;
	
	if (GAMBAS_DoNotRaiseEvent)
		return FALSE;

	if (!OBJECT_is_valid(object) || !OBJECT_has_events(object))
		return FALSE;

	OBJECT_REF(object);

	arg = nparam < 0;
	nparam = abs(nparam);

	_raise_event_level++;

	ON_ERROR_2(error_GB_Raise, object, nparam)
	{
		if (!arg)
		{
			va_start(args, nparam);
			push(nparam, args);
			va_end(args);
		}

		result = FALSE;

		// Observers before

		LIST_for_each(obs, OBJECT_event(object)->observer)
		{
			parent = OBJECT_active_parent(obs);
			if (!parent)
				continue;
			if (obs->after)
				continue;
			//if (obs->object != object)
			//	continue;

			func_id = get_event_func_id(obs->event, event_id);
			if (!func_id)
				continue;

			if (!OBJECT_is_valid(parent))
			{
				OBJECT_detach((OBJECT *)obs);
				continue;
			}

			EXEC_dup(nparam);
			result = raise_event(parent, object, func_id, nparam, can_propagate);

			if (result)
				goto __RETURN;
		}

		// Parent

		parent = OBJECT_active_parent(object);
		if (parent)
		{
			func_id = get_event_func_id(OBJECT_event(object)->event, event_id);
			#if DEBUG_EVENT
				CLASS *class = OBJECT_class(object);
				fprintf(stderr, "GB_Raise(%p, %d, %s)\n", object, event_id, class->event[event_id].name);
				fprintf(stderr, "func_id = %d  parent = (%s %p)\n", func_id, parent->class->name, parent);
				if (OBJECT_is_locked(parent))
					fprintf(stderr, "parent is locked!\n");
			#endif
			if (func_id)
			{
				if (!OBJECT_is_valid(parent))
					OBJECT_detach(object);
				else
				{
					EXEC_dup(nparam);
					result = raise_event(parent, object, func_id, nparam, can_propagate);
					if (result)
						goto __RETURN;
				}
			}
		}

		// Observers after

		LIST_for_each(obs, OBJECT_event(object)->observer)
		{
			parent = OBJECT_active_parent(obs);
			if (!parent)
				continue;
			if (!obs->after)
				continue;
			//if (obs->object != object)
			//	continue;

			func_id = get_event_func_id(obs->event, event_id);
			if (!func_id)
				continue;

			if (!OBJECT_is_valid(parent))
			{
				OBJECT_detach((OBJECT *)obs);
				continue;
			}

			EXEC_dup(nparam);
			result = raise_event(parent, object, func_id, nparam, can_propagate);
			if (result)
				goto __RETURN;
		}

__RETURN:

		RELEASE_MANY(SP, nparam);
		OBJECT_UNREF(object);
	}
	END_ERROR

	_raise_event_level--;

	return result;
}


bool GB_GetFunction(GB_FUNCTION *func, void *object, const char *name, const char *sign, const char *type)
{
	char len_min, nparam, npvar;
	TYPE *tsign;
	TYPE tret;
	int index;
	CLASS *class;
	int kind;
	CLASS_DESC *desc;
	bool error;
	const char *err;

	if (OBJECT_is_class(object))
	{
		class = (CLASS *)object;
		kind = CD_STATIC_METHOD;
	}
	else
	{
		class = OBJECT_class(object);
		kind = CD_METHOD;
	}

	CLASS_load(class);

	index = CLASS_find_symbol(class, name);
	if (index == NO_SYMBOL)
	{
		err = "Symbol not found";
		goto _NOT_FOUND;
	}

	desc = class->table[index].desc;
	if (CLASS_DESC_get_type(desc) != kind)
	{
		err = kind == CD_METHOD ? "Not a method" : "Not a static method";
		goto _NOT_FOUND;
	}

	if (sign)
	{
		TYPE_signature_length(sign, &len_min, &nparam, &npvar);
		tsign = NULL;

		if (nparam)
		{
			ALLOC(&tsign, nparam * sizeof(TYPE));
			tsign = TYPE_transform_signature(&tsign, sign, nparam);
		}

		error = TYPE_compare_signature(desc->method.signature, desc->method.npmax, tsign, nparam, FALSE);

		if (nparam)
			FREE(&tsign);

		if (error)
		{
			err = "Parameters do not match";
			goto _NOT_FOUND;
		}
	}

	if (type)
	{
		tret = TYPE_from_string(&type);
		if (tret != desc->method.type)
		{
			if (tret == T_VOID)
				err = "Must be a procedure";
			else if (desc->method.type == T_VOID)
				err = "Must be a function";
			else
				err = "Return type does not match";

			goto _NOT_FOUND;
		}
	}

	func->object = object;
	func->index = index + 1;

	return FALSE;

_NOT_FOUND:

	GB_Error("Unable to find method &1 in class &2. &3", name, CLASS_get_name(class), err);
	func->object = NULL;
	func->index = 0;
	return TRUE;
}


void *GB_GetClassInterface(void *_class, const char *_name)
{
	CLASS_DESC *desc;
	int index;
	CLASS *class = (CLASS *)_class;
	int len = strlen(_name);
	char name[len + 4];

	CLASS_load(class);

	strcpy(name, "_@");
	strcat(name, _name);

	index = CLASS_find_symbol(class, name);
	if (index == NO_SYMBOL)
		goto __NOT_FOUND;

	desc = class->table[index].desc;
	if (CLASS_DESC_get_type(desc) != CD_CONSTANT)
		goto __NOT_FOUND;

	if (desc->constant.type != T_POINTER)
		goto __NOT_FOUND;

	return desc->constant.value._pointer;

__NOT_FOUND:
	return NULL;
}


GB_VALUE *GB_Call(GB_FUNCTION *func, int nparam, int release)
{
	bool stop_event;
	CLASS *class;
	void *object;
	CLASS_DESC_METHOD *desc;

	if (!func || !func->index)
		GB_Error("Unknown function call");
		//TEMP.type = GB_T_NULL;
	else
	{
		object = func->object;
		if (OBJECT_is_class(object))
		{
			class = object;
			object = NULL;
		}
		else
		{
			class = OBJECT_class(object);
		}
		
		desc = &class->table[func->index - 1].desc->method;
		
		stop_event = GAMBAS_StopEvent;
		EXEC_public_desc(class, object, desc, nparam);
		_event_stopped = GAMBAS_StopEvent;
		GAMBAS_StopEvent = stop_event;

		if (release)
			EXEC_release_return_value();
		else
			EXEC_move_ret_to_temp();
	}

	return (GB_VALUE *)&TEMP;
}


int GB_GetEvent(void *class, char *name)
{
	CLASS_DESC_EVENT *cd;

	cd = CLASS_get_event_desc((CLASS *)class, name);
	if (!cd)
		return (-1);
	else
		return cd->index;
}


char *GB_GetLastEventName()
{
	return EVENT_Name;
}


bool GB_Stopped(void)
{
	return _event_stopped;
}


int GB_NParam(void)
{
	return EXEC_unknown_nparam;
}


const char *GB_GetUnknown(void)
{
	return EXEC_unknown_name;
}


void GB_OnErrorBegin(GB_ERROR_HANDLER *handler)
{
	handler->prev = ERROR_handler;
	handler->context = ERROR_current;
	ERROR_handler = (ERROR_HANDLER *)handler;
}

void GB_OnErrorEnd(GB_ERROR_HANDLER *handler)
{
	ERROR_handler = (ERROR_HANDLER *)handler->prev;
}

void GB_Error(const char *error, ...)
{
	va_list args;
	char *arg[4];
	int i;

	if (!error)
	{
		EXEC_set_native_error(FALSE);
		return;
	}

	va_start(args, error);

	for (i = 0; i < 4; i++)
		arg[i] = va_arg(args, char *);

	va_end(args);

	ERROR_define(error, arg);
	EXEC_set_native_error(TRUE);
}


bool GB_HasError()
{
	return EXEC_has_native_error();
}


char *GB_GetErrorMessage()
{
	return ERROR_last.msg;
}


void GB_Deprecated(const char *msg, const char *func, const char *repl)
{
	if (FLAG.debug)
	{
		if (repl)
			GB_Error("&1: &2 is deprecated. Use &3 instead", msg, func, repl);
		else
			GB_Error("&1: &2 is deprecated. Do not use it anymore", msg, func);
	}
	else
	{
		DEBUG_where();
		if (repl)
			fprintf(stderr, "%s: %s is deprecated. Use %s instead\n", msg, func, repl);
		else
			fprintf(stderr, "%s: %s is deprecated. Do not use it anymore\n", msg, func);
	}
}

#if DEBUG_REF

#include <execinfo.h>

static void print_stack_backtrace()
{
	void *trace[16];
	char **messages = (char **)NULL;
	int i, trace_size = 0;

	trace_size = backtrace(trace, 16);
	messages = backtrace_symbols(trace, trace_size);
	//printf("[bt] Execution path:\n");
	fprintf(stderr, "[ ");
	for (i=0; i<trace_size; ++i)
		fprintf(stderr, "%s ", messages[i]);
	fprintf(stderr, "] ");
}

#endif


void GB_Ref(void *object)
{
	#if TRACE_MEMORY
	CLASS *save = CP;
	CP = NULL;
	#endif

	if (object)
	{
		#if DEBUG_REF
		print_stack_backtrace();
		#endif
		OBJECT_REF(object);
	}

	#if TRACE_MEMORY
	CP = save;
	#endif
}


void GB_Unref(void **object)
{
	#if TRACE_MEMORY
	CLASS *save = CP;
	CP = NULL;
	#endif

	if (*object)
	{
		#if DEBUG_REF
		print_stack_backtrace();
		#endif
		OBJECT_UNREF(*object);
	}

	#if TRACE_MEMORY
	CP = save;
	#endif
}


/*void GB_UnrefKeep(void **object, int delete)
{
	#if TRACE_MEMORY
	CLASS *save = CP;
	CP = NULL;
	#endif

	if (*object != NULL)
	{
		#if DEBUG_REF
		//print_stack_backtrace();
		#endif
		if (delete)
		{
			OBJECT_UNREF(*object);
		}
		else
		{
			OBJECT_UNREF_KEEP(*object);
		}
	}

	#if TRACE_MEMORY
	CP = save;
	#endif
}*/


void GB_Detach(void *object)
{
	if (object)
	{
		#if DEBUG_REF
		//print_stack_backtrace();
		#endif
		OBJECT_detach(object);
	}
}


void GB_Attach(void *object, void *parent, const char *name)
{
	if (object)
	{
		#if DEBUG_REF
		//print_stack_backtrace();
		#endif
		OBJECT_attach(object, parent, name);
	}
}


void GB_StopEnum(void)
{
	/* Do not forget than even if we stop the enumeration, the return value
		of _next will be converted
	*/
	//VALUE_default(&TEMP, *GAMBAS_ReturnType);
	TEMP.type = T_VOID;
	EXEC_enum->stop = TRUE;
}

void GB_OnFreeEnum(void (*cb)(void *))
{
	EXEC_enum->free = cb;
}


void *GB_GetEnum(void)
{
	return (void *)&EXEC_enum->data;
}


static void *_enum_object;

void *GB_BeginEnum(void *enum_object)
{
	CENUM *old = EXEC_enum;
	EXEC_enum = NULL;
	_enum_object = enum_object;
	return old;
}

void GB_EndEnum(void *old_enum)
{
	EXEC_enum = old_enum;
}


bool GB_NextEnum(void)
{
	for(;;)
	{
		EXEC_enum = CENUM_get_next(EXEC_enum);
		if (!EXEC_enum)
			return TRUE;
		if (EXEC_enum->enum_object == _enum_object && !EXEC_enum->stop)
			return FALSE;
	}
}

void GB_StopAllEnum(void *enum_object)
{
	void *save = GB_BeginEnum(enum_object);
	while (!GB_NextEnum())
		GB_StopEnum();
	GB_EndEnum(save);
}

GB_VALUE *GB_GetReturnValue(void)
{
	return (GB_VALUE *)&TEMP;
}

void GB_Return(GB_TYPE type, ...)
{
	static void *jump[16] = {
		&&__VOID, &&__BOOLEAN, &&__BYTE, &&__SHORT, &&__INTEGER, &&__LONG, &&__SINGLE, &&__FLOAT, &&__DATE,
		&&__STRING, &&__STRING, &&__POINTER, &&__VARIANT, &&__FUNCTION, &&__CLASS, &&__NULL
		};

	VALUE *ret = &TEMP;
	va_list args;

	va_start(args, type);

	ret->type = type;
	if (TYPE_is_object(type))
		goto __OBJECT;
	else
		goto *jump[type];

__BOOLEAN:

	ret->_integer.value = va_arg(args, int) ? (-1) : 0;
	goto __CONV;

__BYTE:

	ret->_integer.value = va_arg(args, int);
	goto __CONV;

__SHORT:

	ret->_integer.value = va_arg(args, int);
	goto __CONV;

__INTEGER:

	ret->_integer.value = va_arg(args, int);
	goto __CONV;

__LONG:

	ret->_long.value = va_arg(args, int64_t);
	goto __CONV;

__SINGLE:

	ret->_single.value = va_arg(args, double);
	goto __CONV;

__FLOAT:

	ret->_float.value = va_arg(args, double);
	goto __CONV;

__DATE:

	ret->_date.date = va_arg(args, int);
	ret->_date.time = va_arg(args, int);
	goto __CONV;

__OBJECT:

	ret->_object.object = va_arg(args, void *);
	goto __CONV;

__POINTER:

	ret->_pointer.value = va_arg(args, void *);
	goto __CONV;

__CLASS:
	ret->_class.class = va_arg(args, CLASS *);
	goto __CONV;

__CONV:
__STRING:
__VOID:
__VARIANT:
__FUNCTION:
__NULL:

	va_end(args);
	return;
}


void GB_ReturnInteger(int val)
{
	TEMP.type = T_INTEGER;
	TEMP._integer.value = val;
}


void GB_ReturnLong(int64_t val)
{
	TEMP.type = T_LONG;
	TEMP._long.value = val;
}


void GB_ReturnPointer(void *val)
{
	TEMP.type = T_POINTER;
	TEMP._pointer.value = val;
}


void GB_ReturnSingle(float val)
{
	TEMP.type = T_SINGLE;
	TEMP._single.value = val;
}

void GB_ReturnFloat(double val)
{
	TEMP.type = T_FLOAT;
	TEMP._float.value = val;
}


void GB_ReturnDate(GB_DATE *date)
{
	TEMP.type = T_DATE;
	if (date)
	{
		TEMP._date.date = date->value.date;
		TEMP._date.time = date->value.time;
	}
	else
	{
		TEMP._date.date = 0;
		TEMP._date.time = 0;
	}
}


void GB_ReturnBoolean(int val)
{
	TEMP.type = T_BOOLEAN;
	TEMP._boolean.value = val ? -1 : 0;
}


void GB_ReturnObject(void *val)
{
	if (val == NULL)
		GB_ReturnNull();
	else
	{
		TEMP.type = T_OBJECT; //OBJECT_class(val);
		TEMP._object.object = val;
	}
}


void GB_ReturnVariant(GB_VARIANT_VALUE *val)
{
	TEMP._variant.type = T_VARIANT;

	if (!val)
	{
		TEMP._variant.vtype = T_NULL;
	}
	else
	{
		//VALUE_read(&TEMP, value, type);
		TEMP._variant.vtype = val->type;
		if (TEMP._variant.vtype == T_VOID)
			TEMP._variant.vtype = T_NULL;

		VARIANT_copy_value(&TEMP._variant, (VARIANT *)val);
	}
}


void GB_ReturnConvVariant(void)
{
	BORROW(&TEMP);
	VALUE_conv(&TEMP, T_VARIANT);
	UNBORROW(&TEMP);
}

void GB_ReturnBorrow(void)
{
	BORROW(&TEMP);
}

void GB_ReturnRelease(void)
{
	UNBORROW(&TEMP);
}

void GB_ReturnPtr(GB_TYPE type, void *value)
{
	if (type == T_VOID)
		return;

	if (!value)
		VALUE_default(&TEMP, type);
	else
		VALUE_read(&TEMP, value, type);
}


void GB_ReturnSelf(void *object)
{
	if (object)
	{
		TEMP.type = T_OBJECT;
		TEMP._object.object = object;
	}
	else
	{
		TEMP.type = T_CLASS;
		TEMP._class.class = NULL;
	}
}


char *GB_ToZeroString(GB_STRING *src)
{
	char *str;

	str = STRING_new_temp(src->value.addr + src->value.start, src->value.len);

	if (str == NULL)
		return "";
	else
		return str;
}


void GB_ReturnString(char *str)
{
	TEMP.type = T_STRING;
	TEMP._string.addr = str;
	TEMP._string.start = 0;
	TEMP._string.len = STRING_length(str);

	if (TEMP._string.len == 0)
		TEMP._string.addr = 0;
}


void GB_ReturnConstString(const char *str, int len)
{
	TEMP.type = T_CSTRING;
	TEMP._string.addr = (char *)str;
	TEMP._string.start = 0;
	TEMP._string.len = len;

	if (TEMP._string.len == 0)
		TEMP._string.addr = 0;
}


void GB_ReturnConstZeroString(const char *str)
{
	int len;

	if (str)
		len = strlen(str);
	else
		len = 0;

	GB_ReturnConstString(str, len);
}


void GB_ReturnNewString(const char *src, int len)
{
	if (len <= 0)
	{
		if (src && *src)
		{
			ERROR_warning("please use GB.ReturnNewZeroString() instead of GB.ReturnNewString()");
			len = strlen(src);
		}
		else
		{
			GB_ReturnVoidString();
			return;
		}
	}

	GB_ReturnString(STRING_new_temp(src, len));
}


void GB_ReturnNewZeroString(const char *src)
{
	GB_ReturnString(STRING_new_temp_zero(src));
}


void GB_ReturnNull(void)
{
	VALUE_null(&TEMP);
}

void GB_ReturnVoidString(void)
{
	STRING_void_value(&TEMP);
}


void *GB_GetClass(void *object)
{
	if (object)
		return OBJECT_class(object);
	else
		return EXEC.class;
}


char *GB_GetClassName(void *object)
{
	CLASS *class = GB_GetClass(object);
	return class == CLASS_Class ? CLASS_get_name((CLASS *)object) : CLASS_get_name(class);
}


bool GB_Is(void *object, void *class)
{
	CLASS *ob_class;

	if (!object)
		return FALSE;

	ob_class = OBJECT_class(object);

	return ((ob_class == class) || CLASS_inherits(ob_class, class));
}


bool GB_LoadFile(const char *path, int lenp, char **addr, int *len)
{
	bool ret = FALSE;

	//fprintf(stderr, "GB_LoadFile: %.*s\n", lenp ? lenp : strlen(path), path);

	TRY
	{
		*addr = 0;
		*len = 0;

		STREAM_map(STRING_conv_file_name(path, lenp), addr, len);
	}
	CATCH
	{
		if (*addr)
			STREAM_unmap(*addr, *len);

		ret = TRUE;
	}
	END_TRY

	if (ret)
		EXEC_set_native_error(TRUE);
	
	return ret;
}


bool GB_ExistFile(const char *path)
{
	return FILE_exist(path);
}


void GB_Store(GB_TYPE type, GB_VALUE *src, void *dst)
{
	if (src != NULL)
		VALUE_write((VALUE *)src, dst, type);
	else
		VALUE_free(dst, type);
}


void GB_StoreString(GB_STRING *src, char **dst)
{
	STRING_unref(dst);
	if (src)
	{
		*dst = STRING_new(src->value.addr + src->value.start, src->value.len);
	}
	else
		*dst = NULL;
}

bool GB_CheckString(GB_STRING *src, char *dst)
{
	if (src && src->value.len == 0)
		src = NULL;

	if (src && dst)
		return !STRING_equal(src->value.addr + src->value.start, src->value.len, dst, STRING_length(dst));
	else
		return src != NULL || dst != NULL;
}

void GB_StoreObject(GB_OBJECT *src, void **dst)
{
	void *object;

	if (src)
		object = src->value;
	else
		object = NULL;

	OBJECT_REF_CHECK(object);
	OBJECT_UNREF(*dst);
	*dst = object;
}

void GB_StoreVariant(GB_VARIANT *src, void *dst)
{
	/*GB_Store(GB_T_VARIANT, (GB_VALUE *)src, dst);*/
	if (src)
	{
		VALUE_write((VALUE *)src, dst, T_VARIANT);

		/*VARIANT_keep((VARIANT *)&src->value);
		VARIANT_free((VARIANT *)dst);
		*((VARIANT *)dst) = *((VARIANT *)&src->value);*/
	}
	else
	{
		VARIANT_free((VARIANT *)dst);
		((VARIANT *)dst)->type = T_NULL;
	}
}

void GB_BorrowValue(GB_VALUE *value)
{
	BORROW((VALUE *)value);
}

void GB_ReleaseValue(GB_VALUE *value)
{
	RELEASE((VALUE *)value);
}

void GB_Watch(int fd, int flag, void *callback, intptr_t param)
{
	HOOK_DEFAULT(watch, WATCH_watch)(fd, flag, callback, param);
}


void *GB_Create(void *class, const char *name, void *parent)
{
	void *object;

	object = OBJECT_new(class, name, parent);
	OBJECT_UNREF_KEEP(object);

	return object;
}

void *GB_New(void *class, const char *name, void *parent)
{
	if (name && !parent)
	{
		parent = OP;
		if (!parent)
			parent = CP;
	}

	if (!((CLASS *)class)->no_create)
	{
		int nparam = 0;
		if (!name && parent)
		{
			nparam = (int)(intptr_t)parent;
			parent = NULL;
		}

		return OBJECT_create(class, name, parent, nparam);
	}
	else
		return GB_Create(class, name, parent);
}

bool GB_CheckObject(void *object)
{
	CLASS *class;

	if (object == NULL)
	{
		GB_Error((char *)E_NULL);
		return TRUE;
	}

	class = OBJECT_class(object);

	if (class->check && (*(class->check))(object))
	{
		GB_Error((char *)E_IOBJECT);
		return TRUE;
	}

	return FALSE;
}


const char *GB_AppName(void)
{
	return PROJECT_name;
}

const char *GB_AppPath(void)
{
	return PROJECT_path;
}

const char *GB_AppTitle(void)
{
	return LOCAL_gettext(PROJECT_title);
}

const char *GB_AppVersion(void)
{
	return PROJECT_version;
}

void *GB_AppStartupClass(void)
{
	return PROJECT_class;
}


void *GB_Eval(void *expr, void *func)
{
	bool err = EVAL_expression((EXPRESSION *)expr, (EVAL_FUNCTION)func);

	EXEC_set_native_error(err);

	if (err)
		return NULL;
	else
		return &TEMP;
}


void GB_Alloc(void **addr, int len)
{
	ALLOC(addr, len);
}

void GB_AllocZero(void **addr, int len)
{
	ALLOC_ZERO(addr, len);
}

void GB_Free(void **addr)
{
	FREE(addr);
}

void GB_Realloc(void **addr, int len)
{
	REALLOC(addr, len);
}


bool GB_Conv(GB_VALUE *arg, GB_TYPE type)
{
	CATCH_ERROR
	{
		VALUE_conv((VALUE *)arg, (GB_TYPE)type);
	}
	END_CATCH_ERROR
}


DATE_SERIAL *GB_SplitDate(GB_VALUE *arg)
{
	return DATE_split((VALUE *)arg);
}

/*bool GB_MakeDate(DATE_SERIAL *date, GB_DATE *arg, bool local)
{
	return DATE_make_local(date, (VALUE *)arg, local);
}*/


int GB_StringLength(const char *str)
{
	return STRING_length(str);
}


bool GB_NumberToString(int local, double value, const char *format, char **str, int *len)
{
	return
		LOCAL_format_number
		(
			value,
			format ? GB_LF_USER : GB_LF_GENERAL_NUMBER,
			format,
			format ? strlen(format) : 0,
			str, len, local != 0
		);
}


void GB_HashTableNew(GB_HASHTABLE *hash, int mode)
{
	HASH_TABLE_create((HASH_TABLE **)hash, sizeof(void *), mode);
}

void GB_HashTableAdd(GB_HASHTABLE hash, const char *key, int len, void *data)
{
	if (len <= 0)
		len = strlen(key);

	*((void **)HASH_TABLE_insert((HASH_TABLE *)hash, key, len)) = data;
}

void GB_HashTableRemove(GB_HASHTABLE hash, const char *key, int len)
{
	if (len <= 0)
		len = strlen(key);

	HASH_TABLE_remove((HASH_TABLE *)hash, key, len);
}

bool GB_HashTableGet(GB_HASHTABLE hash, const char *key, int len, void **data)
{
	void **pdata;

	if (len <= 0)
		len = strlen(key);

	pdata = (void **)HASH_TABLE_lookup((HASH_TABLE *)hash, key, len, FALSE);
	if (pdata)
	{
		*data = *pdata;
		return FALSE;
	}
	else
		return TRUE;
}

void GB_HashTableEnum(GB_HASHTABLE hash, GB_HASHTABLE_ENUM_FUNC func)
{
	HASH_ENUM iter;
	void **pdata;

	CLEAR(&iter);

	for(;;)
	{
		pdata = (void **)HASH_TABLE_next((HASH_TABLE *)hash, &iter, FALSE);
		if (!pdata)
			break;

		(*func)(*pdata);
	}
}

bool GB_HashTableFirst(GB_HASHTABLE hash, void **data)
{
	HASH_ENUM iter;
	void **pdata;

	CLEAR(&iter);
	pdata = (void **)HASH_TABLE_next((HASH_TABLE *)hash, &iter, FALSE);
	if (pdata)
	{
		*data = *pdata;
		return FALSE;
	}
	else
		return TRUE;
}

void GB_NewArray(void *pdata, int size, int count)
{
	ARRAY_create_with_size(pdata, size, 16);
	if (count)
		ARRAY_add_data(pdata, count, TRUE);
}


int GB_CountArray(void *data)
{
	return ARRAY_count(data);
}


void *GB_Add(void *pdata)
{
	return ARRAY_add_void_size((char **)pdata);
}

char *GB_NewZeroString(char *src)
{
	return STRING_new_zero(src);
}

char *GB_TempString(char *src, int len)
{
	return STRING_new_temp(src, len);
}

char *GB_RefString(char *str)
{
	STRING_ref(str);
	return str;
}

void GB_FreeString(char **str)
{
	STRING_unref(str);
	*str = NULL;
}

bool GB_ConvString(char **result, const char *str, int len, const char *src, const char *dst)
{
	CATCH_ERROR
	{
		STRING_conv(result, str, len, src, dst, TRUE);
	}
	END_CATCH_ERROR
}


char *GB_SystemCharset(void)
{
	return LOCAL_is_UTF8 ? "UTF-8" : LOCAL_encoding;
}

char *GB_SystemDomainName(void)
{
	char host[256], domain[256];

	if (gethostname(host, sizeof(host) - 1))
		return "";

	if (getdomainname(domain, sizeof(domain) - 1))
		return "";

	if ((strlen(host) + strlen(domain)) > COMMON_BUF_MAX)
		return "";

	*COMMON_buffer = 0;
	if (*domain && strcmp(domain, "(none)"))
	{
		strcpy(COMMON_buffer, domain);
		strcat(COMMON_buffer, ".");
	}
	strcat(COMMON_buffer, host);

	return COMMON_buffer;
}

bool GB_IsRightToLeft(void)
{
	return LOCAL_local.rtl;
}

char *GB_SystemPath(void)
{
	return PROJECT_exec_path;
}

/*void GB_StreamInit(GB_STREAM *stream, int fd)
{
	STREAM *s = (STREAM *)stream;

	s->type = &STREAM_direct;
	s->direct.fd = fd;
}*/

GB_STREAM *GB_StreamGet(void *object)
{
	return (GB_STREAM *)CSTREAM_TO_STREAM(object);
}

void GB_StreamSetSwapping(GB_STREAM *stream, int v)
{
	((STREAM *)stream)->common.swap = v;
}

void GB_StreamSetAvailableNow(GB_STREAM *stream, int v)
{
	((STREAM *)stream)->common.available_now = v;
}

bool GB_StreamBlock(GB_STREAM *stream, int block)
{
	STREAM *st = (STREAM *)stream;
	bool old = STREAM_is_blocking(st);
	STREAM_blocking(st, block);
	return old;
}

int GB_StreamRead(GB_STREAM *stream, void *addr, int len)
{
	CATCH_ERROR_INT
	{
		if (len < 0)
			ret = STREAM_read_max((STREAM *)stream, addr, -len);
		else
			ret = STREAM_read((STREAM *)stream, addr, len);
	}
	END_CATCH_ERROR_INT
}

int GB_StreamWrite(GB_STREAM *stream, void *addr, int len)
{
	CATCH_ERROR_INT
	{
		STREAM_write((STREAM *)stream, addr, len);
		ret = len;
	}
	END_CATCH_ERROR_INT
}

int GB_tolower(int c)
{
	return tolower(c);
}

int GB_toupper(int c)
{
	return toupper(c);
}

char *GB_TempDir(void)
{
	return FILE_make_temp(NULL, NULL);
}

char *GB_TempFile(const char *pattern)
{
	int len;
	return FILE_make_temp(&len, pattern);
}

bool GB_CopyFile(const char *src, const char *dst)
{
	CATCH_ERROR
	{
		FILE_copy(src, dst);
	}
	END_CATCH_ERROR
}

#if 0
int GB_FindFile(const char *dir, int recursive, int follow, void (*found)(const char *))
{
	int ret = 0;
	char *pattern;
	int len_pattern;
	char *str;

	TRY
	{
		if (recursive)
			FILE_recursive_dir(dir, found, NULL, 0, follow);
		else
		{
			FILE_dir_first(dir, NULL, 0);
			while (!FILE_dir_next(&pattern, &len_pattern))
			{
				if (!LOCAL_is_UTF8)
				{
					if (STRING_conv(&str, pattern, len_pattern, LOCAL_encoding, SC_UTF8, FALSE))
						STRING_new(&str, pattern, len_pattern);
					else
						STRING_ref(str);
				}
				else
					STRING_new(&str, pattern, len_pattern);

				(*found)(str);
				STRING_unref(&str);
			}
		}
	}
	CATCH
	{
		ret = 1;
		GAMBAS_Error = TRUE;
	}
	END_TRY;

	return ret;
}
#endif

char *GB_RealFileName(const char *name, int len)
{
	char *path = STRING_conv_file_name(name, len);
	char *real;
	char *temp;

	if (!STREAM_in_archive(path))
		return path;

	temp = FILE_make_temp(NULL, NULL);
	real = STRING_new_temp(NULL, strlen(temp) + strlen(path) + strlen("/data/"));
	snprintf(real, strlen(temp) + strlen(path) + strlen("/data/") + 1, "%s/data/%s", temp, path);

	if (!FILE_exist(real))
	{
		TRY
		{
			FILE_make_path_dir(real);
			FILE_copy(path, real);
		}
		CATCH
		{
			real = path;
		}
		END_TRY
	}

	return real;
}

static GB_BROWSE_PROJECT_CALLBACK _browse_project_func;

static void project_file_found(const char *path)
{
	FILE_STAT info;
	size_t size;

	if (FILE_stat(path, &info, FALSE))
		size = 0;
	else
		size = info.size;

	(*_browse_project_func)(path, size);
}

void GB_BrowseProject(GB_BROWSE_PROJECT_CALLBACK func)
{
	ARCHIVE *arch = NULL;

	ARCHIVE_get_current(&arch);
	if (!arch || (arch == ARCHIVE_main && !FLAG.arch))
	{
		_browse_project_func = func;
		FILE_recursive_dir(PROJECT_path, project_file_found, NULL, 0, FALSE);
	}
	else
		ARCHIVE_browse(arch, func);
}

void GB_BrowseDirectory(const char *dir, GB_BROWSE_DIRECTORY_CALLBACK before, GB_BROWSE_DIRECTORY_CALLBACK after)
{
	FILE_recursive_dir(dir, before, after, 0, FALSE);
}


const char *GB_CurrentComponent()
{
	ARCHIVE *arch;

	ARCHIVE_get_current(&arch);
	return arch->name ? arch->name : "";
}


void *GB_DebugGetExec(void)
{
	return &EXEC_current;
}

void GB_DebugBreakOnError(bool b)
{
	FLAG.break_on_error = b;
}

void GB_DebugInside(bool b)
{
	FLAG.debug_inside = b;
}

void GB_DebugHold(void)
{
	FLAG.debug_hold = TRUE;
}

bool GB_SystemDebug(void)
{
	return FLAG.debug;
}

void GB_SystemHasForked(void)
{
	MATH_init();
	FILE_init();
	LOCAL_init();
	
	if (FLAG.profile)
		DEBUG.Profile.Cancel();
	FLAG.profile = FALSE;
	FLAG.profile_instr = FALSE;

	SIGNAL_has_forked();

	FLAG.has_forked = TRUE;
}

bool GB_ExistClass(const char *name)
{
	return CLASS_look_global(name, strlen(name)) != NULL;
}


bool GB_ExistClassLocal(const char *name)
{
	return CLASS_look(name, strlen(name)) != NULL;
}

void *GB_FindClass(const char *name)
{
	return CLASS_find_global(name);
}

void *GB_FindClassLocal(const char *name)
{
	return CLASS_find(name);
}

TYPE GB_GetArrayType(void *klass)
{
  return ((CLASS *)klass)->array_type;
}

void GB_Wait(int delay)
{
	struct timespec rem;
	double wait;
	double stop, time;
	int duration;

	DEBUG_enter_event_loop();

	if (delay <= 0)
	{
		HOOK_DEFAULT(wait, WATCH_wait)(delay);
	}
	else
	{
		wait = delay / 1000.0;

		DATE_timer(&stop, FALSE);
		stop += wait;

		for(;;)
		{
			duration = (int)(wait * 1000 + 0.5);
			HOOK_DEFAULT(wait, WATCH_wait)(duration);

			if (DATE_timer(&time, FALSE))
				break;

			wait = stop - time;
			if (wait <= 0.0)
				break;

			rem.tv_sec = 0;
			rem.tv_nsec = 1000000;
			nanosleep(&rem, &rem);
		}
	}

	DEBUG_leave_event_loop();
}

bool GB_Serialize(const char *path, GB_VALUE *value)
{
	bool ret = FALSE;
	CFILE *cfile = NULL;
	STREAM stream;

	TRY
	{
		STREAM_open(&stream, path, GB_ST_CREATE);
		cfile = OBJECT_REF(CFILE_create(&stream, GB_ST_CREATE));
		STREAM_write_type(&cfile->ob.stream, T_VARIANT, (VALUE *)value);
		OBJECT_UNREF(cfile);
	}
	CATCH
	{
		ret = TRUE;
		EXEC_set_native_error(TRUE);
	}
	END_TRY

	if (cfile)
		OBJECT_UNREF(cfile);

	return ret;
}

bool GB_UnSerialize(const char *path, GB_VALUE *value)
{
	bool ret = FALSE;
	CFILE *cfile = NULL;
	STREAM stream;

	TRY
	{
		STREAM_open(&stream, path, GB_ST_READ);
		cfile = OBJECT_REF(CFILE_create(&stream, GB_ST_CREATE));
		STREAM_read_type(&cfile->ob.stream, T_VARIANT, (VALUE *)value);
		BORROW((VALUE *)value);
		OBJECT_UNREF(cfile);
		UNBORROW((VALUE *)value);
	}
	CATCH
	{
		ret = TRUE;
		EXEC_set_native_error(TRUE);
	}
	END_TRY

	if (cfile)
		OBJECT_UNREF(cfile);

	return ret;

}

