/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/engines/tinsel/pcode.cpp $
 * $Id: pcode.cpp 48065 2010-02-15 10:01:26Z fingolfin $
 *
 * Virtual processor.
 */

#include "tinsel/dw.h"
#include "tinsel/drives.h"
#include "tinsel/events.h"	// 'POINTED' etc.
#include "tinsel/handle.h"	// LockMem()
#include "tinsel/dialogs.h"	// for inventory id's
#include "tinsel/pcode.h"	// opcodes etc.
#include "tinsel/scn.h"	// FindChunk()
#include "common/serializer.h"
#include "tinsel/timers.h"
#include "tinsel/tinlib.h"	// Library routines
#include "tinsel/tinsel.h"

#include "common/util.h"

namespace Tinsel {

//----------------- EXTERN FUNCTIONS --------------------

extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState);

//----------------- LOCAL DEFINES --------------------

#define	GLOBALS_FILENAME	"gdata"		// name of globals file

/** list of all opcodes */
enum OPCODE {
	OP_HALT = 0,	///< end of program
	OP_IMM = 1,		///< loads signed immediate onto stack
	OP_ZERO = 2,	///< loads zero onto stack
	OP_ONE = 3,		///< loads one onto stack
	OP_MINUSONE = 4,	///< loads minus one onto stack
	OP_STR = 5,		///< loads string offset onto stack
	OP_FILM = 6,	///< loads film offset onto stack
	OP_FONT = 7,	///< loads font offset onto stack
	OP_PAL = 8,		///< loads palette offset onto stack
	OP_LOAD = 9,	///< loads local variable onto stack
	OP_GLOAD = 10,	///< loads global variable onto stack - long offset to variable
	OP_STORE = 11,	///< pops stack and stores in local variable - long offset to variable
	OP_GSTORE = 12,	///< pops stack and stores in global variable - long offset to variable
	OP_CALL = 13,	///< procedure call
	OP_LIBCALL = 14,	///< library procedure call - long offset to procedure
	OP_RET = 15,		///< procedure return
	OP_ALLOC = 16,	///< allocate storage on stack
	OP_JUMP = 17,	///< unconditional jump	- signed word offset
	OP_JMPFALSE = 18,	///< conditional jump	- signed word offset
	OP_JMPTRUE = 19,	///< conditional jump	- signed word offset
	OP_EQUAL = 20,	///< tests top two items on stack for equality
	OP_LESS,	///< tests top two items on stack
	OP_LEQUAL,	///< tests top two items on stack
	OP_NEQUAL,	///< tests top two items on stack
	OP_GEQUAL,	///< tests top two items on stack
	OP_GREAT = 25,	///< tests top two items on stack
	OP_PLUS,	///< adds top two items on stack and replaces with result
	OP_MINUS,	///< subs top two items on stack and replaces with result
	OP_LOR,		///< logical or of top two items on stack and replaces with result
	OP_MULT,	///< multiplies top two items on stack and replaces with result
	OP_DIV = 30,		///< divides top two items on stack and replaces with result
	OP_MOD,		///< divides top two items on stack and replaces with modulus
	OP_AND,		///< bitwise ands top two items on stack and replaces with result
	OP_OR,		///< bitwise ors top two items on stack and replaces with result
	OP_EOR,		///< bitwise exclusive ors top two items on stack and replaces with result
	OP_LAND = 35,	///< logical ands top two items on stack and replaces with result
	OP_NOT,		///< logical nots top item on stack
	OP_COMP,	///< complements top item on stack
	OP_NEG,		///< negates top item on stack
	OP_DUP,		///< duplicates top item on stack
	OP_ESCON = 40,	///< start of escapable sequence
	OP_ESCOFF = 41,	///< end of escapable sequence
	OP_CIMM,	///< loads signed immediate onto stack (special to case statements)
	OP_CDFILM	///< loads film offset onto stack but not in current scene
};

// modifiers for the above opcodes
#define	OPSIZE8		0x40	///< when this bit is set - the operand size is 8 bits
#define	OPSIZE16	0x80	///< when this bit is set - the operand size is 16 bits

#define	OPMASK		0x3F	///< mask to isolate the opcode

bool bNoPause = false;

//----------------- LOCAL GLOBAL DATA --------------------

