/*
	#FILENAME#

	#DESCRIPTION#

	Copyright (C) 2002 #AUTHOR#

	Author: #AUTHOR#
	Date: #DATE#

	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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <fnmatch.h>
#include <errno.h>

#include "QF/cvar.h"
#include "QF/quakefs.h"
#include "QF/zone.h"
#include "QF/va.h"
#include "QF/sys.h"
#include "QF/cmd.h"
#include "QF/cbuf.h"
#include "QF/hash.h"
#include "QF/llist.h"
#include "QF/dstring.h"
#include "QF/gib.h"

#include "regex.h"
#include "gib_buffer.h"
#include "gib_parse.h"
#include "gib_function.h"
#include "gib_vars.h"
#include "gib_regex.h"
#include "gib_thread.h"
#include "gib_handle.h"
#include "gib_builtin.h"
#include "gib_classes.h"

static char _gib_null_string[] = "";
VISIBLE char * const gib_null_string = _gib_null_string;

hashtab_t  *gib_builtins;

/*
	Hashtable callbacks
*/
static const char *
GIB_Builtin_Get_Key (const void *ele, void *ptr)
{
	return ((gib_builtin_t *) ele)->name;
}
static void
GIB_Builtin_Free (void *ele, void *ptr)
{
	gib_builtin_t *b;

	b = (gib_builtin_t *) ele;
	free ((void *)b->name);
	free (b);
}

/*
	GIB_Builtin_Add

	Registers a new builtin GIB command.
*/

VISIBLE void
GIB_Builtin_Add (const char *name, void (*func) (void))
{
	gib_builtin_t *new;

	if (!gib_builtins)
		gib_builtins =
			Hash_NewTable (1024, GIB_Builtin_Get_Key, GIB_Builtin_Free, 0, 0);

	new = calloc (1, sizeof (gib_builtin_t));
	new->func = func;
	new->name = strdup (name);
	Hash_Add (gib_builtins, new);
}

VISIBLE void
GIB_Builtin_Remove (const char *name)
{
	gib_builtin_t *del;

	if ((del = Hash_Del (gib_builtins, name)))
		Hash_Free (gib_builtins, del);
}

VISIBLE qboolean
GIB_Builtin_Exists (const char *name)
{
	return Hash_Find (gib_builtins, name) ? true : false;
}

/*
	GIB_Builtin_Find

	Looks up the builtin name in the builtin hash,
	returning a pointer to the struct on success,
	zero otherwise.
*/
VISIBLE gib_builtin_t *
GIB_Builtin_Find (const char *name)
{
	if (!gib_builtins)
		return 0;
	return (gib_builtin_t *) Hash_Find (gib_builtins, name);
}

VISIBLE dstring_t *
GIB_Return (const char *str)
{
	dstring_t  *dstr;

	if (GIB_DATA (cbuf_active)->waitret) {
		dstr = GIB_Buffer_Dsarray_Get (cbuf_active);
		dstring_clearstr (dstr);
		if (!str)
			return dstr;
		else
			dstring_appendstr (dstr, str);
	}
	return 0;
}

VISIBLE void
GIB_Error (const char *type, const char *fmt, ...)
{
	va_list     args;

	va_start (args, fmt);
	GIB_Buffer_Error (cbuf_active, type, fmt, args);
	va_end (args);
}

/*
	GIB Builtin functions

	See GIB docs for information.
*/
static void
GIB_Function_f (void)
{
	gib_tree_t *program;
	gib_function_t *func;
	int i;
	int argc = GIB_Argc ();

	if (argc < 3) {
		GIB_USAGE ("name [arg1 arg2 ...] program");
	} else {
		// Is the function program already tokenized?
		if (GIB_Argm (argc-1)->delim != '{') {
			// Parse on the fly
			if (!(program = GIB_Parse_Lines (GIB_Argv
							(argc-1), 0))) {
				// Error!
				GIB_Error ("ParseError", "Parse error while defining function '%s'.",
						   GIB_Argv (1));
				return;
			}
		} else
			program = GIB_Argm (argc-1)->children;
		func = GIB_Function_Define (GIB_Argv (1), GIB_Argv (argc-1), program,
							 GIB_DATA (cbuf_active)->script,
							 GIB_DATA (cbuf_active)->globals);
		llist_flush (func->arglist);
		for (i = 2; i < argc-1; i++)
			llist_append (func->arglist, strdup (GIB_Argv(i)));
		func->minargs = argc-2;
	}
}

