/*
	gib_execute.c

	GIB runtime execution functions

	Copyright (C) 2002 Brian Koropoff

	Author: Brian Koropoff
	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 <string.h>
#include <stdlib.h>

#include "QF/cbuf.h"
#include "QF/cmd.h"
#include "QF/gib.h"

#include "gib_buffer.h"
#include "gib_builtin.h"
#include "gib_function.h"
#include "gib_vars.h"
#include "gib_tree.h"
#include "gib_process.h"
#include "gib_execute.h"

static void
GIB_Execute_Generate_Composite (struct cbuf_s *cbuf)
{
	cbuf_args_t *args = cbuf->args;
	int         i;

	dstring_clearstr (GIB_DATA (cbuf)->arg_composite);
	for (i = 0; i < args->argc; i++) {
		// ->str could be moved by realloc when a dstring is resized
		// so save the offset instead of the pointer
		args->args[i] =	(const char *) strlen (GIB_DATA (cbuf)->arg_composite->str);
		dstring_appendstr (GIB_DATA (cbuf)->arg_composite, args->argv[i]->str);
		dstring_appendstr (GIB_DATA (cbuf)->arg_composite, " ");
	}

	// Get rid of trailing space
	GIB_DATA (cbuf)->arg_composite->
		str[strlen (GIB_DATA (cbuf)->arg_composite->str) - 1] = 0;

	for (i = 0; i < args->argc; i++)
		// now that arg_composite is done we can add the pointer to the stored
		// offsets and get valid pointers. This *should* be portable.
		args->args[i] += (intptr_t) GIB_DATA (cbuf)->arg_composite->str;
}

static void
GIB_Execute_Split_Var (cbuf_t * cbuf)
{
	gib_var_t  *var;
	unsigned int i;
	unsigned int start = 0, end = ((unsigned int) ~0 >> 1);
	char       *c, *str = cbuf->args->argv[cbuf->args->argc - 1]->str + 1;
	void       *m = cbuf->args->argm[cbuf->args->argc - 1];

	i = strlen (str);
	if (i)
		i--;
	if (str[-1] == '@') {
		if (str[i] == ']')
			for (; i; i--)
				if (str[i] == '[') {
					str[i] = 0;
					start = atoi (str + i + 1);
					if ((c = strchr (str + i + 1, ':'))) {
						if (c[1] != ']')
							end = atoi (c + 1);
					} else
						end = start + 1;
					break;
				}
		cbuf->args->argc--;
		if (!(var = GIB_Var_Get_Complex (
			&GIB_DATA (cbuf)->locals,
			&GIB_DATA (cbuf)->globals,
			str, &i, false)))
				return;
		if ((int) end < 0)
			end += var->size;
		else if (end > var->size)
			end = var->size;
		if ((int) start < 0) {
			start += var->size;
			if ((int) start < 0)
				start = 0;
		} else if (start >= var->size || start >= end)
			return;
		for (i = start; i < end; i++) {
			if (var->array[i].value)
				Cbuf_ArgsAdd (cbuf->args, var->array[i].value->str);
			else
				Cbuf_ArgsAdd (cbuf->args, "");
			cbuf->args->argm[cbuf->args->argc - 1] = m;
		}
	} else {
		gib_var_t **vlist, **v;

		cbuf->args->argc--;
		if (!(var = GIB_Var_Get_Complex (
			&GIB_DATA (cbuf)->locals,
			&GIB_DATA (cbuf)->globals,
			str, &i, false)))
				return;
		if (!var->array[i].leaves)
			return;
		vlist = (gib_var_t **) Hash_GetList (var->array[i].leaves);
		for (v = vlist; *v; v++)
			Cbuf_ArgsAdd (cbuf->args, (*v)->key);
	}
}

static int
GIB_Execute_Prepare_Line (cbuf_t * cbuf, gib_tree_t * line)
{
	gib_tree_t *cur;
	cbuf_args_t *args = cbuf->args;
	unsigned int pos;

	args->argc = 0;

	for (cur = line->children; cur; cur = cur->next) {
		if (cur->flags & TREE_A_CONCAT) {
			pos = args->argv[args->argc - 1]->size - 1;
			if (cur->flags & TREE_A_EMBED) {
				GIB_Process_Embedded (cur, cbuf->args);
			} else
				dstring_appendstr (args->argv[args->argc - 1], cur->str);
		} else {
			pos = 0;
			if (cur->flags & TREE_A_EMBED) {
				Cbuf_ArgsAdd (args, "");
				GIB_Process_Embedded (cur, cbuf->args);
			} else {
				Cbuf_ArgsAdd (args, cur->str);
				args->argm[args->argc - 1] = cur;
			}
		}
		if (cur->delim == '('
			&& GIB_Process_Math (args->argv[args->argc - 1], pos))
			return -1;
		if (cur->flags & TREE_A_EXPAND)
			GIB_Execute_Split_Var (cbuf);
	}
	return 0;
}

static int
GIB_Execute_For_Next (cbuf_t * cbuf)
{
	unsigned int index;
	gib_var_t  *var;
	struct gib_dsarray_s *array = GIB_DATA (cbuf)->stack.values + GIB_DATA (cbuf)->stack.p - 1;
	if (array->size == 1) {
		GIB_Buffer_Pop_Sstack (cbuf);
		return -1;
	}
	array->size--;
	var = GIB_Var_Get_Complex (
		&GIB_DATA (cbuf)->locals,
		&GIB_DATA (cbuf)->globals, array->dstrs[0]->str,
		&index, true
	);
	dstring_clearstr (var->array[index].value);
	dstring_appendstr (var->array[index].value, array->dstrs[array->size]->str);
	return 0;
}

void
GIB_Execute (cbuf_t * cbuf)
{
	gib_buffer_data_t *g = GIB_DATA (cbuf);
	gib_builtin_t *b;
	gib_function_t *f;
	gib_object_t *obj;
	unsigned int index;
	gib_var_t *var;
	int i;
	qboolean super;

	static const char **mesg = NULL;
	static int maxmesg = 0;

	if (!g->program)
		return;
	g->ip = g->ip ? g->ip->next : g->program;
	while (g->ip) {
		switch (g->ip->type) {
			case TREE_T_LABEL: // Move to next instruction
				g->ip = g->ip->next;
				continue;
			case TREE_T_JUMP: // Absolute jump
				g->ip = g->ip->jump;
				continue;
			case TREE_T_FORNEXT: // Fetch next value in a for loop
				if (GIB_Execute_For_Next (cbuf))
					g->ip = g->ip->jump->next;
				else
					g->ip = g->ip->next;
				continue;
			case TREE_T_COND: // Conditional jump, move to next instruction
				if (GIB_Execute_Prepare_Line (cbuf, g->ip))
					return;
				if (g->ip->flags & TREE_L_NOT ?
					atof (cbuf->args->argv[1]->str) :
					!atof (cbuf->args->argv[1]->str))
						g->ip = g->ip->jump->next;
				else
					g->ip = g->ip->next;
				continue;
			case TREE_T_ASSIGN: // Assignment
				if (GIB_Execute_Prepare_Line (cbuf, g->ip))
					return;
				var = GIB_Var_Get_Complex (
					&g->locals,
					&g->globals,
					cbuf->args->argv[0]->str, &index, true);
				GIB_Var_Assign (var, index, cbuf->args->argv + 2, cbuf->args->argc - 2, cbuf->args->argv[0]->str[strlen (cbuf->args->argv[0]->str) - 1] != ']');
				if (g->ip->flags & TREE_L_EMBED) {
					GIB_Buffer_Push_Sstack (cbuf);
					g->waitret = true;
					if (GIB_CanReturn ())
						for (i = 2; i < cbuf->args->argc; i++)
							GIB_Return (cbuf->args->argv[i]->str);
				} else
					g->waitret = false;
				g->ip = g->ip->next;
				continue;
			case TREE_T_SEND: // Message sending
				if (GIB_Execute_Prepare_Line (cbuf, g->ip))
					return;
				if (cbuf->args->argc - 2 > maxmesg) {
					maxmesg += 32;
					mesg = realloc ((void*)mesg, sizeof (char *) * maxmesg);
				}
				for (i = 2; i < cbuf->args->argc; i++)
					mesg[i-2] = cbuf->args->argv[i]->str;

				super = false;
				if (!strcmp (cbuf->args->argv[0]->str,
							"super")) {
						if (!(obj = g->reply.obj)) {
							GIB_Error (
								"send",
								"Sending "
								"message to "
								"super not "
								"possible in "
								"this context."
							);
							return;
						}
						super = true;
				} else if (!(obj = GIB_Object_Get (cbuf->args->argv[0]->str))) {
					GIB_Error (
						"send",
						"No such object or class: %s",
						cbuf->args->argv[0]->str
					);
					return;
				}
				if (g->ip->flags & TREE_L_EMBED) {
					// Get ready for return values
					g->waitret = true;
					GIB_Buffer_Push_Sstack (cbuf);
					cbuf->state = CBUF_STATE_BLOCKED;
					i = super ? GIB_SendToMethod (obj,
							g->reply.method->parent,
							g->reply.obj, i - 2, mesg,
							GIB_Buffer_Reply_Callback,
							cbuf) : GIB_Send (obj,
							g->reply.obj, i - 2, mesg,
							GIB_Buffer_Reply_Callback,
							cbuf);
				} else {
					g->waitret = false;
					i = super ? GIB_SendToMethod (obj,
							g->reply.method->parent,
							g->reply.obj, i - 2, mesg,
							NULL, NULL) :
							GIB_Send (obj,g->reply.obj,
							i - 2, mesg, NULL,
							NULL);
				}
				if (i < 0) {
					GIB_Error (
						"send",
						"Object %s (%s) could not handle message %s",
						cbuf->args->argv[0]->str,
						obj->class->name,
						cbuf->args->argv[2]->str
					);
					return;
				}
				g->ip = g->ip->next;
				continue;
			case TREE_T_CMD: // Normal command
				if (GIB_Execute_Prepare_Line (cbuf, g->ip))
					return;
				if (g->ip->flags & TREE_L_EMBED) {
				// Get ready for return values
					g->waitret = true;
					GIB_Buffer_Push_Sstack (cbuf);
				} else
					g->waitret = false;
				if (cbuf->args->argc) {
					if ((b = GIB_Builtin_Find (cbuf->args->argv[0]->str)))
						b->func ();
					else if ((f = GIB_Function_Find (cbuf->args->argv[0]->str))) {
						cbuf_t     *new = Cbuf_PushStack (&gib_interp);
						if (GIB_Function_Execute_D
								(new, f,
								 cbuf->args->argv,
								 cbuf->args->argc))
							GIB_Error ("syntax", "not "
									"enough "
									"arguments to "
									"function '%s'",
									cbuf->args->argv[0]->str);
					} else {
						GIB_Execute_Generate_Composite (cbuf);
						if (Cmd_Command (cbuf->args))
							GIB_Error (
								"UnknownCommandError",
								"No builtin, function, or console command "
								"named '%s' was found.",
								cbuf->args->argv[0]->str
							);
					}
				}
				if (cbuf->state)
					return;
				g->ip = g->ip->next;
				continue;
			default: // We should never get here
				GIB_Error (
					"QUAKEFORGE-BUG-PLEASE-REPORT",
					"Unknown instruction type; tastes like chicken."
				);
				return;
		}
	}
	g->ip = 0;
	GIB_Tree_Unref (&g->program);
	g->program = 0;
}