static int32 *pGlobals = 0;		// global vars

static int numGlobals = 0;		// How many global variables to save/restore

static INT_CONTEXT *icList = 0;

static uint32 hMasterScript;

//----------------- SCRIPT BUGS WORKAROUNDS --------------

/**
 * This structure is used to introduce bug fixes into the scripts used by the games.
 */
struct WorkaroundEntry {
	TinselEngineVersion version;	///< Engine version this workaround applies to
	bool scnFlag;					///< Only applicable for Tinsel 1 (DW 1)
	SCNHANDLE hCode;				///< Script to apply fragment to
	int ip;							///< Script offset to run this fragment before
	int numBytes;					///< Number of bytes in the script
	const byte *script;				///< Instruction(s) to execute
};

#define FRAGMENT_WORD(x)	(byte)(x & 0xFF), (byte)(x >> 8)

static const byte fragment1[] = {OP_ZERO, OP_GSTORE | OPSIZE16, 206, 0};
static const byte fragment2[] = {OP_LIBCALL | OPSIZE8, 110};
static const byte fragment3[] = {OP_ZERO, OP_GSTORE | OPSIZE16, FRAGMENT_WORD(490)};
static const byte fragment4[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(900), OP_JUMP | OPSIZE16, FRAGMENT_WORD(466)};
static const byte fragment5[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(901), OP_JUMP | OPSIZE16, FRAGMENT_WORD(488)};
static const byte fragment6[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(903), OP_JUMP | OPSIZE16, FRAGMENT_WORD(516)};
static const byte fragment7[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(908), OP_JUMP | OPSIZE16, FRAGMENT_WORD(616)};
static const byte fragment8[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(910), OP_JUMP | OPSIZE16, FRAGMENT_WORD(644)};
static const byte fragment9[] = {OP_JUMP | OPSIZE8, 123};
static const byte fragment10[] = {OP_IMM | OPSIZE16, FRAGMENT_WORD(160), OP_JUMP | OPSIZE16, FRAGMENT_WORD(136)};
static const byte fragment11[] = {OP_JMPTRUE | OPSIZE16, FRAGMENT_WORD(1572), 
		OP_ONE, OP_LIBCALL | OPSIZE8, 14,									// Re-show the cursor
		OP_IMM | OPSIZE16, FRAGMENT_WORD(322), OP_LIBCALL | OPSIZE8, 46,	// Give back the whistle
		OP_JUMP | OPSIZE16, FRAGMENT_WORD(1661)};
static const byte fragment12[] = {OP_JMPTRUE | OPSIZE16, FRAGMENT_WORD(1491), 
		OP_ONE, OP_LIBCALL | OPSIZE8, 14,									// Re-show the cursor
		OP_IMM | OPSIZE16, FRAGMENT_WORD(322), OP_LIBCALL | OPSIZE8, 46,	// Give back the whistle
		OP_JUMP | OPSIZE16, FRAGMENT_WORD(1568)};

#undef FRAGMENT_WORD

