//-----------------------------------------------------------------------------
//
//  $Logfile:: /EF2/Code/DLLs/game/interpreter.cpp                            $
// $Revision:: 22                                                             $
//     $Date:: 9/26/03 2:36p                                                  $
//
// Copyright (C) 1999 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
//
// DESCRIPTION:
// 

#include "_pch_cpp.h"
#include "interpreter.h"
#include "program.h"
#include "compiler.h"
#include "scriptmaster.h"
#include "scriptslave.h"
#include "camera.h"

CLASS_DECLARATION( Listener, Interpreter, NULL )
{
	{ &EV_ScriptThread_Execute,						&Interpreter::Execute },

	{ NULL, NULL }
};

inline void Interpreter::Archive( Archiver &arc )
{
	int i;

	Listener::Archive( arc );

	arc.ArchiveInteger( &pr_depth );

	for ( i = 0; i < pr_depth; i++ ) 
	{
		arc.ArchiveInteger( &pr_stack[i].s );
		arc.ArchiveObjectPointer( ( Class ** )&pr_stack[i].f );
		arc.ArchiveInteger( &pr_stack[i].stackbase );
	}

	arc.ArchiveRaw( localstack_type, sizeof( localstack_type[0] ) * LOCALSTACK_SIZE );
	arc.ArchiveRaw( localstack, sizeof( localstack[0] ) * LOCALSTACK_SIZE );
	arc.ArchiveInteger( &localstack_used );
	arc.ArchiveInteger( &localstack_base );

	arc.ArchiveBool( &pr_trace );
	arc.ArchiveObjectPointer( ( Class ** )&pr_xfunction );
	arc.ArchiveInteger( &pr_xstatement );

//	arc.ArchiveObjectPointer( ( Class ** )&program );	// shouldn't need this since only one program object
	arc.ArchiveInteger( &instruction_pointer );
	arc.ArchiveBool( &doneProcessing );
	arc.ArchiveBool( &threadDying );

	updateList.Archive( arc );

	arc.ArchiveInteger( &threadNum );
	arc.ArchiveString( &threadName );
}