static void
GIB_Function_Get_f (void)
{
	if (GIB_Argc () != 2) {
		GIB_USAGE ("name");
	} else {
		gib_function_t *f;

		if ((f = GIB_Function_Find (GIB_Argv (1))))
			GIB_Return (f->text->str);
		else
			GIB_Return ("");
	}
}

static void
GIB_Local_f (void)
{
	gib_var_t  *var;
	unsigned int index;
	int i;
	static hashtab_t  *zero = 0;

	if (GIB_Argc () < 2) {
		GIB_USAGE ("var [= value1 value2 ...] || var [var2 var3 ...]");
	} else if (!strcmp (GIB_Argv(2), "=")) {
		var = GIB_Var_Get_Complex (&GIB_DATA (cbuf_active)->locals, &zero,
								 GIB_Argv (1), &index, true);
		if (GIB_Argc () >= 3)
			GIB_Var_Assign (var, index, cbuf_active->args->argv + 3,
							GIB_Argc () - 3, GIB_Argv (1)[strlen (GIB_Argv(1)) - 1] != ']');
		if (GIB_CanReturn ())
			for (i = 3; i < GIB_Argc(); i++)
				GIB_Return (GIB_Argv(i));
	} else for (i = 1; i < GIB_Argc(); i++)
		GIB_Var_Get_Complex (&GIB_DATA (cbuf_active)->locals, &zero,
							 GIB_Argv (i), &index, true);
}


static void
GIB_Shared_f (void)
{
	gib_var_t  *var;
	unsigned int index;
	int i;
	static hashtab_t  *zero = 0;

	if (GIB_Argc () < 2) {
		GIB_USAGE ("var [= value1 value2 ...] || var [var2 var3 ...]");
	} else if (!strcmp (GIB_Argv(2), "=")) {
		var = GIB_Var_Get_Complex (&GIB_DATA (cbuf_active)->globals, &zero,
								 GIB_Argv (1), &index, true);
		if (GIB_Argc () >= 3)
			GIB_Var_Assign (var, index, cbuf_active->args->argv + 3,
							GIB_Argc () - 3, GIB_Argv (1)[strlen (GIB_Argv(1)) - 1] != ']');
		if (GIB_CanReturn ())
			for (i = 3; i < GIB_Argc(); i++)
				GIB_Return (GIB_Argv(i));
	} else for (i = 1; i < GIB_Argc(); i++)
		GIB_Var_Get_Complex (&GIB_DATA (cbuf_active)->globals, &zero,
							 GIB_Argv (i), &index, true);
}

static void
GIB_Delete_f (void)
{
	gib_var_t *var;
	unsigned int index;
	int i;
	hashtab_t *source;
	char *c;

	if (GIB_Argc () < 2) {
		GIB_USAGE ("var [var2 var2 ...]");
	} else for (i = 1; i < GIB_Argc(); i++) {
		if ((c = strrchr (GIB_Argv(i), '.'))) {
			*(c++) = 0;
			if (!(var = GIB_Var_Get_Complex (&GIB_DATA (cbuf_active)->locals,
								&GIB_DATA(cbuf_active)->globals,
								 GIB_Argv (i), &index, false)))
				continue;
			source = var->array[index].leaves;
		} else {
			c = GIB_Argv(i);
			source = GIB_DATA(cbuf_active)->globals;
		}
		Hash_Free (source, Hash_Del (source, c));
	}
}

static void
GIB_Domain_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("domain");
	else
		GIB_DATA (cbuf_active)->globals = GIB_Domain_Get (GIB_Argv (1));
}

static void
GIB_Domain_Clear_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("domain");
	else
		Hash_FlushTable (GIB_Domain_Get (GIB_Argv (2)));
}

static gib_tree_t fakeip = {0,0,0,0,0,0,0,0};