const WorkaroundEntry workaroundList[] = {
	// DW1-SCN: Global 206 is whether Rincewind is trying to take the
	// book back to the present. In the GRA version, it was global 373,
	// and was reset when he is returned to the past, but was forgotten
	// in the SCN version, so this ensures the flag is properly reset.
	{TINSEL_V1, true, 427942095, 1, sizeof(fragment1), fragment1},

	// DW1-GRA: Rincewind exiting the Inn is blocked by the luggage.
	// Whilst you can then move into walkable areas, saving and
	// restoring the game, it will error if you try to move. This
	// fragment turns off NPC blocking for the Outside Inn rooms so that
	// the luggage won't block Past Outside Inn.
	// See bug report #2525010.
	{TINSEL_V1, false, 444622076, 0,  sizeof(fragment2), fragment2},
	// Present Outside Inn
	{TINSEL_V1, false, 352600876, 0,  sizeof(fragment2), fragment2},

	// DW1-GRA: Talking to palace guards in Act 2 gives !!!HIGH
	// STRING||| - this happens if you initiate dialog with one of the
	// guards, but not the other. So these fragments provide the correct
	// talk parameters where needed.
	// See bug report #2831159.
	{TINSEL_V1, false, 310506872, 463, sizeof(fragment4), fragment4},
	{TINSEL_V1, false, 310506872, 485, sizeof(fragment5), fragment5},
	{TINSEL_V1, false, 310506872, 513, sizeof(fragment6), fragment6},
	{TINSEL_V1, false, 310506872, 613, sizeof(fragment7), fragment7},
	{TINSEL_V1, false, 310506872, 641, sizeof(fragment8), fragment8},

	// DW1-SCN: The script for the lovable street-Starfish does a
	// 'StopSample' after flicking the coin to ensure it's sound is
	// stopped, but which also accidentally can stop any active
	// conversation with the Amazon.
	{TINSEL_V1, true, 394640351, 121, sizeof(fragment9), fragment9},

	// DW2: In the garden, global #490 is set when the bees begin their
	// 'out of hive' animation, and reset when done. But if the game is
	// saved/restored during it, the animation sequence is reset without
	// the global being cleared. This causes bugs in several actions
	// which try to disable the bees animation, since they wait
	// indefinitely for the global to be cleared, incorrectly believing
	// the animation is currently playing. This includes:
	//  * Giving the brochure to the beekeeper (bug #2680397)
	//  * Stealing the mallets from the wizards (bug #2820788).
	// This fix ensures that the global is reset when the Garden scene
	// is loaded (both entering and restoring a game).
	{TINSEL_V2, true, 2888147476U, 0, sizeof(fragment3), fragment3},

	// DW1-GRA: Corrects text being drawn partially off-screen during
	// the blackboard description of the Librarian.
	{TINSEL_V1, false, 293831402, 133, sizeof(fragment10), fragment10},

	// DW1-GRA/SCN: Corrects the dead-end of being able to give the
	// whistle back to the pirate before giving him the parrot.
	// See bug report #2934211.
	{TINSEL_V1, true, 352601285, 1569, sizeof(fragment11), fragment11},
	{TINSEL_V1, false, 352602304, 1488, sizeof(fragment12), fragment12},

	{TINSEL_V0, false, 0, 0, 0, NULL}
};

//----------------- LOCAL GLOBAL DATA --------------------

/**
 * Keeps the code array pointer up to date.
 */
void LockCode(INT_CONTEXT *ic) {
	if (ic->GSort == GS_MASTER) {
		if (TinselV2)
			// Get the srcipt handle from a specific global chunk
			ic->code = (byte *)LockMem(hMasterScript);
		else
			ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
	} else
		ic->code = (byte *)LockMem(ic->hCode);
}

/**
 * Find a free interpret context and allocate it to the calling process.
 */
static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) {
	INT_CONTEXT *pic;
	int	i;

	for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
		if (pic->GSort == GS_NONE) {
			pic->pProc = g_scheduler->getCurrentProcess();
			pic->GSort = gsort;
			return pic;
		}
#ifdef DEBUG
		else {
			if (pic->pProc == g_scheduler->getCurrentProcess())
				error("Found unreleased interpret context");
		}
#endif
	}

	error("Out of interpret contexts");
}

static void FreeWaitCheck(PINT_CONTEXT pic, bool bVoluntary) {
	int i;

	// Is this waiting for something?
	if (pic->waitNumber1) {
		for (i = 0; i < NUM_INTERPRET; i++) {
			if ((icList + i)->waitNumber2 == pic->waitNumber1) {
				(icList + i)->waitNumber2 = 0;
				break;
			}
		}
	}

	// Is someone waiting for this?
	if (pic->waitNumber2) {
		for (i = 0; i < NUM_INTERPRET; i++) {
			if ((icList + i)->waitNumber1 == pic->waitNumber2) {
				(icList + i)->waitNumber1 = 0;
				(icList + i)->resumeCode = bVoluntary ? RES_FINISHED : RES_CUTSHORT;
				g_scheduler->reschedule((icList + i)->pProc);
				break;
			}
		}
		assert(i < NUM_INTERPRET);
	}
}

/**
 * Normal release of an interpret context.
 * Called from the end of Interpret().
 */
static void FreeInterpretContextPi(INT_CONTEXT *pic) {
	FreeWaitCheck(pic, true);
	if (TinselV2)
		memset(pic, 0, sizeof(INT_CONTEXT));
	pic->GSort = GS_NONE;
}

/**
 * Free interpret context owned by a dying process.
 * Ensures that interpret contexts don't get lost when an Interpret()
 * call doesn't complete.
 */