/*
============
GlobalString

Returns a string with a description and the contents of a global,
padded to 20 field width
============
*/
const char *Interpreter::GlobalString( int ofs )
{
/****************************************************************************
Squirrel : #if 0 / 1 block demoted to comment

#if 0
	char	   *s;
	int		i;
	ddef_t	*def;
	void	   *val;
	static char	line[ 128 ];
	
	val = ( void * )&pr_globals[ ofs ];
	def = ED_GlobalAtOfs( ofs );
	if (!def)
		sprintf (line,"%i(???)", ofs);
	else
	{
		s = PR_ValueString (def->type, val);
		sprintf (line,"%i(%s)%s", ofs, pr_strings + def->s_name, s);
	}
	
	i = strlen(line);
	for ( ; i<20 ; i++)
		strcat (line," ");
	strcat (line," ");
		
	return line;
#else

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

	return "";
/****************************************************************************
Squirrel : #if 0 / 1 block demoted to comment

#endif

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

}

const char *Interpreter::GlobalStringNoContents( int ofs )
{
/****************************************************************************
Squirrel : #if 0 / 1 block demoted to comment

#if 0
	int		i;
	ddef_t	*def;
	static char	line[128];
	
	def = ED_GlobalAtOfs(ofs);
	if (!def)
		sprintf (line,"%i(???)", ofs);
	else
		sprintf (line,"%i(%s)", ofs, pr_strings + def->s_name);
	
	i = strlen(line);
	for ( ; i<20 ; i++)
		strcat (line," ");
	strcat (line," ");
		
	return line;
#else

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

	return "";
/****************************************************************************
Squirrel : #if 0 / 1 block demoted to comment

#endif

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

}

/*
=================
PrintStatement
=================
*/
void Interpreter::PrintStatement( const dstatement_t *s )
{
	int i;
	
	if ( ( unsigned )s->op < ( sizeof( pr_opcodes ) / sizeof( pr_opcodes[ 0 ] ) ) )
	{
		Com_Printf( "%s ", pr_opcodes[ s->op ].name );
		
		i = strlen( pr_opcodes[ s->op ].name );
		for( ; i < 10; i++ )
		{
			Com_Printf( " " );
		}
	}
	
	if ( ( s->op == OP_IF ) || ( s->op == OP_IFNOT ) )
	{
		Com_Printf( "%sbranch %i", GlobalString( s->a ), s->b );
	}
	else if ( s->op == OP_GOTO )
	{
		Com_Printf( "branch %i", s->a );
	}
	else if ( ( unsigned )( s->op - OP_STORE_F ) < 6 )
	{
		Com_Printf( "%s", GlobalString( s->a ) );
		Com_Printf( "%s", GlobalStringNoContents( s->b ) );
	}
	else
	{
		if ( s->a )
		{
			Com_Printf( "%s", GlobalString( s->a ) );
		}
		if ( s->b )
		{
			Com_Printf( "%s", GlobalString( s->b ) );
		}
		if ( s->c )
		{
			Com_Printf( "%s", GlobalStringNoContents( s->c ) );
		}
	}
	
	Com_Printf( "\n" );
}

/*
============
StackTrace
============
*/
void Interpreter::StackTrace( void )
{
	dfunction_t	*f;
	int			i;
	
	// anything less than zero will be bad
	if ( pr_depth <= 0 )
	{
		Com_Printf( "<NO STACK>\n" );
		return;
	}
	
	pr_stack[ pr_depth ].f = pr_xfunction;
	for( i = pr_depth; i >= 0; i-- )
	{
		f = pr_stack[ i ].f;
		
		if ( !f )
		{
			Com_Printf( "<NO FUNCTION>\n" );
		}
		else
		{
			Com_Printf( "%12s : %s\n", f->s_file.c_str(), f->s_name.c_str() );
		}
	}
}

/*
============
Profile

============
*/
void Interpreter::Profile( void )
{
	dfunction_t	*f;
	dfunction_t *best;
	int			max;
	int			num;
	int			i;
	
	num = 0;
	do
	{
		max = 0;
		best = NULL;
		for( i = 0; i < program->numfunctions; i++ )
		{
			f = &program->functions[ i ];
			if ( f->profile > max )
			{
				max = f->profile;
				best = f;
			}
		}
		   
		if ( best )
		{
			if ( num < 10 )
			{
				Com_Printf( "%7i %s\n", best->profile, best->s_name.c_str() );
			}
			   
			num++;
			best->profile = 0;
		}
	}
	while( best );
}

/*
============
RunError

Aborts the currently executing function
============
*/
void Interpreter::RunError( const char *error, ... )
{
	va_list	argptr;
	char		string[ 1024 ];
	
	va_start( argptr, error );
	vsprintf( string, error, argptr );
	va_end( argptr );
	
	//PrintStatement( program->statements + pr_xstatement );
	StackTrace();
	Com_Printf( "%s\n", string );
	
	// dump the stack so host_error can shutdown functions
	pr_depth = 0;
	
	gi.Error( ERR_DROP, "Program error" );
}
/*
====================
Interpreter::EntityError
Prints an error message and stack trace when an entity error is found.
====================
*/
inline void Interpreter::NullEntityError( const dstatement_t* statement )
{
	str filename( program->filenames[ statement->file - 1 ] );
	Com_Printf( "===============================================\n\n" );
	gi.WDPrintf( "Null entity referenced in %s on line %d\n", filename.c_str(), statement->linenumber );
	Com_Printf( "Stack Trace follows:\n" );
	StackTrace();
	Com_Printf( "===============================================\n\n" );
}

/*
====================
ThreadCall

Copys the args from the calling thread's stack
====================
*/
void Interpreter::ThreadCall( const Interpreter *source, dfunction_t *newf, int args )
{
	int start;
	int i;
	
	if ( scr_printfunccalls->integer )
		gi.Printf( "thread call - %s (ignore previous and next func call)\n", newf->s_name.c_str() );		
	
	Reset();
	start = source->localstack_used - args;
	for( i = 0; i < args; i++ )
	{
		localstack_type[ i ] = source->localstack_type[ start + i ];
		switch( localstack_type[ i ] )
		{
		case ev_string :
			localstack[ i ] = program->AllocString();
			program->strings[ localstack[ i ] ].s = program->strings[ source->localstack[ start + i ] ].s;
            break;
			
		case ev_vector :
            localstack[ i + 0 ] = source->localstack[ start + i + 0 ];
            localstack[ i + 1 ] = source->localstack[ start + i + 1 ];
            localstack[ i + 2 ] = source->localstack[ start + i + 2 ];
			
            // set the type of the other two to float to be safe
            localstack_type[ i + 1 ] = ev_float;
            localstack_type[ i + 2 ] = ev_float;
			
            // only add 2 since for loop will add 1
            i += 2;
            break;
			
		default :
            localstack[ i ] = source->localstack[ start + i ];
            break;
		}
	}
	
	localstack_used = args;
	localstack_base = 0;
	
	instruction_pointer = EnterFunction( newf );
	
	CancelEventsOfType( EV_ScriptThread_Execute );
	PostEvent( EV_ScriptThread_Execute, 0.0f );
}

/*
====================
EnterFunction

Returns the new program statement counter
====================
*/
int Interpreter::EnterFunction( dfunction_t *f )
{
	int i;
	int c;
	int off;
	int stackpos;
	etype_t type;
	
	if ( pr_depth >= MAX_STACK_DEPTH )
	{
		RunError( "stack overflow" );
	}
	
	if ( scr_printfunccalls->integer )
		gi.Printf( "func call - %s\n", f->s_name.c_str() );		
	
	pr_stack[ pr_depth ].s = pr_xstatement;
	pr_stack[ pr_depth ].f = pr_xfunction;
	pr_stack[ pr_depth ].stackbase = localstack_base;
	pr_depth++;
	
	// allocate space on the stack for locals
	// parms are already on stack
	c = f->locals - f->parm_total;
	assert( c >= 0 );
	
	if ( localstack_used + c > LOCALSTACK_SIZE )
	{
		RunError( "EnterFuncton: locals stack overflow\n" );
	}
	
	off = f->parm_start + f->parm_total;
	for( i = 0; i < c; i++ )
	{
		stackpos = localstack_used + i;
		
		type = program->pr_global_defs[ off + i ]->type->type;
		
		localstack_type[ stackpos ] = type;
		switch( localstack_type[ stackpos ] )
		{
		case ev_string :
			localstack[ stackpos ] = program->AllocString();
            break;
			
		case ev_vector :
            localstack[ stackpos + 0 ] = program->getInteger( off + i + 0 );
            localstack[ stackpos + 1 ] = program->getInteger( off + i + 1 );
            localstack[ stackpos + 2 ] = program->getInteger( off + i + 2 );
			
            // set the type of the other two to float to be safe
            localstack_type[ stackpos + 1 ] = ev_float;
            localstack_type[ stackpos + 2 ] = ev_float;
			
            // only add 2 since for loop will add 1
            i += 2;
            break;
			
		default :
            localstack[ stackpos ] = program->getInteger( off + i );
            break;
		}
	}
	
	localstack_used += c;
	localstack_base = localstack_used - f->locals - 1;
	
	pr_xfunction = f;
	
	// offset the s++
	return f->first_statement - 1;
}

/*
====================
CleanupStack
====================
*/
void Interpreter::CleanupStack( int localstack_used, int oldstacktop )
{
	int i;
	
	// delete any strings that were on the stack
	for( i = localstack_used; i < oldstacktop; i++ ) 
	{
		if ( localstack_type[ i ] == ev_string )
			program->FreeString( localstack[ i ] );
		localstack_type[ i ] = ev_void;
	}
}

/*
====================
LeaveFunction
====================
*/
int Interpreter::LeaveFunction( void )
{
	int c;
	int oldstacktop;
	
	if ( pr_depth <= 0 )
	{
		gi.Error( ERR_DROP, "prog stack underflow" );
	}
	
	// remove locals from the stack
	c = pr_xfunction->locals;
	oldstacktop = localstack_used;
	localstack_used -= c;
	if ( localstack_used < 0 )
	{
		RunError( "LeaveFunction: locals stack underflow\n" );
	}
	
	assert( localstack_used == ( localstack_base + 1 ) );
	
	CleanupStack( localstack_used, oldstacktop );
	
	// up stack
	pr_depth--;
	pr_xfunction = pr_stack[ pr_depth ].f;
	localstack_base = pr_stack[ pr_depth ].stackbase;
	
	return pr_stack[ pr_depth ].s;
}

const char *param_types[] = {"void", "string", "float", "vector", "entity", "function" };

Event *Interpreter::EventForFunction( const dfunction_t *func, int args )
{
	Event *ev;
	int i;
	eval_t *st;
	int pos;
	int start;
	
	ev = new Event( func->eventnum );
	ev->SetSource( EV_FROM_SCRIPT );
	ev->SetThread( ( CThread * )this );
	ev->SetLineNumber( program->statements[ instruction_pointer ].linenumber );
	
	start = localstack_used - args;
	for( i = 0, pos = 0; pos < args; i++ )
	{
		st = ( eval_t * )&localstack[ start + pos ];
		
		// remove this for shipping product.
		if (
			(func->parm_type[i] != localstack_type[start + pos]) &&
			(func->parm_type[i] != ev_vector) && 
			(localstack_type[start + pos] != ev_float)
			)
		{
			RunError	 ( "===============================\n"
				"Type mismatch on line %d. Func %s expected %s, got %s\n"
				"===============================\n", 
				program->statements[ instruction_pointer ].linenumber,
				func->s_name.c_str(),
				param_types[func->parm_type[i]],
				param_types[localstack_type[start + pos]] );
		}
		
		switch( func->parm_type[ i ] )
		{
		case ev_string :
            ev->AddString( program->strings[ st->string ].s );
            break;
			
		case ev_float :
            ev->AddFloat( st->_float );
            break;
			
		case ev_vector :
            ev->AddVector( Vector( st->vector ) );
            break;
			
		case ev_entity :
			if ( st->entity > 0 )
			{
				if ( st->entity >= game.maxentities )
				{
					gi.WDPrintf( "Bad entity number %d from line %d in the script\n", st->entity - 1, program->statements[ instruction_pointer ].linenumber );
				}
				else
				{
					ev->AddEntity( G_GetEntity( st->entity - 1 ) );
				}
			}
            else if ( st->entity < 0 )
			{
				TargetList *list;
				
				list = world->GetTargetList( -st->entity );
				if ( list )
				{
					ev->AddEntity( list->GetNextEntity( NULL ) );
				}
				else
				{
					ev->AddEntity( NULL );
				}
			}
            else
			{
				ev->AddEntity( NULL );
			}
            break;
            
		default:
            RunError( "Bad type on builtin call" );
		}
		
		pos += func->parm_size[ i ];
	}
	
	return ev;
}

void Interpreter::DoMove( void )
{
}

/*
====================
ExecuteProgram
====================
*/
void Interpreter::Execute( Event *e )
{
	eval_t	      *a;
	eval_t	      *b;
	eval_t	      *c;
	dstatement_t	*st;
	dfunction_t	   *newf;
	int		      runaway;
	int		      exitdepth;
	Listener       *obj;
	Entity         *ent;
	Event          *ev;
	char           text[ 128 ];
	int            i;
	int            n;
	TargetList     *list;
	int            oldstacktop;
	Interpreter    *newThread;
	int				stridx;
	
	if ( threadDying )
	{
		return;
	}
	
	runaway = 100000;
	
	// clear the updateList so that all objects moved this frame are notified before they receive any commands
	// we have to do this here as well as in DoMove, since DoMove may not be called
	updateList.ClearObjectList();
	
	// make a stack frame
	exitdepth = 0;
	
	doneProcessing = false;
	while( !doneProcessing && !threadDying )
	{
		// next statement
		instruction_pointer++;
		
		st = &program->statements[ instruction_pointer ];
		if ( st->a < 0 )
		{
			a = ( eval_t * )&localstack[ localstack_base - st->a ];
		}
		else
		{
			a = ( eval_t * )&program->pr_globals[ st->a ];
		}
		if ( st->b < 0 )
		{
			b = ( eval_t * )&localstack[ localstack_base - st->b ];
		}
		else
		{
			b = ( eval_t * )&program->pr_globals[ st->b ];
		}
		if ( st->c < 0 )
		{
			c = ( eval_t * )&localstack[ localstack_base - st->c ];
		}
		else
		{
			c = ( eval_t * )&program->pr_globals[ st->c ];
		}
		
		if ( !--runaway )
		{
			RunError( "runaway loop error" );
		}
		
		pr_xfunction->profile++;
		pr_xstatement = instruction_pointer;
		
		if ( pr_trace )
		{
			PrintStatement( st );
		}
		
		switch( st->op )
		{
		case OP_ADD_F:
			c->_float = a->_float + b->_float;
			break;
			
		case OP_ADD_V:
            VectorAdd( a->vector, b->vector, c->vector );
			break;
			
		case OP_ADD_S:
            program->strings[ c->string ].s = program->strings[ a->string ].s + program->strings[ b->string ].s;
			break;
			
		case OP_ADD_FS:
            sprintf( text, "%g", a->_float );
            program->strings[ c->string ].s = text + program->strings[ b->string ].s;
			break;
			
		case OP_ADD_SF:
            sprintf( text, "%g", b->_float );
            program->strings[ c->string ].s = program->strings[ a->string ].s + text;
			break;
			
		case OP_ADD_VS:
			sprintf( text, "(%g %g %g)", a->vector[0], a->vector[1], a->vector[2]);
			program->strings[ c->string ].s = text + program->strings[ b->string ].s;
			break;
			
		case OP_ADD_SV:
			sprintf( text, "(%g %g %g)", b->vector[0], b->vector[1], b->vector[2]);
			program->strings[ c->string ].s = program->strings[ a->string ].s + text ;
			break ;
			
		case OP_SUB_F:
			c->_float = a->_float - b->_float;
			break;
			
		case OP_SUB_V:
            VectorSubtract( a->vector, b->vector, c->vector );
			break;
			
		case OP_MUL_F:
			c->_float = a->_float * b->_float;
			break;
			
		case OP_MUL_V:
            c->_float = DotProduct( a->vector, b->vector );
			break;
			
		case OP_MUL_FV:
            VectorScale( b->vector, a->_float, c->vector );
			break;
			
		case OP_MUL_VF:
            VectorScale( a->vector, b->_float, c->vector );
			break;
			
		case OP_DIV_F:
            if ( b->_float != 0.0f )
			{
				c->_float = a->_float / b->_float;
			}
            else
			{
				assert( 0 );
				c->_float = 0;
			}
			break;
			
		case OP_BITAND:
			c->_float = ( int )a->_float & ( int )b->_float;
			break;
			
		case OP_BITOR:
			c->_float = ( int )a->_float | ( int )b->_float;
			break;
			
		case OP_GE:
			c->_float = a->_float >= b->_float;
			break;
			
		case OP_LE:
			c->_float = a->_float <= b->_float;
			break;
			
		case OP_GT:
			c->_float = a->_float > b->_float;
			break;
			
		case OP_LT:
			c->_float = a->_float < b->_float;
			break;
			
		case OP_AND:
			c->_float = a->_float && b->_float;
			break;
			
		case OP_OR:
			c->_float = a->_float || b->_float;
			break;
			
		case OP_NOT_F:
			c->_float = !a->_float;
			break;
			
		case OP_NOT_V:
			c->_float = !a->vector[ 0 ] && !a->vector[ 1 ] && !a->vector[ 2 ];
			break;
			
		case OP_NOT_S:
            c->_float = !&program->strings[ a->string ].s || ( program->strings[ a->string ].s.length() == 0 );
			break;
			
		case OP_NOT_FNC:
			c->_float = !a->function;
			break;
			
		case OP_NOT_ENT:
			c->_float = !a->entity;
			break;
			
		case OP_EQ_F:
			c->_float = ( a->_float == b->_float );
			break;
			
		case OP_EQ_V:
			c->_float = ( a->vector[ 0 ] == b->vector[ 0 ] ) &&
						( a->vector[ 1 ] == b->vector[ 1 ] ) &&
						( a->vector[ 2 ] == b->vector[ 2 ] );
			break;
			
		case OP_EQ_S:
			c->_float = !program->strings[ a->string ].s.cmp( program->strings[ b->string ].s );
			break;
			
		case OP_EQ_E:
			c->_float = ( a->_int == b->_int );
			break;
			
		case OP_EQ_FNC:
			c->_float = ( a->function == b->function );
			break;
			
		case OP_NE_F:
			c->_float = ( a->_float != b->_float );
			break;
			
		case OP_NE_V:
			c->_float = ( a->vector[ 0 ] != b->vector[ 0 ] ) ||
						( a->vector[ 1 ] != b->vector[ 1 ] ) ||
						( a->vector[ 2 ] != b->vector[ 2 ] );
			break;
			
		case OP_NE_S:
			c->_float = program->strings[ a->string ].s.cmp( program->strings[ b->string ].s );
			break;
			
		case OP_NE_E:
			c->_float = ( a->_int != b->_int );
			break;
			
		case OP_NE_FNC:
			c->_float = ( a->function != b->function );
			break;
			
		case OP_UADD_F :
            b->_float += a->_float;
            break;
			
		case OP_USUB_F :
            b->_float -= a->_float;
            break;
			
		case OP_UMUL_F :
            b->_float *= a->_float;
            break;
			
		case OP_UDIV_F :
            if ( a->_float != 0.0f )
			{
				b->_float /= a->_float;
			}
            else
			{
				assert( 0 );
				b->_float = 0;
			}
            break;
			
		case OP_UOR_F :
			b->_float = ( int )b->_float | ( int )a->_float;
            break;
			
		case OP_UAND_F : 
            b->_float = ( int )b->_float & ( int )a->_float;
            break;
			
		case OP_UINC_F :
            a->_float++;
            break;
			
		case OP_UDEC_F :
            a->_float--;
            break;
			
		case OP_STORE_F:
		case OP_STORE_ENT:
		case OP_STORE_FNC:		// pointers
			b->_int = a->_int;
			break;
			
		case OP_STORE_S:
            if ( st->b == OFS_RETURN )
			{
				// always use a static string for return values so that we
				// don't have to worry about freeing it up
				b->string = 0;
			}
            program->strings[ b->string ] = program->strings[ a->string ];
			break;
			
			
		case OP_STORE_V:
            VectorCopy( a->vector, b->vector );
			break;
			
		case OP_STORE_FTOS:
            if ( st->b == OFS_RETURN )
			{
				// always use a static string for return values so that we
				// don't have to worry about freeing it up
				b->string = 0;
			}
            if ( a->_float == ( float )( int )a->_float )
			{
				sprintf( text, "%d", ( int )a->_float );
			}
            else
			{
				sprintf( text, "%f", a->_float );
			}
            program->strings[ b->string ].s = text;
			break;
			
		case OP_IFNOT:
			if ( !a->_int )
			{
				// offset the instruction_pointer++
				instruction_pointer += st->b - 1;
			}
			break;
			
		case OP_IF:
			if ( a->_int )
			{
				// offset the instruction_pointer++
				instruction_pointer += st->b - 1;
			}
			break;
			
		case OP_GOTO:
            // offset the instruction_pointer++
			instruction_pointer += st->a - 1;
			break;
			
		case OP_THREAD:
			
			if ( !a->function )
			{
				RunError( "NULL function" );
			}
			
			newf = &program->functions[ a->function ];
			
            newThread = Director.CreateThread( newf->s_name );
            newThread->ThreadCall( this, newf, st->b );
			
            // return the thread number to the script
			program->pr_globals[ OFS_RETURN ]      = newThread->ThreadNum();
			program->pr_globals[ OFS_RETURN + 1 ]  = 0;
			program->pr_globals[ OFS_RETURN + 2 ]  = 0;
			
            // pop our parms off the stack
            oldstacktop = localstack_used;
            localstack_used -= st->b;
			if ( localstack_used < 0 )
			{
				RunError( "Execute: locals stack underflow\n" );
			}
			
			CleanupStack( localstack_used, oldstacktop );
			break;
			
		case OP_CALL:
			
			if ( !a->function )
			{
				RunError( "NULL function" );
			}
			
			newf = &program->functions[ a->function ];
			if ( newf->first_statement < 0 )
			{
				// negative statements are events
				if ( ValidEvent( newf->eventnum ) )
				{
					ev = EventForFunction( newf, st->b );
					ProcessEvent( ev );
					
					if ( scr_printeventcalls->integer )
						gi.Printf( "event call - %s\n", ev->getName() );
				}
				
				// pop our parms off the stack
				oldstacktop = localstack_used;
				localstack_used -= st->b;
				if ( localstack_used < 0 )
				{
					RunError( "Execute: locals stack underflow\n" );
				}
				
				CleanupStack( localstack_used, oldstacktop );
				break;
			}
			
			instruction_pointer = EnterFunction( newf );
			break;
			
		case OP_OCALL:
			
			if ( !a->function )
			{
				RunError( "NULL function" );
			}
			
			newf = &program->functions[ a->function ];
			assert( newf->first_statement < 0 );
			
            if ( ( st->b >= RESERVED_OFS ) && ( st->b < OFS_END ) )
			{
				switch( st->b )
				{
				case OFS_CAM :
					obj = &CameraMan;
					break;
					
				default :
					obj = NULL;  // shutup compiler
					RunError( "Execute: Invalid object call\n" );
					break;
				}
				
				if ( obj->ValidEvent( newf->eventnum ) )
				{
					ev = EventForFunction( newf, st->c );
					obj->ProcessEvent( ev );
					
					if ( scr_printeventcalls->integer )
						gi.Printf( "event call - %s\n", ev->getName() );
				}
			}
            else if ( b->entity > 0 )
			{
				ent = G_GetEntity( b->entity - 1 );
				if ( ent && ent->ValidEvent( newf->eventnum ) )
				{
					if ( !updateList.ObjectInList( ent->entnum ) )
					{
						updateList.AddObject( ent->entnum );
						
						// Tell the object that we're about to send it some orders
						ent->ProcessEvent( EV_ScriptSlave_NewOrders );
					}
					
					ev = EventForFunction( newf, st->c );
					ent->ProcessEvent( ev );
					
					if ( scr_printeventcalls->integer )							
						gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() );
				}
			}
            else if ( b->entity < 0 )
			{
				list = world->GetTargetList( -b->entity );
				
				if ( !list )
				{
					NullEntityError( st );
				}
				
				if ( list )
				{
					n = list->list.NumObjects();
					
					if ( !n )
					{
						NullEntityError( st );
					}
					
					if ( n )
					{
						ev = EventForFunction( newf, st->c );
						for( i = 1; i < n; i++ ) 
                        {
							ent = list->list.ObjectAt( i );
							if ( ent->ValidEvent( newf->eventnum ) )
							{
								if ( !updateList.ObjectInList( ent->entnum ) )
								{
									updateList.AddObject( ent->entnum );
									
									// Tell the object that we're about to send it some orders
									ent->ProcessEvent( EV_ScriptSlave_NewOrders );
								}
								
								ent->ProcessEvent( new Event( ev ) );
								
								if ( scr_printeventcalls->integer )										
									gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() );
							}
							else
							{
								ev->Error( "Entity '%s'(%d) cannot process event %s\n", 
									ent->targetname.c_str(), ent->entnum, ev->getName() );
							}
                        }
						
						ent = list->list.ObjectAt( i );
						if ( ent->ValidEvent( newf->eventnum ) )
                        {
							if ( !updateList.ObjectInList( ent->entnum ) )
							{
								updateList.AddObject( ent->entnum );
								
								// Tell the object that we're about to send it some orders
								ent->ProcessEvent( EV_ScriptSlave_NewOrders );
							}
							
							ent->ProcessEvent( ev );
							
							if ( scr_printeventcalls->integer )
								gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() );
                        }
						else
                        {
							ev->Error( "Entity '%s'(%d) cannot process event %s\n", 
								ent->targetname.c_str(), ent->entnum, ev->getName() );
							delete ev;
                        }
					}
				}
			}
			else
			{
				NullEntityError( st );
			}
			
            // pop our parms off the stack
            oldstacktop = localstack_used;
            localstack_used -= st->c;
			if ( localstack_used < 0 )
			{
				RunError( "Execute: locals stack underflow\n" );
			}
			
			CleanupStack( localstack_used, oldstacktop );
            break;
			
		case OP_PUSH_F :
			if ( localstack_used >= LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			localstack_type[ localstack_used ] = ev_float;
			localstack[ localstack_used++ ] = a->_int;
			break;
			 
		case OP_PUSH_FTOS :
			if ( localstack_used >= LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			if ( a->_float == ( float )( int )a->_float )
			{
				sprintf( text, "%d", ( int )a->_float );
			}
			else
			{
				sprintf( text, "%f", a->_float );
			}
			localstack_type[ localstack_used ] = ev_string;
			stridx = program->AllocString();
			program->strings[ stridx ].s = text;
			localstack[ localstack_used++ ] = ( int )stridx;
			break;
			 
		case OP_PUSH_ENT :
			if ( localstack_used >= LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			localstack_type[ localstack_used ] = ev_entity;
			localstack[ localstack_used++ ] = a->_int;
			break;
			 
		case OP_PUSH_FNC :
			if ( localstack_used >= LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			localstack_type[ localstack_used ] = ev_function;
			localstack[ localstack_used++ ] = a->_int;
			break;
			 
		case OP_PUSH_S :
			if ( localstack_used >= LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			localstack_type[ localstack_used ] = ev_string;
			stridx = program->AllocString();
			program->strings[ stridx ].s = program->strings[ a->string ].s;
			localstack[ localstack_used++ ] = ( int )stridx;
			break;
			 
		case OP_PUSH_V :
			if ( localstack_used + 3 > LOCALSTACK_SIZE )
			{
				RunError( "Execute: locals stack overflow\n" );
			}

			localstack_type[ localstack_used ] = ev_float;
			localstack[ localstack_used++ ] = *( int * )&a->vector[ 0 ];

			localstack_type[ localstack_used ] = ev_float;
			localstack[ localstack_used++ ] = *( int * )&a->vector[ 1 ];

			localstack_type[ localstack_used ] = ev_float;
			localstack[ localstack_used++ ] = *( int * )&a->vector[ 2 ];
			break;
			 
		case OP_DONE:
		case OP_RETURN:
			if ( st->a < 0 )
			{
				if ( localstack_type[ localstack_base - st->a ] == ev_string )
				{
					// always use a static string for return values so that we
					// don't have to worry about freeing it up
					program->pr_globals[ OFS_RETURN ] = 0;
				}
				else
				{
					// wacky casting to prevent conversion from int to float
					program->pr_globals[ OFS_RETURN ]      = *(float *)&localstack[ localstack_base - st->a ];
					program->pr_globals[ OFS_RETURN + 1 ]  = *(float *)&localstack[ localstack_base - st->a + 1 ];
					program->pr_globals[ OFS_RETURN + 2 ]  = *(float *)&localstack[ localstack_base - st->a + 2 ];
				}
			}
			else if ( st->a != 0 )
			{
				program->pr_globals[ OFS_RETURN ]      = program->pr_globals[ st->a ];
				program->pr_globals[ OFS_RETURN + 1 ]  = program->pr_globals[ st->a + 1 ];
				program->pr_globals[ OFS_RETURN + 2 ]  = program->pr_globals[ st->a + 2 ];
			}
			 
			instruction_pointer = LeaveFunction();
			if ( pr_depth == exitdepth )
			{
				// all done
				DoMove();
				Director.NotifyOtherThreads( threadNum );
				PostEvent( EV_Remove, 0.0f );
				doneProcessing = true;
				threadDying = true;
			}
			break;
			 
		default:
			RunError( "Bad opcode %i", st->op );
		}
	}
}

void Interpreter::Reset( void )
{
	CleanupStack( 0, localstack_used );
	
	pr_depth = 0;
	localstack_used = 0;
	localstack_base = 0;
	pr_trace = false;
	
	pr_xfunction = NULL;
	pr_xstatement = -1;
	
	threadDying			= false;
	doneProcessing		= true;
	
	memset( localstack, 0, sizeof( localstack ) );
	memset( pr_stack, 0, sizeof( pr_stack ) );
}

Interpreter::Interpreter()
{
	localstack_used = 0;	// these need to be here for cleaupStack
	localstack_base = 0;
	threadNum = 0;
	program = &::program;
	Reset();
}

Interpreter::~Interpreter()
{
	Director.NotifyOtherThreads( threadNum );
	Director.RemoveThread( threadNum );
	
	CleanupStack( 0, localstack_used );
}

qboolean Interpreter::labelExists( const char *label )
{
	func_t func;
	
	func = program->findFunction( label );
	if ( func < 0 || ( func >= program->numfunctions ) )
	{
		return false;
	}
	
	return true;
}

qboolean Interpreter::Goto( const char *label )
{
	func_t func;
	
	func = program->findFunction( label );
	if ( ( func < 0 ) || ( func >= program->numfunctions ) )
	{
		gi.WDPrintf( "Can't find function '%s' in program\n", label );
		return false;
	}
	
	instruction_pointer = EnterFunction( &program->functions[ func ] );
	
	CancelEventsOfType( EV_ScriptThread_Execute );
	PostEvent( EV_ScriptThread_Execute, 0.0f );
	
	return true;
}

qboolean Interpreter::Setup( int num, const char *label )
{
	Reset();
	
	threadNum = num;
	threadName = label;
	
	return Goto( label );
}