static void
GIB_Return_f (void)
{
	cbuf_t     *sp = cbuf_active->up;

	GIB_DATA (cbuf_active)->ip = &fakeip;

	if (GIB_DATA (cbuf_active)->reply.obj) {
		gib_buffer_data_t *g = GIB_DATA (cbuf_active);
		const char **argv = malloc (sizeof (char *) * GIB_Argc() -1);
		int i;

		for (i = 1; i < GIB_Argc(); i++)
			argv[i-1] = GIB_Argv(i);

		GIB_Reply (g->reply.obj, g->reply.mesg, GIB_Argc()-1, argv);
		free ((void*)argv);
		g->dnotify = NULL;
	} else if (GIB_Argc () > 1 && sp && sp->interpreter == &gib_interp
		&& GIB_DATA (sp)->waitret) {
		int i;
		dstring_t  *dstr;

		for (i = 1; i < GIB_Argc (); i++) {
			dstr = GIB_Buffer_Dsarray_Get (sp);
			dstring_clearstr (dstr);
			dstring_appendstr (dstr, GIB_Argv (i));
		}
	}
}

static void
GIB_For_f (void)
{
	dstring_t  *dstr;
	int i;

	GIB_Buffer_Push_Sstack (cbuf_active);
	dstr = GIB_Buffer_Dsarray_Get (cbuf_active);
	dstring_clearstr (dstr);
	dstring_appendstr (dstr, GIB_Argv (1));
	for (i = GIB_Argc () - 2; i > 2; i--) {
		dstr = GIB_Buffer_Dsarray_Get (cbuf_active);
		dstring_appendstr (dstr, GIB_Argv (i));
	}
}

// Note: this is a standard console command, not a GIB builtin
static void
GIB_Runexported_f (void)
{
	gib_function_t *f;
	const char **args;

	if (!(f = GIB_Function_Find (Cmd_Argv (0)))) {
		Sys_Printf ("Error:  No function found for exported command \"%s\".\n"
					"This is most likely a bug, please report it to"
					"The QuakeForge developers.", Cmd_Argv (0));
	} else {
		cbuf_t     *sub = Cbuf_PushStack (&gib_interp);
		int i;

		args = malloc (sizeof (char *) * Cmd_Argc());
		for (i = 0; i < Cmd_Argc(); i++)
			args[i] = Cmd_Argv(i);
		GIB_Function_Execute (sub, f, args, Cmd_Argc());
		free ((void*)args);
	}
}

static void
GIB_Function_Export_f (void)
{
	gib_function_t *f;
	int         i;

	if (GIB_Argc () < 2)
		GIB_USAGE ("function1 [function2 function3 ...]");
	for (i = 1; i < GIB_Argc (); i++) {
		if (!(f = GIB_Function_Find (GIB_Argv (i))))
			GIB_Error ("UnknownFunctionError", "%s: function '%s' not found.", GIB_Argv (0),
					   GIB_Argv (i));
		else if (!f->exported) {
			if (Cmd_Exists (f->name)) {
				GIB_Error ("NameConflictError",
						   "%s: A console command with the name '%s' already exists.",
						   GIB_Argv (0), GIB_Argv (i));
				return;
			} else {
				Cmd_AddCommand (f->name, GIB_Runexported_f,
								"Exported GIB function.");
				f->exported = true;
			}
		}
	}
}

static void
GIB_Length_f (void)
{
	dstring_t  *ret;

	if (GIB_Argc () != 2)
		GIB_USAGE ("string");
	else if ((ret = GIB_Return (0)))
		dsprintf (ret, "%i", (int) strlen (GIB_Argv (1)));
}

static void
GIB_Equal_f (void)
{
	if (GIB_Argc () != 3)
		GIB_USAGE ("string1 string2");
	else if (strcmp (GIB_Argv (1), GIB_Argv (2)))
		GIB_Return ("0");
	else
		GIB_Return ("1");
}

static void
GIB_Count_f (void)
{
	if (GIB_CanReturn())
		dsprintf (GIB_Return(0), "%u", GIB_Argc() - 1);
}