void FreeInterpretContextPr(PROCESS *pProc) {
	INT_CONTEXT *pic;
	int	i;

	for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
		if (pic->GSort != GS_NONE && pic->pProc == pProc) {
			FreeWaitCheck(pic, false);
			if (TinselV2)
				memset(pic, 0, sizeof(INT_CONTEXT));
			pic->GSort = GS_NONE;
			break;
		}
	}
}

/**
 * Free all interpret contexts except for the master script's
 */
void FreeMostInterpretContexts() {
	INT_CONTEXT *pic;
	int	i;

	for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++) {
		if ((pic->GSort != GS_MASTER) && (pic->GSort != GS_GPROCESS)) {
			memset(pic, 0, sizeof(INT_CONTEXT));
			pic->GSort = GS_NONE;
		}
	}
}

/**
 * Free the master script's interpret context.
 */
void FreeMasterInterpretContext() {
	INT_CONTEXT *pic;
	int	i;

	for (i = 0, pic = icList; i < NUM_INTERPRET; i++, pic++)	{
		if ((pic->GSort == GS_MASTER) || (pic->GSort == GS_GPROCESS)) {
			memset(pic, 0, sizeof(INT_CONTEXT));
			pic->GSort = GS_NONE;
			return;
		}
	}
}

/**
 * Allocate and initialise an interpret context.
 * Called from a process prior to Interpret().
 * @param gsort			which sort of code
 * @param hCode			Handle to code to execute
 * @param event			Causal event
 * @param hpoly			Associated polygon (if any)
 * @param actorId		Associated actor (if any)
 * @param pinvo			Associated inventory object
 */
INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode,	TINSEL_EVENT event,
		HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo, int myEscape) {
	INT_CONTEXT *ic;

	ic = AllocateInterpretContext(gsort);

	// Previously parameters to Interpret()
	ic->hCode = hCode;
	LockCode(ic);
	ic->event = event;
	ic->hPoly = hpoly;
	ic->idActor = actorid;
	ic->pinvo = pinvo;

	// Previously local variables in Interpret()
	ic->bHalt = false;		// set to exit interpeter
	ic->escOn = myEscape > 0;
	ic->myEscape = myEscape;
	ic->sp = 0;
	ic->bp = ic->sp + 1;
	ic->ip = 0;			// start of code

	ic->resumeState = RES_NOT;

	return ic;
}

/**
 * Allocate and initialise an interpret context with restored data.
 */
INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) {
	INT_CONTEXT *ic;

	ic = AllocateInterpretContext(GS_NONE);	// Sort will soon be overridden

	memcpy(ic, ric, sizeof(INT_CONTEXT));
	ic->pProc = g_scheduler->getCurrentProcess();
	ic->resumeState = RES_1;

	LockCode(ic);

	return ic;
}

/**
 * Allocates enough RAM to hold the global Glitter variables.
 */
void RegisterGlobals(int num) {
	if (pGlobals == NULL) {
		numGlobals = num;

		hMasterScript = !TinselV2 ? 0 :
			READ_LE_UINT32(FindChunk(MASTER_SCNHANDLE, CHUNK_MASTER_SCRIPT));

		// Allocate RAM for pGlobals and make sure it's allocated
		pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
		if (pGlobals == NULL) {
			error("Cannot allocate memory for global data");
		}

		// Allocate RAM for interpret contexts and make sure it's allocated
		icList = (INT_CONTEXT *)calloc(NUM_INTERPRET, sizeof(INT_CONTEXT));
		if (icList == NULL) {
			error("Cannot allocate memory for interpret contexts");
		}
		g_scheduler->setResourceCallback(FreeInterpretContextPr);
	} else {
		// Check size is still the same
		assert(numGlobals == num);

		memset(pGlobals, 0, numGlobals * sizeof(int32));
		memset(icList, 0, NUM_INTERPRET * sizeof(INT_CONTEXT));
	}

	if (TinselV2) {
		// read initial values
		CdCD(nullContext);

		Common::File f;
		if (!f.open(GLOBALS_FILENAME))
			error(CANNOT_FIND_FILE, GLOBALS_FILENAME);

		int32 length = f.readSint32LE();
		if (length != num)
			error(FILE_IS_CORRUPT, GLOBALS_FILENAME);

		for (int i = 0; i < length; ++i)
			pGlobals[i] = f.readSint32LE();

		if (f.eos() || f.err())
			error(FILE_IS_CORRUPT, GLOBALS_FILENAME);

		f.close();
	}
}

