/*=========================================================================

	script.cpp

	Author:		PKG
	Created: 
	Project:	Spongebob
	Purpose: 

	Copyright (c) 2000 Climax Development Ltd

===========================================================================*/


/*----------------------------------------------------------------------
	Includes
	-------- */

#include "script\script.h"

#ifndef __SCRIPT_FUNCTION_H__
#include "script\function.h"
#endif

#ifndef	__SYSTEM_DBG_H__
#include "system\dbg.h"
#endif

#ifndef	__MEMORY_HEADER__
#include "mem\memory.h"
#endif


/*	Std Lib
	------- */

/*	Data
	---- */

/*----------------------------------------------------------------------
	Tyepdefs && Defines
	------------------- */

//#define FULL_CODE_OUTPUT
//#define SHOW_RUN_COUNT


/*----------------------------------------------------------------------
	Structure defintions
	-------------------- */

/*----------------------------------------------------------------------
	Function Prototypes
	------------------- */

/*----------------------------------------------------------------------
	Vars
	---- */

signed short	CScript::s_globalVars[NUM_GLOBAL_VARS]=
{
	0,			// LIVES
};


// Buffer for passing arguments to functions
unsigned short	CScript::s_argBuffer[MAX_FUNCTION_ARGS];


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::initialise(FileEquate _fe)
{
	int	i;

	m_code=(unsigned short*)CFileIO::loadFile(_fe);
	m_stack=(unsigned short*)MemAlloc(sizeof(unsigned short)*STACK_SIZE,"ScriptStack");
	reset();
	for(i=0;i<NUM_LOCAL_VARS;i++)
	{
		m_localVars[i]=0;
	}
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::dump()
{
	MemFree(m_stack);
	MemFree(m_code);
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::run()
{
#ifdef SHOW_RUN_COUNT
	int run=0;
#endif
	if(m_state<=RUNNING)
	{
		m_state=RUNNING;
		do
		{
			executeNextInstruction();
#ifdef SHOW_RUN_COUNT
			run++;			
#endif
		}
		while(m_state==RUNNING);
	}
#ifdef SHOW_RUN_COUNT
	if(run)
	{
		PAUL_DBGMSG("ran %d instructions",run);
	}
#endif
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::reset()
{
	m_pc=0;
	m_sp=0;
	m_state=RESET;
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
int CScript::isFinished()
{
	return !(m_state<=RUNNING);
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::executeNextInstruction()
{
	unsigned short	instruction;
	signed short	val1,val2,val3;
	int				i;

#ifdef FULL_CODE_OUTPUT
PAUL_DBGMSG("pc:0x%04d  sp:%03d",m_pc*2,m_sp);
#endif
	instruction=readNextInstruction();
	switch(instruction)
	{
		case OP_NOP:
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("NOP");
#endif
			break;

		case OP_STOP:				//
			PAUL_DBGMSG("STOP");
			if(m_sp==0)
			{
				m_state=STOPPED;
			}
			else
			{
				PAUL_DBGMSG("!STACK NOT EMPTY!");
				m_state=STOPPED_STACK_NOT_EMPTY;
			}
			break;
			
		case OP_PAUSE:				//
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("PAUSE");
#endif
			m_state=PAUSED;
			break;

		case OP_PUSHVALUE:			//	value
			val1=readNextInstruction();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("PUSHVALUE %d",val1);
#endif
			push(val1);
			break;
			
		case OP_PUSHVARVALUE:		//	varidx
			val1=readNextInstruction();
			val2=getVar(val1);
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("PUSHVARVALUE $%d ( %d )",val1,val2);
#endif
			push(val2);
			break;

		case OP_POP:				//								value
			val1=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("POP %d",val1);
#endif
			break;

		case OP_JMP:				//								jump
			val1=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("JMP %d",val1);
#endif
			jump(val1);
			break;
			
		case OP_JMPF:				//								jump, value
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("JMPF %d,%d",val1,val2);
#endif
			if(val2==0)jump(val1);
			break;

		case OP_JMPT:				//								jump, value
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("JMPT %d,%d",val1,val2);
#endif
			if(val2!=0)jump(val1);
			break;

		case OP_IS_EQUAL_VALUE:		//								value, value			pushes result ( 0 or 1 ) to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("IS_EQUAL_VALUE %d,%d",val1,val2);
#endif
			push(val1==val2);
			break;

		case OP_IS_NOTEQUAL_VALUE:	//								value, value			pushes result ( 0 or 1 ) to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("IS_NOTEQUAL_VALUE %d,%d",val1,val2);
#endif
			push(val1!=val2);
			break;

		case OP_IS_LESSTHAN_VALUE:	//								value, value			pushes result ( 0 or 1 ) to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("OP_IS_LESSTHAN_VALUE %d,%d",val1,val2);
#endif
			push(val1<val2);
			break;
			
		case OP_IS_GREATERTHAN_VALUE://								value, value			pushes result ( 0 or 1 ) to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("OP_IS_GREATERTHAN_VALUE %d,%d",val1,val2);
#endif
			push(val1>val2);
			break;
			
		case OP_ASSIGN:				//								varidx, value
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("$%d=%d",val1,val2);
#endif
			setVar(val1,val2);
			break;

		case OP_ADD:				//								value, value			pushes result to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("ADD %d,%d",val1,val2);
#endif
			push(val1+val2);
			break;

		case OP_MULTIPLY:			//								value, value			pushes result to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("MULTIPLY %d,%d",val1,val2);
#endif
			push(val1*val2);
			break;

		case OP_DIVIDE:				//								value, value			pushes result to stack
			val1=pop();
			val2=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("DIVIDE %d,%d",val1,val2);
#endif
			if(val2==0)
			{
				SYSTEM_DBGMSG("[SCRIPT] DIVIDE BY ZERO @%d",m_pc);
				m_state=CRASHED_DIVIDE_BY_ZERO;
			}
			else
			{
				push(val1/val2);
			}
			break;

		case OP_NEG:				//								value					pushes result to stack
			val1=pop();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("NEG %d",val1);
#endif
			push(-val1);
			break;

		case OP_PRINT:				//								value
			val1=pop();
			PAUL_DBGMSG("PRINT %d",val1);
			break;

		case OP_CALL_FUNCTION:		//	functionnumber, argcount	args					pushes return value to stack
			val1=readNextInstruction();
			val2=readNextInstruction();
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("CALL_FUNCTION %d ( %d args )",val1,val2);
#endif
			ASSERT(val2<MAX_FUNCTION_ARGS);		// Too many args.. just increase the #define to fix this
			for(i=0;i<val2;i++)
			{
				s_argBuffer[val2-i-1]=pop();
			}
			val3=callFunction(val1,val2,s_argBuffer);
#ifdef FULL_CODE_OUTPUT
			PAUL_DBGMSG("( return value is %d )",val3);
#endif
			push(val3);
			break;

		default:
			SYSTEM_DBGMSG("[SCRIPT] ILLEGAL OPCODE@%d ( %d )",m_pc,instruction);
			m_state=CRASHED_ILLEGAL_OPCODE;
			break;
	}
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
unsigned short CScript::readNextInstruction()
{
	return m_code[m_pc++];
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::jump(signed short _distance)
{
	m_pc+=_distance;
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::push(unsigned short _data)
{
	ASSERT(m_sp<=STACK_SIZE-1);		// Stack overflow about to occur :(
	m_stack[m_sp++]=_data;
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
unsigned short CScript::pop()
{
	ASSERT(m_sp>=1);				// Stack underflow about to occur :(
	return(m_stack[--m_sp]);
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
void CScript::setVar(int _varIdx,signed short _value)
{
	ASSERT(_varIdx>=0&&_varIdx<=NUM_GLOBAL_VARS+NUM_LOCAL_VARS-1);

	if(_varIdx<NUM_GLOBAL_VARS)
	{
		ASSERT(0);		// Need to update global vars.. (PKG)
		s_globalVars[_varIdx]=_value;
	}
	else if(_varIdx<NUM_GLOBAL_VARS+NUM_LOCAL_VARS)
	{
		m_localVars[_varIdx-NUM_GLOBAL_VARS]=_value;
	}
}


/*----------------------------------------------------------------------
	Function:
	Purpose:
	Params:
	Returns:
  ---------------------------------------------------------------------- */
signed short CScript::getVar(int _varIdx)
{
	ASSERT(_varIdx>=0&&_varIdx<=NUM_GLOBAL_VARS+NUM_LOCAL_VARS-1);

	int	ret=0;
	
	if(_varIdx<NUM_GLOBAL_VARS)
	{
		ASSERT(0);		// Need to read global vars.. (PKG)
		ret=s_globalVars[_varIdx];
	}
	else if(_varIdx<NUM_GLOBAL_VARS+NUM_LOCAL_VARS)
	{
		ret=m_localVars[_varIdx-NUM_GLOBAL_VARS];
	}
	return ret;
}


/*===========================================================================
 end */