static void
GIB_Contains_f (void)
{
	int i;
	if (GIB_Argc () < 2)
		GIB_USAGE ("needle [straw1 straw2 ...]");
	else if (GIB_CanReturn ())
		for (i = 2; i < GIB_Argc(); i++)
			if (!strcmp(GIB_Argv(1), GIB_Argv(i))) {
				GIB_Return("1");
				return;
			}
	GIB_Return ("0");
}

static void
GIB_Slice_f (void)
{
	dstring_t  *ret;
	int         start, end, len;

	if (GIB_Argc () < 3 || GIB_Argc () > 4) {
		GIB_USAGE ("string start [end]");
	} else {
		len = strlen (GIB_Argv (1));
		start = atoi (GIB_Argv (2));
		end = *GIB_Argv (3) ? atoi (GIB_Argv (3)) : len;
		if (end < 0)
			end += len;
		else if (end > len)
			end = len;
		if (start < 0) {
			start += len;
			if (start < 0)
				start = 0;
		} else if (start >= len || start >= end)
			return;
		if ((ret = GIB_Return (0)))
			dstring_appendsubstr (ret, GIB_Argv (1) + start, end - start);
	}
}


static void
GIB_Slice_Find_f (void)
{
	char       *res;

	if (GIB_Argc () != 3) {
		GIB_USAGE ("haystack needle");
		return;
	} else if (!GIB_CanReturn ()) {
		return;
	} else if ((res = strstr (GIB_Argv (1), GIB_Argv (2)))) {
		dsprintf (GIB_Return (0), "%lu",
				  (unsigned long int) (res - GIB_Argv (1)));
		dsprintf (GIB_Return (0), "%lu",
				  (unsigned long int) (res - GIB_Argv (1) +
									   strlen (GIB_Argv (2))));
	}
}


static void
GIB_Split_f (void)
{
	char       *end, *start;
	const char *ifs;

	if (GIB_Argc () < 2 || GIB_Argc () > 3) {
		GIB_USAGE ("string [fs]");
		return;
	}

	ifs = GIB_Argc () == 3 ? GIB_Argv (2) : " \t\r\n";

	end = GIB_Argv (1);
	while (*end) {
		for (; strchr (ifs, *end); end++)
			if (!*end)
				return;
		start = end;
		while (!strchr (ifs, *end))
			end++;
		if (*end)
			*(end++) = 0;
		GIB_Return (start);
	}
}

static void
GIB_Chomp_f (void)
{
	char *str;
	const char *junk;
	unsigned int i;

	if (GIB_Argc () < 2 || GIB_Argc () > 3) {
		GIB_USAGE ("string [junk]");
		return;
	}

	str = GIB_Argv (1);
	if (GIB_Argc () == 2)
		junk = " \t\n\r";
	else
		junk = GIB_Argv (2);

	for (; *str && strchr (junk, *str); str++);
	for (i = strlen(str) - 1; i && strchr (junk, str[i]); i--);
	str[i+1] = 0;
	GIB_Return (str);
}

static void
GIB_Regex_Match_f (void)
{
	regex_t    *reg;

	if (GIB_Argc () != 4) {
		GIB_USAGE ("string regex options");
		return;
	}

	if (!(reg =  GIB_Regex_Compile (GIB_Argv (2), REG_EXTENDED |
					GIB_Regex_Translate_Options (GIB_Argv (3)))))
		GIB_Error ("RegexError", "%s: %s", GIB_Argv (0), GIB_Regex_Error ());
	else if (regexec (reg, GIB_Argv (1), 0, 0, GIB_Regex_Translate_Runtime_Options (GIB_Argv (3))))
		GIB_Return ("0");
	else
		GIB_Return ("1");
}