void FreeGlobals() {
	free(pGlobals);
	pGlobals = NULL;

	free(icList);
	icList = NULL;
}

/**
 * (Un)serialize the global data for save/restore game.
 */
void syncGlobInfo(Common::Serializer &s) {
	for (int i = 0; i < numGlobals; i++) {
		s.syncAsSint32LE(pGlobals[i]);
	}
}

/**
 * (Un)serialize an interpreter context for save/restore game.
 */
void INT_CONTEXT::syncWithSerializer(Common::Serializer &s) {
	if (s.isLoading()) {
		// Null out the pointer fields
		pProc = NULL;
		code = NULL;
		pinvo = NULL;
	}
	// Write out used fields
	s.syncAsUint32LE(GSort);
	s.syncAsUint32LE(hCode);
	s.syncAsUint32LE(event);
	s.syncAsSint32LE(hPoly);
	s.syncAsSint32LE(idActor);

	for (int i = 0; i < PCODE_STACK_SIZE; ++i)
		s.syncAsSint32LE(stack[i]);

	s.syncAsSint32LE(sp);
	s.syncAsSint32LE(bp);
	s.syncAsSint32LE(ip);
	s.syncAsUint32LE(bHalt);
	s.syncAsUint32LE(escOn);
	s.syncAsSint32LE(myEscape);
}

/**
 * Return pointer to and size of global data for save/restore game.
 */
void SaveInterpretContexts(INT_CONTEXT *sICInfo) {
	memcpy(sICInfo, icList, NUM_INTERPRET * sizeof(INT_CONTEXT));
}

/**
 * Fetches up to 4 bytes from the code script
 */
static int32 GetBytes(const byte *scriptCode, const WorkaroundEntry* &wkEntry, int &ip, uint numBytes) {
	assert(numBytes <= 4 && numBytes != 3);
	const byte *code = scriptCode;

	if (wkEntry != NULL) {
		if (ip >= wkEntry->numBytes) {
			// Finished the workaround
			ip = wkEntry->ip;
			wkEntry = NULL;
		} else {
			code = wkEntry->script;
		}
	}

	uint32 tmp;
	switch (numBytes) {
	case 0:
		// Instruction byte
		tmp = code[ip++ * (TinselV0 ? 4 : 1)];
		break;
	case 1:
		// Fetch and sign extend a 8 bit value to 32 bits.
		tmp = (int8)code[ip++];
		break;
	case 2:
		// Fetch and sign extend a 16 bit value to 32 bits.
		tmp = (int16)READ_LE_UINT16(code + ip);
		ip += 2;
		break;
	default:
		if (TinselV0)
			tmp = (int32)READ_LE_UINT32(code + ip++ * 4);
		else {
			tmp = (int32)READ_LE_UINT32(code + ip);
			ip += 4;
		}
		break;
	}

	return tmp;
}

/**
 * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
 * stream and advance the instruction pointer accordingly.
 */
static int32 Fetch(byte opcode, const byte *code, const WorkaroundEntry* &wkEntry, int &ip) {
	if (TinselV0)
		// Fetch a 32 bit value.
		return GetBytes(code, wkEntry, ip, 4);
	else if (opcode & OPSIZE8)
		// Fetch and sign extend a 8 bit value to 32 bits.
		return GetBytes(code, wkEntry, ip, 1);
	else if (opcode & OPSIZE16)
		return GetBytes(code, wkEntry, ip, 2);

	return GetBytes(code, wkEntry, ip, 4);
}

/**
 * Interprets the PCODE instructions in the code array.
 */
