/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */


#include "common/util.h"
#include "common/stack.h"
#include "common/textconsole.h"

#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/defs.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/memory.h"
#include "sword2/resman.h"

namespace Sword2 {

#define STACK_SIZE 10

// The machine code table

#ifndef REDUCE_MEMORY_USAGE
#	define OPCODE(x)	{ &Logic::x, #x }
#else
#	define OPCODE(x)	{ &Logic::x, "" }
#endif

void Logic::setupOpcodes() {
	static const OpcodeEntry opcodes[] = {
		/* 00 */
		OPCODE(fnTestFunction),
		OPCODE(fnTestFlags),
		OPCODE(fnRegisterStartPoint),
		OPCODE(fnInitBackground),
		/* 04 */
		OPCODE(fnSetSession),
		OPCODE(fnBackSprite),
		OPCODE(fnSortSprite),
		OPCODE(fnForeSprite),
		/* 08 */
		OPCODE(fnRegisterMouse),
		OPCODE(fnAnim),
		OPCODE(fnRandom),
		OPCODE(fnPreLoad),
		/* 0C */
		OPCODE(fnAddSubject),
		OPCODE(fnInteract),
		OPCODE(fnChoose),
		OPCODE(fnWalk),
		/* 10 */
		OPCODE(fnWalkToAnim),
		OPCODE(fnTurn),
		OPCODE(fnStandAt),
		OPCODE(fnStand),
		/* 14 */
		OPCODE(fnStandAfterAnim),
		OPCODE(fnPause),
		OPCODE(fnMegaTableAnim),
		OPCODE(fnAddMenuObject),
		/* 18 */
		OPCODE(fnStartConversation),
		OPCODE(fnEndConversation),
		OPCODE(fnSetFrame),
		OPCODE(fnRandomPause),
		/* 1C */
		OPCODE(fnRegisterFrame),
		OPCODE(fnNoSprite),
		OPCODE(fnSendSync),
		OPCODE(fnUpdatePlayerStats),
		/* 20 */
		OPCODE(fnPassGraph),
		OPCODE(fnInitFloorMouse),
		OPCODE(fnPassMega),
		OPCODE(fnFaceXY),
		/* 24 */
		OPCODE(fnEndSession),
		OPCODE(fnNoHuman),
		OPCODE(fnAddHuman),
		OPCODE(fnWeWait),
		/* 28 */
		OPCODE(fnTheyDoWeWait),
		OPCODE(fnTheyDo),
		OPCODE(fnWalkToTalkToMega),
		OPCODE(fnFadeDown),
		/* 2C */
		OPCODE(fnISpeak),
		OPCODE(fnTotalRestart),
		OPCODE(fnSetWalkGrid),
		OPCODE(fnSpeechProcess),
		/* 30 */
		OPCODE(fnSetScaling),
		OPCODE(fnStartEvent),
		OPCODE(fnCheckEventWaiting),
		OPCODE(fnRequestSpeech),
		/* 34 */
		OPCODE(fnGosub),
		OPCODE(fnTimedWait),
		OPCODE(fnPlayFx),
		OPCODE(fnStopFx),
		/* 38 */
		OPCODE(fnPlayMusic),
		OPCODE(fnStopMusic),
		OPCODE(fnSetValue),
		OPCODE(fnNewScript),
		/* 3C */
		OPCODE(fnGetSync),
		OPCODE(fnWaitSync),
		OPCODE(fnRegisterWalkGrid),
		OPCODE(fnReverseMegaTableAnim),
		/* 40 */
		OPCODE(fnReverseAnim),
		OPCODE(fnAddToKillList),
		OPCODE(fnSetStandbyCoords),
		OPCODE(fnBackPar0Sprite),
		/* 44 */
		OPCODE(fnBackPar1Sprite),
		OPCODE(fnForePar0Sprite),
		OPCODE(fnForePar1Sprite),
		OPCODE(fnSetPlayerActionEvent),
		/* 48 */
		OPCODE(fnSetScrollCoordinate),
		OPCODE(fnStandAtAnim),
		OPCODE(fnSetScrollLeftMouse),
		OPCODE(fnSetScrollRightMouse),
		/* 4C */
		OPCODE(fnColor),
		OPCODE(fnFlash),
		OPCODE(fnPreFetch),
		OPCODE(fnGetPlayerSaveData),
		/* 50 */
		OPCODE(fnPassPlayerSaveData),
		OPCODE(fnSendEvent),
		OPCODE(fnAddWalkGrid),
		OPCODE(fnRemoveWalkGrid),
		/* 54 */
		OPCODE(fnCheckForEvent),
		OPCODE(fnPauseForEvent),
		OPCODE(fnClearEvent),
		OPCODE(fnFaceMega),
		/* 58 */
		OPCODE(fnPlaySequence),
		OPCODE(fnShadedSprite),
		OPCODE(fnUnshadedSprite),
		OPCODE(fnFadeUp),
		/* 5C */
		OPCODE(fnDisplayMsg),
		OPCODE(fnSetObjectHeld),
		OPCODE(fnAddSequenceText),
		OPCODE(fnResetGlobals),
		/* 60 */
		OPCODE(fnSetPalette),
		OPCODE(fnRegisterPointerText),
		OPCODE(fnFetchWait),
		OPCODE(fnRelease),
		/* 64 */
		OPCODE(fnPrepareMusic),
		OPCODE(fnSoundFetch),
		OPCODE(fnPrepareMusic),	// Again, apparently
		OPCODE(fnSmackerLeadIn),
		/* 68 */
		OPCODE(fnSmackerLeadOut),
		OPCODE(fnStopAllFx),
		OPCODE(fnCheckPlayerActivity),
		OPCODE(fnResetPlayerActivityDelay),
		/* 6C */
		OPCODE(fnCheckMusicPlaying),
		OPCODE(fnPlayCredits),
		OPCODE(fnSetScrollSpeedNormal),
		OPCODE(fnSetScrollSpeedSlow),
		/* 70 */
		OPCODE(fnRemoveChooser),
		OPCODE(fnSetFxVolAndPan),
		OPCODE(fnSetFxVol),
		OPCODE(fnRestoreGame),
		/* 74 */
		OPCODE(fnRefreshInventory),
		OPCODE(fnChangeShadows)
	};

	_numOpcodes = ARRAYSIZE(opcodes);
	_opcodes = opcodes;
}

int Logic::runResScript(uint32 scriptRes, uint32 offset) {
	byte *scriptAddr;
	int result;

	scriptAddr = _vm->_resman->openResource(scriptRes);
	result = runScript(scriptAddr, scriptAddr, offset);
	_vm->_resman->closeResource(scriptRes);

	return result;
}

int Logic::runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset) {
	byte *scriptAddr, *objAddr;
	int result;

	scriptAddr = _vm->_resman->openResource(scriptRes);
	objAddr = _vm->_resman->openResource(objRes);
	result = runScript(scriptAddr, objAddr, offset);
	_vm->_resman->closeResource(objRes);
	_vm->_resman->closeResource(scriptRes);

	return result;
}