static void
GIB_Regex_Replace_f (void)
{
	regex_t    *reg;
	int         ofs;
	regmatch_t  match[10];

	if (GIB_Argc () != 5) {
		GIB_USAGE ("string regex options replacement");
		return;
	}

	ofs = 0;

	if (!
		(reg =
		 GIB_Regex_Compile (GIB_Argv (2),
							REG_EXTENDED |
							GIB_Regex_Translate_Options (GIB_Argv (3)))))
		GIB_Error ("RegexError", "%s: %s", GIB_Argv (0), GIB_Regex_Error ());
	else if (strchr (GIB_Argv (3), 'g'))
		while (!regexec
			   (reg, GIB_Argv (1) + ofs, 10, match, ofs > 0 ? REG_NOTBOL : 0)
			   && match[0].rm_eo)
			ofs +=
				GIB_Regex_Apply_Match (match, GIB_Argd (1), ofs, GIB_Argv (4));
	else if (!regexec (reg, GIB_Argv (1), 10, match, GIB_Regex_Translate_Runtime_Options (GIB_Argv (3))) && match[0].rm_eo)
		GIB_Regex_Apply_Match (match, GIB_Argd (1), 0, GIB_Argv (4));
	GIB_Return (GIB_Argv (1));
}

static void
GIB_Regex_Extract_f (void)
{
	regex_t    *reg;
	regmatch_t *match;
	int         i;
	char        o;

	if (GIB_Argc () != 4) {
		GIB_USAGE ("string regex options");
		return;
	} else if (!GIB_CanReturn ())
		return;
	match = calloc (32, sizeof (regmatch_t));

	if (!
		(reg =
		 GIB_Regex_Compile (GIB_Argv (2),
							REG_EXTENDED |
							GIB_Regex_Translate_Options (GIB_Argv (3)))))
		GIB_Error ("RegexError", "%s: %s", GIB_Argv (0), GIB_Regex_Error ());
	else if (!regexec (reg, GIB_Argv (1), 32, match, GIB_Regex_Translate_Runtime_Options (GIB_Argv (3))) && match[0].rm_eo) {
		dsprintf (GIB_Return (0), "%lu", (unsigned long) match[0].rm_eo);
		for (i = 0; i < 32; i++) {
			if (match[i].rm_so != -1) {
				o = GIB_Argv (1)[match[i].rm_eo];
				GIB_Argv (1)[match[i].rm_eo] = 0;
				GIB_Return (GIB_Argv (1) + match[i].rm_so);
				GIB_Argv (1)[match[i].rm_eo] = o;
			}
		}
	}
	free (match);
}

static void
GIB_Text_White_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("text");
	else if (GIB_CanReturn ()) {
		unsigned int i;
		dstring_t *dstr;
		char *str;

		dstr = GIB_Return (0);
		dstring_appendstr (dstr, GIB_Argv(1));
		str = dstr->str;

		for (i = 0; i < dstr->size-1; i++)
			str[i] = str[i] & ~0x80;
	}
}

static void
GIB_Text_Brown_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("text");
	else if (GIB_CanReturn ()) {
		unsigned int i;
		dstring_t *dstr;
		char *str;

		dstr = GIB_Return (0);
		dstring_appendstr (dstr, GIB_Argv(1));
		str = dstr->str;

		for (i = 0; i < dstr->size-1; i++)
			str[i] = str[i] | 0x80;
	}
}

/*
static void
GIB_Text_To_Gold_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("text");
	else if (GIB_CanReturn ()) {
		dstring_t *dstr;
		char *str;

		dstr = GIB_Return (0);
		dstring_copystr (dstr, GIB_Argv(1));

		for (str = dstr->str; *str; str++) {
			switch (*str) {
*/

static void
GIB_Text_To_Decimal_f (void)
{
	if (GIB_Argc () != 2)
		GIB_USAGE ("text");
	else if (GIB_CanReturn ()) {
		char *str;

		for (str = GIB_Argv(1); *str; str++)
			dsprintf (GIB_Return (0), "%i", (int) *str);
	}
}

static void
GIB_Text_From_Decimal_f (void)
{
	if (GIB_Argc () < 2)
		GIB_USAGE ("num1 [...]");
	else if (GIB_CanReturn ()) {
		int i;
		dstring_t *dstr;
		char *str;

		dstr = GIB_Return (0);
		dstr->size = GIB_Argc();
		dstring_adjust (dstr);

		str = dstr->str;

		for (i = 1; i < GIB_Argc(); i++, str++)
			*str = (char) atoi (GIB_Argv(i));
		*str = 0;
	}
}