void Interpret(CORO_PARAM, INT_CONTEXT *ic) {
	do {
		int tmp, tmp2;
		int ip = ic->ip;
		const WorkaroundEntry *wkEntry = ic->fragmentPtr;

		if (wkEntry == NULL) {
			// Check to see if a workaround fragment needs to be executed
			for (wkEntry = workaroundList; wkEntry->script != NULL; ++wkEntry) {
				if ((wkEntry->version == TinselVersion) &&
					(wkEntry->hCode == ic->hCode) &&
					(wkEntry->ip == ip) &&
					(!TinselV1 || (wkEntry->scnFlag == ((_vm->getFeatures() & GF_SCNFILES) != 0)))) {
					// Point to start of workaround fragment
					ip = 0;
					break;
				}
			}
			if (wkEntry->script == NULL)
				wkEntry = NULL;
		}

		byte opcode = (byte)GetBytes(ic->code, wkEntry, ip, 0);
		if (TinselV0 && ((opcode & OPMASK) > OP_IMM))
			opcode += 3;

		debug(7, "ip=%d  Opcode %d (-> %d)", ic->ip, opcode, opcode & OPMASK);
		switch (opcode & OPMASK) {
		case OP_HALT:			// end of program

			ic->bHalt = true;
			break;

		case OP_IMM:			// loads immediate data onto stack
		case OP_STR:			// loads string handle onto stack
		case OP_FILM:			// loads film handle onto stack
		case OP_CDFILM:			// loads film handle onto stack
		case OP_FONT:			// loads font handle onto stack
		case OP_PAL:			// loads palette handle onto stack

			ic->stack[++ic->sp] = Fetch(opcode, ic->code, wkEntry, ip);
			break;

		case OP_ZERO:			// loads zero onto stack
			ic->stack[++ic->sp] = 0;
			break;

		case OP_ONE:			// loads one onto stack
			ic->stack[++ic->sp] = 1;
			break;

		case OP_MINUSONE:		// loads minus one onto stack
			ic->stack[++ic->sp] = -1;
			break;

		case OP_LOAD:			// loads local variable onto stack

			ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)];
			break;

		case OP_GLOAD:				// loads global variable onto stack

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			assert(0 <= tmp && tmp < numGlobals);
			ic->stack[++ic->sp] = pGlobals[tmp];
			break;

		case OP_STORE:				// pops stack and stores in local variable

			ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)] = ic->stack[ic->sp--];
			break;

		case OP_GSTORE:				// pops stack and stores in global variable

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			assert(0 <= tmp && tmp < numGlobals);
			pGlobals[tmp] = ic->stack[ic->sp--];
			break;

		case OP_CALL:				// procedure call

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			//assert(0 <= tmp && tmp < codeSize);	// TODO: Verify jumps are not out of bounds
			ic->stack[ic->sp + 1] = 0;	// static link
			ic->stack[ic->sp + 2] = ic->bp;	// dynamic link
			ic->stack[ic->sp + 3] = ip;	// return address
			ic->bp = ic->sp + 1;		// set new base pointer
			ip = tmp;	// set ip to procedure address
			break;

		case OP_LIBCALL:		// library procedure or function call

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			// NOTE: Interpret() itself is not using the coroutine facilities,
			// but still accepts a CORO_PARAM, so from the outside it looks
			// like a coroutine. In fact it may still acts as a kind of "proxy"
			// for some underlying coroutine. To enable this, we just pass on
			// 'coroParam' to CallLibraryRoutine(). If we then detect that
			// coroParam was set to a non-zero value, this means that some
			// coroutine code did run at some point, and we are now supposed
			// to sleep or die -- hence, we 'return' if coroParam != 0.
			//
			// This works because Interpret() is fully re-entrant: If we return
			// now and are later called again, then we will end up in the very
			// same spot (i.e. here).
			//
			// The reasons we do it this way, instead of turning Interpret into
			// a 'proper' coroutine are (1) we avoid implementation problems
			// (CORO_INVOKE involves adding 'case' statements, but Interpret
			// already has a huge switch/case, so that would not work out of the
			// box), (2) we incurr less overhead, (3) it's easier to debug,
			// (4) it's simply cool ;).
			tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
			if (coroParam)
				return;
			if (!TinselV0)
				ic->sp += tmp2;
			LockCode(ic);
			if (TinselV2 && (ic->resumeState == RES_1))
				ic->resumeState = RES_NOT;
			break;

		case OP_RET:			// procedure return

			ic->sp = ic->bp - 1;		// restore stack
			ip = ic->stack[ic->sp + 3];	// return address
			ic->bp = ic->stack[ic->sp + 2];	// restore previous base pointer
			break;

		case OP_ALLOC:			// allocate storage on stack

			ic->sp += (int32)Fetch(opcode, ic->code, wkEntry, ip);
			break;

		case OP_JUMP:	// unconditional jump

			ip = Fetch(opcode, ic->code, wkEntry, ip);
			wkEntry = NULL;					// In case a jump occurs from a workaround
			break;

		case OP_JMPFALSE:	// conditional jump

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			if (ic->stack[ic->sp--] == 0) {
				// condition satisfied - do the jump
				ip = tmp;
				wkEntry = NULL;					// In case a jump occurs from a workaround
			}
			break;

		case OP_JMPTRUE:	// conditional jump

			tmp = Fetch(opcode, ic->code, wkEntry, ip);
			if (ic->stack[ic->sp--] != 0) {
				// condition satisfied - do the jump
				ip = tmp;
				wkEntry = NULL;					// In case a jump occurs from a workaround
			}
			break;

		case OP_EQUAL:			// tests top two items on stack for equality
		case OP_LESS:			// tests top two items on stack
		case OP_LEQUAL:			// tests top two items on stack
		case OP_NEQUAL:			// tests top two items on stack
		case OP_GEQUAL:			// tests top two items on stack
		case OP_GREAT:			// tests top two items on stack
		case OP_LOR:			// logical or of top two items on stack and replaces with result
		case OP_LAND:			// logical ands top two items on stack and replaces with result

			// pop one operand
			ic->sp--;
			assert(ic->sp >= 0);
			tmp = ic->stack[ic->sp];
			tmp2 = ic->stack[ic->sp + 1];

			// replace other operand with result of operation
			switch (opcode) {
			case OP_EQUAL:  tmp = (tmp == tmp2); break;
			case OP_LESS:   tmp = (tmp <  tmp2); break;
			case OP_LEQUAL: tmp = (tmp <= tmp2); break;
			case OP_NEQUAL: tmp = (tmp != tmp2); break;
			case OP_GEQUAL: tmp = (tmp >= tmp2); break;
			case OP_GREAT:  tmp = (tmp >  tmp2); break;

			case OP_LOR:    tmp = (tmp || tmp2); break;
			case OP_LAND:   tmp = (tmp && tmp2); break;
			}

			ic->stack[ic->sp] = tmp;
			break;

		case OP_PLUS:			// adds top two items on stack and replaces with result
		case OP_MINUS:			// subs top two items on stack and replaces with result
		case OP_MULT:			// multiplies top two items on stack and replaces with result
		case OP_DIV:			// divides top two items on stack and replaces with result
		case OP_MOD:			// divides top two items on stack and replaces with modulus
		case OP_AND:			// bitwise ands top two items on stack and replaces with result
		case OP_OR:				// bitwise ors top two items on stack and replaces with result
		case OP_EOR:			// bitwise exclusive ors top two items on stack and replaces with result

			// pop one operand
			ic->sp--;
			assert(ic->sp >= 0);
			tmp = ic->stack[ic->sp];
			tmp2 = ic->stack[ic->sp + 1];

			// replace other operand with result of operation
			switch (opcode) {
			case OP_PLUS:   tmp += tmp2; break;
			case OP_MINUS:  tmp -= tmp2; break;
			case OP_MULT:   tmp *= tmp2; break;
			case OP_DIV:    tmp /= tmp2; break;
			case OP_MOD:    tmp %= tmp2; break;
			case OP_AND:    tmp &= tmp2; break;
			case OP_OR:     tmp |= tmp2; break;
			case OP_EOR:    tmp ^= tmp2; break;
			}
			ic->stack[ic->sp] = tmp;
			break;

		case OP_NOT:			// logical nots top item on stack

			ic->stack[ic->sp] = !ic->stack[ic->sp];
			break;

		case OP_COMP:			// complements top item on stack
			ic->stack[ic->sp] = ~ic->stack[ic->sp];
			break;

		case OP_NEG:			// negates top item on stack
			ic->stack[ic->sp] = -ic->stack[ic->sp];
			break;

		case OP_DUP:			// duplicates top item on stack
			ic->stack[ic->sp + 1] = ic->stack[ic->sp];
			ic->sp++;
			break;

		case OP_ESCON:
			bNoPause = true;
			ic->escOn = true;
			ic->myEscape = GetEscEvents();
			break;

		case OP_ESCOFF:
			ic->escOn = false;
			ic->myEscape = 0;
			break;

		default:
			error("Interpret() - Unknown opcode");
		}

		// check for stack under-overflow
		assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
		ic->ip = ip;
		ic->fragmentPtr = wkEntry;
	} while (!ic->bHalt);

	// make sure stack is unwound
	assert(ic->sp == 0);

	FreeInterpretContextPi(ic);
}