int Logic::runScript(byte *scriptData, byte *objectData, uint32 offset) {
	byte pc[4];

	WRITE_LE_UINT32(pc, offset);
	return runScript2(scriptData, objectData, pc);
}

// This form of the runScript function is only called directly from
// the processSession() function, which uses it to update the script PC in the
// current object hub. For reasons which I do not understand, I couldn't get it
// to work if I called the function first with a dummy offset variable, and
// and then updated the object hub myself.

int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) {
	int i;

	// Interestingly, unlike our BASS engine the stack is a local variable.
	// I don't know whether or not this is relevant to the working of the
	// BS2 engine.

	Common::FixedStack<int32, STACK_SIZE> stack;
	int32 opcodeParams[STACK_SIZE];

	uint32 offset = READ_LE_UINT32(offsetPtr);

	ResHeader header;

	header.read(scriptData);

	scriptData += ResHeader::size() + ObjectHub::size();

	// The script data format:
	//	int32_TYPE	1		Size of variable space in bytes
	//	...				The variable space
	//	int32_TYPE	1		numberOfScripts
	//	int32_TYPE	numberOfScripts	The offsets for each script

	// Initialize some stuff

	uint32 ip = 0;			 // Code pointer
	int scriptNumber;

	// Get the start of variables and start of code

	byte *localVars = scriptData + 4;
	byte *code = scriptData + READ_LE_UINT32(scriptData) + 4;
	uint32 noScripts = READ_LE_UINT32(code);

	code += 4;

	byte *offsetTable = code;

	if (offset < noScripts) {
		ip = READ_LE_UINT32(offsetTable + offset * 4);
		scriptNumber = offset;
		debug(8, "Starting script %d from %d", scriptNumber, ip);
	} else {
		ip = offset;

		for (i = 1; i < (int)noScripts; i++) {
			if (READ_LE_UINT32(offsetTable + 4 * i) >= ip)
				break;
		}

		scriptNumber = i - 1;
		debug(8, "Resuming script %d from %d", scriptNumber, ip);
	}

	// There are a couple of known script bugs related to interacting with
	// certain objects. We try to work around a few of them.

	bool checkMopBug = false;
	bool checkPyramidBug = false;
	bool checkElevatorBug = false;
	bool checkPearlBug = false;

	if (scriptNumber == 2) {
		if (strcmp((char *)header.name, "mop_73") == 0)
			checkMopBug = true;
		else if (strcmp((char *)header.name, "titipoco_81") == 0)
			checkPyramidBug = true;
		else if (strcmp((char *)header.name, "lift_82") == 0)
			checkElevatorBug = true;
		else if (strcmp((char *)header.name, "pearl_31") == 0)
			checkPearlBug = true;
	}

	code += noScripts * 4;

	// Code should now be pointing at an identifier and a checksum
	byte *checksumBlock = code;

	code += 4 * 3;

	if (READ_LE_UINT32(checksumBlock) != 12345678) {
		error("Invalid script in object %s", header.name);
		//return 0;
	}

	int32 codeLen = READ_LE_UINT32(checksumBlock + 4);
	int32 checksum = 0;

	for (i = 0; i < codeLen; i++)
		checksum += (unsigned char)code[i];

	if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) {
		debug(1, "Checksum error in object %s", header.name);
		// This could be bad, but there has been a report about someone
		// who had problems running the German version because of
		// checksum errors. Could there be a version where checksums
		// weren't properly calculated?
	}

	bool runningScript = true;

	int parameterReturnedFromMcodeFunction = 0;	// Allow scripts to return things
	int savedStartOfMcode = 0;	// For saving start of mcode commands

	while (runningScript) {
		int32 a, b;
		int curCommand, parameter, value; // Command and parameter variables
		int retVal;
		int caseCount;
		bool foundCase;
		byte *ptr;

		curCommand = code[ip++];

		switch (curCommand) {

		// Script-related opcodes

		case CP_END_SCRIPT:
			// End the script
			runningScript = false;

			// WORKAROUND: The dreaded pyramid bug makes the torch
			// untakeable when you speak to Titipoco. This is
			// because one of the conditions for the torch to be
			// takeable is that Titipoco isn't doing anything out
			// of the ordinary. Global variable 913 has to be 0 to
			// signify that he is in his "idle" state.
			//
			// Unfortunately, simply the act of speaking to him
			// sets variable 913 to 1 (probably to stop him from
			// turning around every now and then). The script may
			// then go on to set the variable to different values
			// to trigger various behaviors in him, but if you
			// have run out of these cases the script won't ever
			// set it back to 0 again.
			//
			// So if his click hander finishes, and variable 913 is
			// 1, we set it back to 0 manually.

			if (checkPyramidBug && readVar(913) == 1) {
				warning("Working around pyramid bug: Resetting Titipoco's state");
				writeVar(913, 0);
			}

			// WORKAROUND: The not-so-known-but-should-be-dreaded
			// elevator bug.
			//
			// The click handler for the top of the elevator only
			// handles using the elevator, not examining it. When
			// examining it, the mouse cursor is removed but never
			// restored.

			if (checkElevatorBug && readVar(RIGHT_BUTTON)) {
				warning("Working around elevator bug: Restoring mouse pointer");
				fnAddHuman(nullptr);
			}

			debug(9, "CP_END_SCRIPT");
			break;
		case CP_QUIT:
			// Quit out for a cycle
			WRITE_LE_UINT32(offsetPtr, ip);
			debug(9, "CP_QUIT");
			return 0;
		case CP_TERMINATE:
			// Quit out immediately without affecting the offset
			// pointer
			debug(9, "CP_TERMINATE");
			return 3;
		case CP_RESTART_SCRIPT:
			// Start the script again
			ip = FROM_LE_32(offsetTable[scriptNumber]);
			debug(9, "CP_RESTART_SCRIPT");
			break;

		// Stack-related opcodes

		case CP_PUSH_INT32:
			// Push a long word value on to the stack
			Read32ip(parameter);
			stack.push(parameter);
			debug(9, "CP_PUSH_INT32: %d", parameter);
			break;
		case CP_PUSH_LOCAL_VAR32:
			// Push the contents of a local variable
			Read16ip(parameter);
			stack.push(READ_LE_UINT32(localVars + parameter));
			debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter));
			break;
		case CP_PUSH_GLOBAL_VAR32:
			// Push a global variable
			Read16ip(parameter);
			stack.push(readVar(parameter));
			debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter));
			break;
		case CP_PUSH_LOCAL_ADDR:
			// Push the address of a local variable

			// From what I understand, some scripts store data
			// (e.g. mouse pointers) in their local variable space
			// from the very beginning, and use this mechanism to
			// pass that data to the opcode function. I don't yet
			// know the conceptual difference between this and the
			// CP_PUSH_DEREFERENCED_STRUCTURE opcode.

			Read16ip(parameter);
			stack.push(_vm->_memory->encodePtr(localVars + parameter));
			debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, (void *)(localVars + parameter));
			break;
		case CP_PUSH_STRING:
			// Push the address of a string on to the stack
			// Get the string size
			Read8ip(parameter);

			// ip now points to the string
			ptr = code + ip;
			stack.push(_vm->_memory->encodePtr(ptr));
			debug(9, "CP_PUSH_STRING: \"%s\"", ptr);
			ip += (parameter + 1);
			break;
		case CP_PUSH_DEREFERENCED_STRUCTURE:
			// Push the address of a dereferenced structure
			Read32ip(parameter);
			ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter;
			stack.push(_vm->_memory->encodePtr(ptr));
			debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, (void *)ptr);
			break;
		case CP_POP_LOCAL_VAR32:
			// Pop a value into a local word variable
			Read16ip(parameter);
			value = stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value);
			break;
		case CP_POP_GLOBAL_VAR32:
			// Pop a global variable
			Read16ip(parameter);
			value = stack.pop();

			// WORKAROUND for bug #2058: The not-at-all dreaded
			// mop bug.
			//
			// At the London Docks, global variable 1003 keeps
			// track of Nico:
			//
			// 0: Hiding behind the first crate.
			// 1: Hiding behind the second crate.
			// 2: Standing in plain view on the deck.
			// 3: Hiding on the roof.
			//
			// The bug happens when trying to pick up the mop while
			// hiding on the roof. Nico climbs down, the mop is
			// picked up, but the variable remains set to 3.
			// Visually, everything looks ok. But as far as the
			// scripts are concerned, she's still hiding up on the
			// roof. This is not fatal, but leads to a number of
			// glitches until the state is corrected. E.g. trying
			// to climb back up the ladder will cause Nico to climb
			// down again.
			//
			// Global variable 1017 keeps track of the mop. Setting
			// it to 2 means that the mop has been picked up. We
			// use that as the signal that Nico's state needs to be
			// updated as well.

			if (checkMopBug && parameter == 1017 && readVar(1003) != 2) {
				warning("Working around mop bug: Setting Nico's state");
				writeVar(1003, 2);
			}

			writeVar(parameter, value);
			debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value);
			break;
		case CP_ADDNPOP_LOCAL_VAR32:
			Read16ip(parameter);
			value = READ_LE_UINT32(localVars + parameter) + stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
			break;
		case CP_SUBNPOP_LOCAL_VAR32:
			Read16ip(parameter);
			value = READ_LE_UINT32(localVars + parameter) - stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
			break;
		case CP_ADDNPOP_GLOBAL_VAR32:
			// Add and pop a global variable
			Read16ip(parameter);
			value = readVar(parameter) + stack.pop();
			writeVar(parameter, value);
			debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
			break;
		case CP_SUBNPOP_GLOBAL_VAR32:
			// Sub and pop a global variable
			Read16ip(parameter);
			value = readVar(parameter) - stack.pop();
			writeVar(parameter, value);
			debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
			break;

		// Jump opcodes

		case CP_SKIPONTRUE:
			// Skip if the value on the stack is true
			Read32ipLeaveip(parameter);
			value = stack.pop();
			if (!value) {
				ip += 4;
				debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter);
			} else {
				ip += parameter;
				debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter);
			}
			break;
		case CP_SKIPONFALSE:
			// Skip if the value on the stack is false
			Read32ipLeaveip(parameter);
			value = stack.pop();
			if (value) {
				ip += 4;
				debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter);
			} else {
				ip += parameter;
				debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter);
			}
			break;
		case CP_SKIPALWAYS:
			// skip a block
			Read32ipLeaveip(parameter);
			ip += parameter;
			debug(9, "CP_SKIPALWAYS: %d", parameter);
			break;
		case CP_SWITCH:
			// switch
			value = stack.pop();
			Read32ip(caseCount);

			// Search the cases
			foundCase = false;
			for (i = 0; i < caseCount && !foundCase; i++) {
				if (value == (int32)READ_LE_UINT32(code + ip)) {
					// We have found the case, so lets
					// jump to it
					foundCase = true;
					ip += READ_LE_UINT32(code + ip + 4);
				} else
					ip += 4 * 2;
			}

			// If we found no matching case then use the default

			if (!foundCase)
				ip += READ_LE_UINT32(code + ip);

			debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]");
			break;
		case CP_SAVE_MCODE_START:
			// Save the start position on an mcode instruction in
			// case we need to restart it again
			savedStartOfMcode = ip - 1;
			debug(9, "CP_SAVE_MCODE_START");
			break;
		case CP_CALL_MCODE:
			// Call an mcode routine
			Read16ip(parameter);
			assert(parameter < _numOpcodes);
			// amount to adjust stack by (no of parameters)
			Read8ip(value);
			debug(9, "CP_CALL_MCODE: '%s', %d", _opcodes[parameter].desc, value);

			// The scripts do not always call the mcode command
			// with as many parameters as it can accept. To keep
			// things predictable, initialize the remaining
			// parameters to 0.

			for (i = STACK_SIZE - 1; i >= value; i--) {
				opcodeParams[i] = 0;
			}

			while (--value >= 0) {
				opcodeParams[value] = stack.pop();
			}

			retVal = (this->*_opcodes[parameter].proc)(opcodeParams);

			switch (retVal & 7) {
			case IR_STOP:
				// Quit out for a cycle
				WRITE_LE_UINT32(offsetPtr, ip);
				return 0;
			case IR_CONT:
				// Continue as normal
				break;
			case IR_TERMINATE:
				if (checkPearlBug && readVar(1290) == 0) {
					// Pearl's interaction script will wait
					// until global(1290) is no longer 0
					// before doing anything. But if the
					// script was terminated prematurely,
					// that never happens.
					warning("Working around Pearl bug: Resetting Pearl's state");
					writeVar(1290, 1);
				}
				// Return without updating the offset
				return 2;
			case IR_REPEAT:
				// Return setting offset to start of this
				// function call
				WRITE_LE_UINT32(offsetPtr, savedStartOfMcode);
				return 0;
			case IR_GOSUB:
				// that's really neat
				WRITE_LE_UINT32(offsetPtr, ip);
				return 2;
			default:
				error("Bad return code (%d) from '%s'", retVal & 7, _opcodes[parameter].desc);
			}
			parameterReturnedFromMcodeFunction = retVal >> 3;
			break;
		case CP_JUMP_ON_RETURNED:
			// Jump to a part of the script depending on
			// the return value from an mcode routine

			// Get the maximum value
			Read8ip(parameter);
			debug(9, "CP_JUMP_ON_RETURNED: %d => %d",
				parameterReturnedFromMcodeFunction,
				READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4));
			ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4);
			break;

		// Operators

		case OP_ISEQUAL:
			b = stack.pop();
			a = stack.pop();
			stack.push(a == b);
			debug(9, "OP_ISEQUAL: RESULT = %d", a == b);
			break;
		case OP_NOTEQUAL:
			b = stack.pop();
			a = stack.pop();
			stack.push(a != b);
			debug(9, "OP_NOTEQUAL: RESULT = %d", a != b);
			break;
		case OP_GTTHAN:
			b = stack.pop();
			a = stack.pop();
			stack.push(a > b);
			debug(9, "OP_GTTHAN: RESULT = %d", a > b);
			break;
		case OP_LSTHAN:
			b = stack.pop();
			a = stack.pop();
			stack.push(a < b);
			debug(9, "OP_LSTHAN: RESULT = %d", a < b);
			break;
		case OP_GTTHANE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a >= b);
			debug(9, "OP_GTTHANE: RESULT = %d", a >= b);
			break;
		case OP_LSTHANE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a <= b);
			debug(9, "OP_LSTHANE: RESULT = %d", a <= b);
			break;
		case OP_PLUS:
			b = stack.pop();
			a = stack.pop();
			stack.push(a + b);
			debug(9, "OP_PLUS: RESULT = %d", a + b);
			break;
		case OP_MINUS:
			b = stack.pop();
			a = stack.pop();
			stack.push(a - b);
			debug(9, "OP_MINUS: RESULT = %d", a - b);
			break;
		case OP_TIMES:
			b = stack.pop();
			a = stack.pop();
			stack.push(a * b);
			debug(9, "OP_TIMES: RESULT = %d", a * b);
			break;
		case OP_DIVIDE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a / b);
			debug(9, "OP_DIVIDE: RESULT = %d", a / b);
			break;
		case OP_ANDAND:
			b = stack.pop();
			a = stack.pop();
			stack.push(a && b);
			debug(9, "OP_ANDAND: RESULT = %d", a && b);
			break;
		case OP_OROR:
			b = stack.pop();
			a = stack.pop();
			stack.push(a || b);
			debug(9, "OP_OROR: RESULT = %d", a || b);
			break;

		// Debugging opcodes, I think

		case CP_DEBUGON:
			debug(9, "CP_DEBUGON");
			break;
		case CP_DEBUGOFF:
			debug(9, "CP_DEBUGOFF");
			break;
		case CP_TEMP_TEXT_PROCESS:
			Read32ip(parameter);
			debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter);
			break;
		default:
			error("Invalid script command %d", curCommand);
			return 3;	// for compilers that don't support NORETURN
		}
	}

	return 1;
}

} // End of namespace Sword2