static void
GIB_Event_Register_f (void)
{
	gib_function_t *func;

	if (GIB_Argc () != 3)
		GIB_USAGE ("event function");
	else if (!(func = GIB_Function_Find (GIB_Argv (2))) && GIB_Argv (2)[0])
		GIB_Error ("UnknownFunctionError", "Function %s not found.", GIB_Argv (2));
	else if (GIB_Event_Register (GIB_Argv (1), func))
		GIB_Error ("UnknownEventError", "Event %s not found.", GIB_Argv (1));
}

/* File access */

static int   (*GIB_File_Transform_Path) (dstring_t * path) = NULL;

static int
GIB_File_Transform_Path_Null (dstring_t * path)
{
	char       *s;

	// Convert backslash to forward slash
	for (s = strchr (path->str, '\\'); s; s = strchr (s, '\\'))
		*s = '/';
	return 0;
}

static int
GIB_File_Transform_Path_Secure (dstring_t * path)
{
	char       *s;

	for (s = strchr (path->str, '\\'); s; s = strchr (s, '\\'))
		*s = '/';

	if (path->str[0] != '/')
		dstring_insertstr (path, 0, "/");
	dstring_insertstr (path, 0, qfs_gamedir->dir.def);
	dstring_insertstr (path, 0, "/");
	dstring_insertstr (path, 0, qfs_userpath);
	return 0;
}

static void
GIB_File_Read_f (void)
{
	QFile      *file;
	char       *path;
	int         len;
	dstring_t  *ret;

	if (GIB_Argc () != 2) {
		GIB_USAGE ("file");
		return;
	}
	if (!*GIB_Argv (1)) {
		GIB_Error ("FileAccessError", "%s: null filename provided", GIB_Argv (0));
		return;
	}

	if (!(ret = GIB_Return (0)))
		return;
	path = GIB_Argv (1);
	file = QFS_FOpenFile (path);
	if (file) {
		len = Qfilesize (file);
		ret->size = len + 1;
		dstring_adjust (ret);
		Qread (file, ret->str, len);
		ret->str[len] = 0;
		Qclose (file);
	} else {
		GIB_Error ("FileAccessError",
				   "%s: could not read %s: %s", GIB_Argv (0), path,
				   strerror (errno));
		return;
	}
}

static void
GIB_File_Write_f (void)
{
	char       *path;

	if (GIB_Argc () != 3) {
		GIB_USAGE ("file data");
		return;
	}
	if (!*GIB_Argv (1)) {
		GIB_Error ("InvalidArgumentError", "%s: null filename provided", GIB_Argv (0));
		return;
	}

	path = GIB_Argv (1);
	QFS_WriteFile (va (0, "%s/%s", qfs_gamedir->dir.def, path),
				   GIB_Argv(2), GIB_Argd(2)->size-1);
}

static void
GIB_File_Find_f (void)
{
	DIR        *directory;
	struct dirent *entry;
	const char *path, *glob = 0;
	char       *s;

	if (GIB_Argc () != 2) {
		GIB_USAGE ("glob");
		return;
	}
	if (GIB_File_Transform_Path (GIB_Argd (1))) {
		GIB_Error ("FileAccessError",
				   "%s: access to %s denied", GIB_Argv (0), GIB_Argv (1));
		return;
	}
	path = GIB_Argv (1);
	s = strrchr (path, '/');
	if (!s) {							// No slash in path
		glob = path;					// The glob is the entire argument
		path = ".";						// The path is the current directory
	} else if (s == path)				// Unix filesystem root (carne only)
		path = "/";
	else {
		*s = 0;							// Split the string at the final slash
		glob = s + 1;
	}
	directory = opendir (path);
	if (!directory)
		return;
	while ((entry = readdir (directory)))
		if (strcmp (entry->d_name, ".") && strcmp (entry->d_name, "..")
			&& !fnmatch (glob, entry->d_name, 0))
			GIB_Return (entry->d_name);
	closedir (directory);
}