/**
 * Associates an interpret context with the
 * process that will run it.
 */
void AttachInterpret(INT_CONTEXT *pic, PROCESS *pProc) {
	// Attach the process which is using this context
	pic->pProc = pProc;
}

/**
 * Generate a number that isn't being used.
 */
static uint32 UniqueWaitNumber() {
	uint32 retval;
	int i;

	for (retval = DwGetCurrentTime(); 1; retval--) {
		if (retval == 0)
			retval = (uint32)-1;

		for (i = 0; i < NUM_INTERPRET; i++) {
			if ((icList+i)->waitNumber1 == retval
			 || (icList+i)->waitNumber2 == retval)
				break;
		}

		if (i == NUM_INTERPRET)
			return retval;
	}
}

/**
 * WaitInterpret
 */
void WaitInterpret(CORO_PARAM, PPROCESS pWaitProc, bool *result) {
	int i;
	PPROCESS currentProcess = g_scheduler->getCurrentProcess();
	assert(currentProcess);
	assert(currentProcess != pWaitProc);
	if (result) *result = false;

	/*
	 * Calling process is the waiter, find its interpret context.
	 */

	CORO_BEGIN_CONTEXT;
		PINT_CONTEXT picWaiter, picWaitee;
	CORO_END_CONTEXT(_ctx);


	CORO_BEGIN_CODE(_ctx);

	for (i = 0, _ctx->picWaiter = icList; i < NUM_INTERPRET; i++, _ctx->picWaiter++) {
		if (_ctx->picWaiter->GSort != GS_NONE && _ctx->picWaiter->pProc == currentProcess) {
			break;
		}
	}

	/*
	 * Find the interpret context of the process we're waiting for
	 */
	for (i = 0, _ctx->picWaitee = icList; i < NUM_INTERPRET; i++, _ctx->picWaitee++) {
		if (_ctx->picWaitee->GSort != GS_NONE && _ctx->picWaitee->pProc == pWaitProc) {
			break;
		}
	}

	/*
	 * Set the first as waiting for the second
	 */
	assert(_ctx->picWaitee->waitNumber2 == 0);
	_ctx->picWaiter->waitNumber1 = _ctx->picWaitee->waitNumber2 = UniqueWaitNumber();
	_ctx->picWaiter->resumeCode = RES_WAITING;

	/*
	 * Wait for it
	 */
	CORO_GIVE_WAY;
	while (_ctx->picWaiter->resumeCode == RES_WAITING) {
		CORO_SLEEP(1);
	}

	if (result)
		*result = (_ctx->picWaiter->resumeCode == RES_FINISHED);
	CORO_END_CODE;
}

/**
 * CheckOutWaiters
 */
void CheckOutWaiters() {
	int i, j;

	// Check all waited for have someone waiting
	for (i = 0; i < NUM_INTERPRET; i++)	{
		// If someone is supposedly waiting for this one
		if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber2) {
			// Someone really must be waiting for this one
			for (j = 0; j < NUM_INTERPRET; j++) {
				if ((icList + j)->GSort != GS_NONE
				 && (icList + j)->waitNumber1 == (icList + i)->waitNumber2) {
					break;
				}
			}
			assert(j < NUM_INTERPRET);
		}
	}

	// Check waiting for someone to wait for
	for (i = 0; i < NUM_INTERPRET; i++) {
		// If someone is supposedly waiting for this one
		if ((icList + i)->GSort != GS_NONE && (icList + i)->waitNumber1) {
			// Someone really must be waiting for this one
			for (j = 0; j < NUM_INTERPRET; j++) {
				if ((icList + j)->GSort != GS_NONE
				 && (icList + j)->waitNumber2 == (icList + i)->waitNumber1) {
					break;
				}
			}
			assert(j < NUM_INTERPRET);
		}
	}
}

} // End of namespace Tinsel