static void
GIB_File_Move_f (void)
{
	char       *path1, *path2;

	if (GIB_Argc () != 3) {
		GIB_USAGE ("from_file to_file");
		return;
	}
	if (GIB_File_Transform_Path (GIB_Argd (1))) {
		GIB_Error ("FileAccessError",
				   "%s: access to %s denied", GIB_Argv (0), GIB_Argv (1));
		return;
	}
	if (GIB_File_Transform_Path (GIB_Argd (2))) {
		GIB_Error ("FileAccessError",
				   "%s: access to %s denied", GIB_Argv (0), GIB_Argv (2));
		return;
	}
	path1 = GIB_Argv (1);
	path2 = GIB_Argv (2);
	if (QFS_Rename (path1, path2))
		GIB_Error ("FileAccessError", "%s: could not move %s to %s: %s", GIB_Argv (0),
				   path1, path2, strerror (errno));
}

static void
GIB_File_Delete_f (void)
{
	char       *path;

	if (GIB_Argc () != 2) {
		GIB_USAGE ("file");
		return;
	}
	if (GIB_File_Transform_Path (GIB_Argd (1))) {
		GIB_Error ("FileAccessError",
				   "%s: access to %s denied", GIB_Argv (0), GIB_Argv (1));
		return;
	}
	path = GIB_Argv (1);
	if (QFS_Remove (path))
		GIB_Error ("FileAccessError", "%s: could not delete %s: %s", GIB_Argv (0), path,
				   strerror (errno));
}

static void
GIB_Range_f (void)
{
	double      i, inc, start, limit;
	dstring_t  *dstr;

	if (GIB_Argc () < 3 || GIB_Argc () > 4) {
		GIB_USAGE ("lower upper [step]");
		return;
	}
	limit = atof (GIB_Argv (2));
	start = atof (GIB_Argv (1));
	if (GIB_Argc () == 4) {
		if ((inc = atof (GIB_Argv (3))) == 0.0)
			return;
	} else
		inc = limit < start ? -1.0 : 1.0;
	for (i = atof (GIB_Argv (1)); inc < 0 ? i >= limit : i <= limit; i += inc) {
		if (!(dstr = GIB_Return (0)))
			return;
		dsprintf (dstr, "%.10g", i);
	}
}

static void
GIB_Print_f (void)
{
	if (GIB_Argc () != 2) {
		GIB_USAGE ("text");
		return;
	}
	Sys_Printf ("%s", GIB_Argv (1));
}

static void
GIB_Class_f (void)
{
	if (GIB_Object_Get (GIB_Argv(1))) {
		GIB_Error ("ClassRedefinitionError",
				"Class '%s' already exists", GIB_Argv(1));
	} else if (GIB_Argc () == 5)
		GIB_Classes_Build_Scripted (GIB_Argv(1), GIB_Argv(3),
				GIB_Argm (4)->children,
				GIB_DATA(cbuf_active)->script);
	else
		GIB_Classes_Build_Scripted (GIB_Argv(1), "Object",
				GIB_Argm (2)->children,
				GIB_DATA(cbuf_active)->script);
}

static void
GIB_Emit_f (void)
{
	if (GIB_Argc () < 2) {
		GIB_USAGE ("signal [arg1 arg2 ...]");
		return;
	} else if (!GIB_DATA(cbuf_active)->reply.obj) {
		GIB_Error ("InvalidContextError", "Cannot emit signal in this context.");
		return;
	} else {
		int i;
		const char **argv = malloc (GIB_Argc () - 1);

		for (i = 1; i < GIB_Argc (); i ++)
			argv[i-1] = GIB_Argv (1);

		GIB_Object_Signal_Emit (GIB_DATA(cbuf_active)->reply.obj,
				GIB_Argc () - 1, argv);

		free ((void*)argv);
	}
}

static void
GIB_Exists_f (void)
{
	if (GIB_Object_Get (GIB_Argv (1)))
		GIB_Return ("1");
	else
		GIB_Return ("0");
}

static void
GIB_Error_f (void)
{
	if (GIB_Argc() < 3) {
		GIB_USAGE ("error_type explanation");
		return;
	} else
		GIB_Error (GIB_Argv(1), "%s", GIB_Argv(2));
}

/*
static void
GIB_New_f (void)
{
	GIB_Object_t *classobj;
	if (GIB_Argc() < 2) {
		GIB_USAGE ("classname");
	} else if (
			   !(class = GIB_Object_Get(GIB_Argv(1)))
			|| classobj->class->classobj != classobj) {
		GIB_Error ("UnknownClassError", "Class '%s' does not exist",
				GIB_Argv(1));
	} else {
		GIB_Send (classobj,
*/

static void
GIB_bp1_f (void)
{
}

static void
GIB_bp2_f (void)
{
}

static void
GIB_bp3_f (void)
{
}

static void
GIB_bp4_f (void)
{
}

static void
gib_builtin_shutdown (void *data)
{
	Hash_DelTable (gib_builtins);
}

void
GIB_Builtin_Init (qboolean sandbox)
{

	if (sandbox)
		GIB_File_Transform_Path = GIB_File_Transform_Path_Secure;
	else
		GIB_File_Transform_Path = GIB_File_Transform_Path_Null;

	GIB_Builtin_Add ("function", GIB_Function_f);
	GIB_Builtin_Add ("function::get", GIB_Function_Get_f);
	GIB_Builtin_Add ("function::export", GIB_Function_Export_f);
	GIB_Builtin_Add ("local", GIB_Local_f);
	GIB_Builtin_Add ("shared", GIB_Shared_f);
	GIB_Builtin_Add ("global", GIB_Shared_f);
	GIB_Builtin_Add ("delete", GIB_Delete_f);
	GIB_Builtin_Add ("domain", GIB_Domain_f);
	GIB_Builtin_Add ("domain::clear", GIB_Domain_Clear_f);
	GIB_Builtin_Add ("return", GIB_Return_f);
	GIB_Builtin_Add ("for", GIB_For_f);
	GIB_Builtin_Add ("length", GIB_Length_f);
	GIB_Builtin_Add ("equal", GIB_Equal_f);
	GIB_Builtin_Add ("count", GIB_Count_f);
	GIB_Builtin_Add ("contains", GIB_Contains_f);
	GIB_Builtin_Add ("slice", GIB_Slice_f);
	GIB_Builtin_Add ("slice::find", GIB_Slice_Find_f);
	GIB_Builtin_Add ("split", GIB_Split_f);
	GIB_Builtin_Add ("chomp", GIB_Chomp_f);
	GIB_Builtin_Add ("regex::match", GIB_Regex_Match_f);
	GIB_Builtin_Add ("regex::replace", GIB_Regex_Replace_f);
	GIB_Builtin_Add ("regex::extract", GIB_Regex_Extract_f);
	GIB_Builtin_Add ("text::toWhite", GIB_Text_White_f);
	GIB_Builtin_Add ("text::toBrown", GIB_Text_Brown_f);
	GIB_Builtin_Add ("text::toDecimal", GIB_Text_To_Decimal_f);
	GIB_Builtin_Add ("text::fromDecimal", GIB_Text_From_Decimal_f);
	GIB_Builtin_Add ("event::register", GIB_Event_Register_f);
	GIB_Builtin_Add ("file::read", GIB_File_Read_f);
	GIB_Builtin_Add ("file::write", GIB_File_Write_f);
	GIB_Builtin_Add ("file::find", GIB_File_Find_f);
	GIB_Builtin_Add ("file::move", GIB_File_Move_f);
	GIB_Builtin_Add ("file::delete", GIB_File_Delete_f);
	GIB_Builtin_Add ("range", GIB_Range_f);
	GIB_Builtin_Add ("print", GIB_Print_f);
	GIB_Builtin_Add ("class", GIB_Class_f);
	GIB_Builtin_Add ("emit", GIB_Emit_f);
	GIB_Builtin_Add ("exists", GIB_Exists_f);
	GIB_Builtin_Add ("error", GIB_Error_f);
	GIB_Builtin_Add ("bp1", GIB_bp1_f);
	GIB_Builtin_Add ("bp2", GIB_bp2_f);
	GIB_Builtin_Add ("bp3", GIB_bp3_f);
	GIB_Builtin_Add ("bp4", GIB_bp4_f);

	Sys_RegisterShutdown (gib_builtin_shutdown, 0);
}
