/* 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.
 *
 */

#include "agos/agos.h"
#include "agos/vga.h"

#include "common/endian.h"
#include "common/textconsole.h"

namespace AGOS {

enum {
	kJmpClassNum = -1
};

#define OPCODE(x)	_OPCODE(AGOSEngine_PN, x)

void AGOSEngine_PN::setupOpcodes() {
	static const OpcodeEntryPN opcodes[] = {
		/* 00 */
		OPCODE(opn_opcode00),
		OPCODE(opn_add),
		OPCODE(opn_sub),
		OPCODE(opn_mul),
		/* 04 */
		OPCODE(opn_div),
		OPCODE(opn_opcode05),
		OPCODE(opn_opcode06),
		OPCODE(opn_opcode07),
		/* 08 */
		OPCODE(opn_opcode08),
		OPCODE(opn_opcode09),
		OPCODE(opn_opcode10),
		OPCODE(opn_opcode11),
		/* 12 */
		OPCODE(opn_opcode12),
		OPCODE(opn_opcode13),
		OPCODE(opn_opcode14),
		OPCODE(opn_opcode15),
		/* 16 */
		OPCODE(opn_opcode16),
		OPCODE(opn_lt),
		OPCODE(opn_gt),
		OPCODE(opn_eq),
		/* 20 */
		OPCODE(opn_neq),
		OPCODE(opn_opcode21),
		OPCODE(opn_opcode22),
		OPCODE(opn_opcode23),
		/* 24 */
		OPCODE(opn_opcode24),
		OPCODE(opn_opcode25),
		OPCODE(opn_opcode26),
		OPCODE(opn_opcode27),
		/* 28 */
		OPCODE(opn_opcode28),
		OPCODE(opn_opcode29),
		OPCODE(opn_opcode30),
		OPCODE(opn_opcode31),
		/* 32 */
		OPCODE(opn_opcode32),
		OPCODE(opn_opcode33),
		OPCODE(opn_opcode34),
		OPCODE(opn_opcode35),
		/* 36 */
		OPCODE(opn_opcode36),
		OPCODE(opn_opcode37),
		OPCODE(opn_opcode38),
		OPCODE(opn_opcode39),
		/* 40 */
		OPCODE(opn_opcode40),
		OPCODE(opn_opcode41),
		OPCODE(opn_opcode42),
		OPCODE(opn_opcode43),
		/* 44 */
		OPCODE(opn_opcode44),
		OPCODE(opn_opcode45),
		OPCODE(opn_opcode46),
		OPCODE(opn_opcode47),
		/* 48 */
		OPCODE(opn_opcode48),
		OPCODE(opn_opcode49),
		OPCODE(opn_opcode50),
		OPCODE(opn_opcode51),
		/* 52 */
		OPCODE(opn_opcode52),
		OPCODE(opn_opcode53),
		OPCODE(opn_opcode54),
		OPCODE(opn_opcode55),
		/* 56 */
		OPCODE(opn_opcode56),
		OPCODE(opn_opcode57),
		OPCODE(o_invalid),
		OPCODE(o_invalid),
		/* 60 */
		OPCODE(o_invalid),
		OPCODE(o_invalid),
		OPCODE(opn_opcode62),
		OPCODE(opn_opcode63),
	};

	_opcodesPN = opcodes;
	_numOpcodes = 64;
}

void AGOSEngine_PN::executeOpcode(int opcode) {
	OpcodeProcPN op = _opcodesPN[opcode].proc;
	(this->*op)();
}

int AGOSEngine_PN::readfromline() {
	if (!_linct)
		error("readfromline: Internal Error - Line Over-run");
	_linct--;
	return *_workptr++;
}

// -----------------------------------------------------------------------
// Personal Nightmare Opcodes
// -----------------------------------------------------------------------

void AGOSEngine_PN::opn_opcode00() {
	uint8 *str = _workptr;
	varval();
	writeval(str, varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_add() {
	uint8 *str = _workptr;
	int32 sp = varval() + varval();
	_variableArray[12] = sp % 65536;
	_variableArray[13] = sp / 65536;
	if (sp > 65535)
		sp = 65535;
	writeval(str, (int)sp);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_sub() {
	uint8 *str = _workptr;
	int32 sp = varval();
	sp -= varval();
	_variableArray[12] = sp % 65536;
	_variableArray[13] = sp / 65536;
	if (sp < 0)
		sp = 0;
	writeval(str, (int)sp);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_mul() {
	uint8 *str = _workptr;
	int32 sp = varval() * varval();
	_variableArray[12] = sp % 65536;
	_variableArray[13] = sp / 65536;
	if (sp > 65535)
		sp = 65535;
	writeval(str, (int)sp);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_div() {
	uint8 *str = _workptr;
	int32 sp = varval();
	int32 sp2 = varval();
	if (sp2 == 0)
		error("opn_div: Division by 0");
	sp = sp / sp2;
	_variableArray[12] = sp % 65536;
	_variableArray[13] = sp / 65536;
	writeval(str, (int)sp);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode05() {
	pcf((uint8)'\n');
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode06() {
	pmesd(varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode07() {
	int32 sp = varval();
	plocd((int)sp, varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode08() {
	int32 sp = varval();
	pobjd((int)sp, varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode09() {
	pmesd(varval());
	pcf((uint8)'\n');
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode10() {
	int32 sp = varval();
	plocd((int)sp, varval());
	pcf((uint8)'\n');
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode11() {
	int32 sp = varval();
	pobjd((int)sp, varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode12() {
	char bf[8];
	int a = 0;
	sprintf(bf,"%d", varval());
	while (bf[a])
		pcf(bf[a++]);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode13() {
	char bf[8];
	int a = 0;
	sprintf(bf,"%d", varval());
	while (bf[a])
		pcf(bf[a++]);
	pcf((uint8)'\n');
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode14() {
	clearWindow(_windowArray[_curWindow]);
	pcf((uint8)255);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode15() {
	int32 x = varval();
	if ((x < 0) || (x > 4))
		x = 0;

	pcf((unsigned char)254);
	_curWindow = x;
	_xofs = (8 * _windowArray[_curWindow]->textLength) / 6 + 1;
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode16() {
	int32 sp = varval();
	setScriptReturn((sp >= 0 && sp <= 4));
}

void AGOSEngine_PN::opn_lt() {
	int16 v1 = varval();
	int16 v2 = varval();
	setScriptReturn(v1 < v2);
}

void AGOSEngine_PN::opn_gt() {
	int16 v1 = varval();
	int16 v2 = varval();
	setScriptReturn(v1 > v2);
}

void AGOSEngine_PN::opn_eq() {
	int16 v1 = varval();
	int16 v2 = varval();
	setScriptReturn(v1 == v2);
}

void AGOSEngine_PN::opn_neq() {
	int16 v1 = varval();
	int16 v2 = varval();
	setScriptReturn(v1 != v2);
}

void AGOSEngine_PN::opn_opcode21() {
	setposition(_procnum, varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode22() {
	int pf[8];
	int n = varval();
	funcentry(pf, n);
	funccpy(pf);
	setposition(n, 0);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode23() {
	setScriptReturn(actCallD(varval()));
}

void AGOSEngine_PN::opn_opcode24() {
	popstack(kJmpClassNum);
	// Jump back to the last doline, which will return 2-1=1.
	// That value then is returned to actCallD, which once again
	// returns it. In the end, this amounts to a setScriptReturn(true)
	// (but possibly in a different level than the current one).
	_dolineReturnVal = 2;
	_tagOfActiveDoline = _stackbase->tagOfParentDoline;
}

void AGOSEngine_PN::opn_opcode25() {
	popstack(kJmpClassNum);
	// Jump back to the last doline, which will return 1-1=0.
	// That value then is returned to actCallD, which once again
	// returns it. In the end, this amounts to a setScriptReturn(false)
	// (but possibly in a different level than the current one).
	_dolineReturnVal = 1;
	_tagOfActiveDoline = _stackbase->tagOfParentDoline;
}

void AGOSEngine_PN::opn_opcode26() {
	while ((_stackbase != NULL) && (_stackbase->classnum != kJmpClassNum))
		dumpstack();
	dumpstack();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode27() {
	quitGame();
	// Make sure the quit event is processed immediately.
	delay(0);
}

void AGOSEngine_PN::opn_opcode28() {
	addstack(varval());
	_stackbase->tagOfParentDoline = _tagOfActiveDoline;
	setScriptReturn(false);
}

void AGOSEngine_PN::opn_opcode29() {
	popstack(varval());
	// Jump back to the last doline indicated by the top stackframe.
	// The -1 tells it to simply go on with its business.
	_dolineReturnVal = -1;
	_tagOfActiveDoline = _stackbase->tagOfParentDoline;
}

void AGOSEngine_PN::opn_opcode30() {
	_variableArray[1] = varval();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode31() {
	int a, slot = 0;
	char bf[60];

	if ((a = varval()) > 2) {
		setScriptReturn(false);
		return;
	}

	switch (a) {
		case 0:
			getFilename();
			slot = matchSaveGame(_saveFile, countSaveGames());
			strcpy(bf, genSaveName(slot));
			break;
		case 1:
			strcpy(bf, "pn.sav");
			break;
		case 2:
			// NOTE: Is this case ever used?
			error("opn_opcode31: case 2");
			break;
	}

	if (slot == -1) {
		setScriptReturn(false);
	} else {
		a = loadFile(bf);
		if (a)
			setScriptReturn(badload(a));
		else
			setScriptReturn(true);
	}
}

void AGOSEngine_PN::opn_opcode32() {
	char bf[60];
	int a, slot;

	a = varval();
	if (a > 2) {
		setScriptReturn(true);
		return;
	}

	uint16 curSlot = countSaveGames();
	switch (a) {
		case 0:
			getFilename();
			slot = matchSaveGame(_saveFile, curSlot);
			if (slot != -1)
				strcpy(bf, genSaveName(slot));
			else
				strcpy(bf, genSaveName(curSlot));
			break;
		case 1:
			strcpy(bf, "pn.sav");
			break;
		case 2:
			// NOTE: Is this case ever used?
			error("opn_opcode32: case 2");
			break;
	}

	a = saveFile(bf);
	setScriptReturn(a);
}

void AGOSEngine_PN::opn_opcode33() {
	setScriptReturn((varval() < 3) ? 1 : 0);
}

void AGOSEngine_PN::opn_opcode34() {
	uint16 msgNum1, msgNum2;
	varval();
	getResponse((int)_variableArray[166], (int)_variableArray[167], msgNum1, msgNum2);
	_variableArray[168]= msgNum1;
	_variableArray[169]= msgNum2;
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode35() {
	int a;
	uint8 *sav = _workptr;
	varval();
	a = varval();
	if ((a = gvwrd((uint8 *)_wordcp, a)) == -1) {
		setScriptReturn(false);
		return;
	}

	writeval(sav, a);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode36() {
	for (int i = 0; i < _dataBase[57] + 1; ++i)
		_wordcp[i] = 0;
	if (Common::isSpace(*_inpp))
		while ((*_inpp) && (Common::isSpace(*_inpp)))
			_inpp++;
	if (*_inpp == 0) {
		setScriptReturn(false);
		return;
	}
	_curwrdptr = _inpp;
	_wordcp[0] = *_inpp++;
	if ((_wordcp[0] == '.') || (_wordcp[0] == ',') || (_wordcp[0] == '"')) {
		setScriptReturn(true);
		return;
	}

	int ct = 1;
	while ((*_inpp != '.') && (*_inpp != ',') && (!Common::isSpace(*_inpp)) && (*_inpp != '\0') &&
		(*_inpp!='"')) {
		if (ct < _dataBase[57])
			_wordcp[ct++] = *_inpp;
		_inpp++;
	}
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode37() {
	_curwrdptr = NULL;

	_inputReady = true;
	interact(_inputline, 49);

	if ((_inpp = strchr(_inputline,'\n')) != NULL)
		*_inpp = '\0';
	_inpp = _inputline;
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode38() {
	_noScanFlag = 1;
	clearInputLine();
	writeval(_workptr, _keyPressed.ascii);
	_keyPressed.reset();
	_noScanFlag = 0;
	varval();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode39() {
	pcf((uint8)varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode40() {
	int a = doaction();
	if (_dolineReturnVal != 0)
		return;
	int b = doaction();
	setScriptReturn(a | b);
}

void AGOSEngine_PN::opn_opcode41() {
	int a = doaction();
	if (_dolineReturnVal != 0)
		return;
	int b = doaction();
	setScriptReturn(a & b);
}

void AGOSEngine_PN::opn_opcode42() {
	int a = doaction();
	if (_dolineReturnVal != 0)
		return;
	int b = doaction();
	setScriptReturn(a ^ b);
}

void AGOSEngine_PN::opn_opcode43() {
	int a = doaction();
	setScriptReturn(!a);
}

void AGOSEngine_PN::opn_opcode44() {
	pcf((uint8)254);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode45() {
	uint8 *myptr;
	int x;

	if (_havinit == 0) {
		_seed = (int16)getTime();
		_havinit = 1;
	}
	_seed = 1 + (75 * (_seed + 1) - 1) % 65537;
	myptr = _workptr;
	varval();
	x = varval();
	if (x == 0)
		error("Illegal range specified for RANDOM");
	writeval(myptr, (_seed % x));
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode46() {
	char *x = _curwrdptr;
	if (x == NULL) {
		setScriptReturn(true);
		return;
	}
	pcf(*x);
	if ((*x == '.') || (*x == '"') || (*x == ',')) {
		setScriptReturn(true);
		return;
	}
	x++;
	while ((*x != '.') && (*x != ',') && (*x != '"') && (!Common::isSpace(*x)) && (*x != '\0'))
		pcf(*x++);
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode47() {
	pmesd(varval() * 256 + varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode48() {
	pmesd(varval() * 256 + varval());
	pcf((uint8)'\n');
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode49() {
	setScriptReturn(findentry());
}

void AGOSEngine_PN::opn_opcode50() {
	_fnst = 0;
	setScriptReturn(findset());
}

void AGOSEngine_PN::opn_opcode51() {
	_fnst = varval();
	setScriptReturn(findset());
}

void AGOSEngine_PN::opn_opcode52() {
	int32 mode = varval();
	if (mode == 1) {
		setWindowImage(mode, varval(), true);
	} else {
		setWindowImageEx(mode, varval());
	}

	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode53() {
	vc27_resetSprite();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode54() {
	stopAnimate(varval());
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode55() {
	varval();
	varval();
	varval();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode56() {
	varval();
	varval();
	varval();
	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode57() {
	uint16 windowNum = varval();
	uint16 vgaSpriteId = varval();
	int16 x = varval();
	int16 y = varval();
	uint16 palette = varval();

	_videoLockOut |= 0x40;
	animate(windowNum, 0, vgaSpriteId, x, y, palette);
	_videoLockOut &= ~0x40;

	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode62() {
	int32 zoneNum = varval();

	_videoLockOut |= 0x80;

	vc29_stopAllSounds();

	_hitCalled = 0;
	_oneClick = 0;

	loadZone(zoneNum);

	setWindowImage(2, 2);

	_copyScnFlag = 0;
	_vgaSpriteChanged = 0;

	_videoLockOut &= ~0x80;

	setScriptReturn(true);
}

void AGOSEngine_PN::opn_opcode63() {
	int a = readfromline();
	switch (a) {
		case 65:
			setScriptReturn(inventoryOn(varval()));
			break;
		case 64:
			setScriptReturn((_videoLockOut & 0x10) != 0);
			break;
		case 63:
			setScriptReturn(inventoryOff());
			break;
		default:
			error("opn_opcode63: unknown code %d", a);
	}
}

int AGOSEngine_PN::inventoryOn(int val) {
	writeVariable(210, val);
	if (_videoLockOut & 0x10) {
		iconPage();
	} else {
		_videoLockOut |= 0x10;
		_hitAreaList = _invHitAreas;

		_windowArray[2]->textColor = 0;
		windowPutChar(_windowArray[2], 13);

		clearVideoWindow(4, 0);
		drawIconHitBar();

		_objects = _variableArray[211];
		_objectCountS = -1;
		iconPage();
	}
	return 1;
}

int AGOSEngine_PN::inventoryOff() {
	if (_videoLockOut & 0x10) {
		_windowArray[2]->textColor = 15;

		restoreBlock(48, 2, 272, 130);

		_hitAreaList = _hitAreas;
		_videoLockOut &= ~0x10;
		_vgaSpriteChanged++;
	}
	return 1;
}

// -----------------------------------------------------------------------
// Personal Nightmare Script Code
// -----------------------------------------------------------------------


int AGOSEngine_PN::bitextract(uint32 ptr, int offs) {
	const byte mask = 0x80 >> (offs % 8);
	return ((mask & _dataBase[ptr + offs / 8]) != 0);
}

uint16 AGOSEngine_PN::getptr(uint32 pos) {
	if (pos > _dataBaseSize)
		error("getptr: Read beyond EOF (%d)", pos);
	return (int)READ_LE_UINT16(_dataBase + pos);
}

uint32 AGOSEngine_PN::getlong(uint32 pos) {
	// Only actually reads 24bit though
	if (pos > _dataBaseSize)
		error("getlong: Read beyond EOF (%d)", pos);
	return (uint32)READ_LE_UINT24(_dataBase + pos);
}

int AGOSEngine_PN::varval() {
	int a;
	int b;

	a = readfromline();
	if (a < 247) {
		return a;
	}

	switch (a) {
		case 249:
			b = readfromline();
			return (int)(b + 256 * readfromline());
		case 250:
			return readfromline();
		case 251:
			return (int)_variableArray[varval()];
		case 252:
			b = varval();
			return (int)_dataBase[_quickptr[0] + b * _quickshort[0] + varval()];
		case 254:
			b = varval();
			return (int)_dataBase[_quickptr[3] + b * _quickshort[2] + varval()];
		case 247:
			b = varval();
			return (int)getptr(_quickptr[11] + (b * _quickshort[4]) + (2 * varval()));
		case 248:
			b = varval();
			return (int)getptr(_quickptr[12] + (b * _quickshort[5]) + (2 * varval()));
		case 253:
			b = varval();
			return bitextract((int32)_quickptr[1] + b * _quickshort[1], varval());
		case 255:
			b = varval();
			return bitextract((int32)_quickptr[4] + b * _quickshort[3], varval());
		default:
			error("VARVAL : Illegal code %d encountered", a);
	}
}

void AGOSEngine_PN::writeval(uint8 *ptr, int val) {
	uint8 *savpt = _workptr;
	int lsav = _linct, a, b, x;
	_workptr = ptr;
	_linct = 255;

	if ((a = readfromline()) < 247)
		error("writeval: Write to constant (%d)", a);

	switch (a) {
		case 249:
			error("writeval: Write to constant (%d)", a);
			break;
		case 250:
			error("writeval: Write to constant (%d)", a);
			break;
		case 251:
			_variableArray[varval()] = val;
			break;
		case 252:
			b = varval();
			_dataBase[_quickptr[0] + b * _quickshort[0] + varval()] = val;
			break;
		case 254:
			b = varval();
			_dataBase[_quickptr[3] + b * _quickshort[2] + varval()] = val;
			break;
		case 247:
			b = varval();
			x = _quickptr[11] + b * _quickshort[4] + varval() * 2;
			WRITE_LE_UINT16(_dataBase + x, val);
			break;
		case 248:
			b = varval();
			x = _quickptr[12] + b * _quickshort[5] + varval() * 2;
			WRITE_LE_UINT16(_dataBase + x, val);
			break;
		case 253:
			b = varval();
			setbitf((uint32)_quickptr[1] + b * _quickshort[1], varval(), val);
			break;
		case 255:
			b = varval();
			setbitf((uint32)_quickptr[4] + b * _quickshort[3], varval(), val);
			break;
		default:
			error("WRITEVAL : undefined evaluation %d", a);
	}
	_linct = lsav;
	_workptr = savpt;
}

void AGOSEngine_PN::setbitf(uint32 ptr, int offs, int val) {
	ptr += offs / 8;
	const byte mask = 0x80 >> (offs % 8);
	if (val != 0)
		_dataBase[ptr] |= mask;
	else
		_dataBase[ptr] &= ~mask;
}

int AGOSEngine_PN::actCallD(int n) {
	int pf[8];
	funcentry(pf, n);
	addstack(kJmpClassNum);
	funccpy(pf);
	setposition(n, 0);
	return doline(1);
}

int AGOSEngine_PN::doaction() {
	if (_linct == 0)
		return 0;

	_opcode = readfromline();

	if (_opcode > 63) {
		return (actCallD(_opcode - 64));
	}

	setScriptReturn(0);
	executeOpcode(_opcode);
	delay(0);

	return getScriptReturn();
}

int AGOSEngine_PN::doline(int needsave) {
	assert(!_stackbase == !needsave);

	int x;
	int myTag = ++_tagOfActiveDoline;	// Obtain a unique tag for this doline invocation
	_dolineReturnVal = 0;

	if (_stackbase && needsave)
		_stackbase->tagOfParentDoline = myTag;

	do {
		_linct = ((*_linebase) & 127) - 1;
		_workptr = _linebase + 1;
		if (*_linebase > 127) {
			x = varval();
			if (x != (int)_variableArray[1])
				goto skipln;
		}

		do {
			x = doaction();

			if (_dolineReturnVal != 0) {
				if (_tagOfActiveDoline != myTag)
					return 0;

				x = _dolineReturnVal;
				_dolineReturnVal = 0;

				if (x > 0) {
					if (x != 3)
						dumpstack();
					// Restore the active jmpbuf to its previous value,
					// then return _dolineReturnVal-1 (will be 2-1=1 or 1-1=0).
					_tagOfActiveDoline = myTag - 1;
					return (x - 1);
				}
			}

		} while (x && !shouldQuit());

skipln:
		_linebase += 127 & *_linebase;
		_linembr++;
	} while (!shouldQuit());

	return 0;
}

int AGOSEngine_PN::findentry() {
	int stepmt;
	int curObj = 0;
	uint32 ofs = _quickptr[11];
	int c1, c2;

	c1 = varval();
	c2 = varval();
	stepmt = _quickshort[4];

	while (curObj < _quickshort[6]) {
		if (((c1 == 255) || (c1 == getptr(ofs))) &&
			(c2 == getptr(ofs + 2))) {
				_variableArray[23] = curObj;
				return 1;
		}
		curObj++;
		ofs += stepmt;
	}
	return 0;
}

int AGOSEngine_PN::findset() {
	int curObj = _fnst;
	int c1, c2, c3, c4;
	int stepmt = _quickshort[4];
	uint32 ofs = _quickptr[11] + stepmt * curObj;
	c1 = varval();
	c2 = varval();
	c3 = varval();
	c4 = varval();
	while (curObj < _quickshort[6]) {
		if (((c1 ==255) || (c1 == getptr(ofs))) &&
			((c2 == 255) || (c2 == getptr(ofs + 2))) &&
			((c3 == 255) || (c3 == getptr(ofs + 4))) &&
			((c4 == 255) || (c4 == getptr(ofs + 6)))) {
				_variableArray[23] = curObj;
				_fnst = curObj + 1;
				return 1;
		}
		curObj++;
		ofs += stepmt;
	}
	return 0;
}

void AGOSEngine_PN::funccpy(int *store) {
	for (int i = 24; i < 32; ++i) {
		_variableArray[i] = *store++;
	}
}

void AGOSEngine_PN::funcentry(int *store, int procn) {
	int numParams = _dataBase[getlong(_quickptr[6] + 3 * procn)];
	for (int i = 0; i < numParams; ++i) {
		*store++ = varval();
	}
}

int AGOSEngine_PN::gvwrd(uint8 *wptr, int mask) {
	int val = 0, code = 0, q = _dataBase[57];
	uint8 *vocbase = _dataBase + getlong(15);
	while (*vocbase != 255) {
		if (*vocbase < 0x80) {
			val = vocbase[q] + 256 * vocbase[q + 1];
			code = vocbase[q + 2];
		}
		if (wrdmatch(vocbase, mask, wptr, code))
			return val;
		vocbase += (*vocbase > 127) ? q : q + 3;
	}
	return -1;
}


int AGOSEngine_PN::setposition(int process, int line) {
	uint8 *ourptr;
	int np;
	int ct;

	ourptr = _dataBase + getlong(_quickptr[6] + 3 * process);
	np = *ourptr++;
	for (ct = 0; ct < line; ++ct) {
		ourptr += (127 & *ourptr);
	}

	while (true) {
		_linebase = ourptr;
		_linct = (127 & *ourptr) - 1;
		if (*ourptr++ <= 127)
			break;

		ct = varval();
		if (ct == (int)_variableArray[1])
			break;

		ourptr += _linct - 1;
		line++;
	}

	_linembr = line;
	_procnum = process;
	_variableArray[0] = process;
	_workptr = ourptr;
	return np;
}

int AGOSEngine_PN::wrdmatch(uint8 *word1, int mask1, uint8 *word2, int mask2) {
	uint8 sv;

	if ((mask1 & mask2) == 0)
		return 0;

	sv = *word1;
	*word1 &= 127;
	if (scumm_strnicmp((const char *)word1, (const char *)word2, _dataBase[57])) {
		*word1 = sv;
		return 0;
	}
	*word1 = sv;
	return 1;
}

// -----------------------------------------------------------------------
// Personal Nightmare Stack Code
// -----------------------------------------------------------------------

void AGOSEngine_PN::addstack(int type) {
	StackFrame *a;
	int i;

	a = (StackFrame *)calloc(1, sizeof(StackFrame));
	if (a == NULL)
		error("addstack: Out of memory - stack overflow");

	a->nextframe = _stackbase;
	_stackbase = a;

	for (i = 0; i < 6; ++i)
		a->flag[i] = _variableArray[i];
	for (i = 0; i < 8; ++i)
		a->param[i] = _variableArray[24 + i];
	a->classnum = type;
	a->ll = _linct;
	a->linenum = _linembr;
	a->linpos = _workptr;
	a->lbase = _linebase;
	a->process = _procnum;
}

void AGOSEngine_PN::dumpstack() {
	StackFrame *a;

	if (_stackbase == NULL)
		error("dumpstack: Stack underflow or unknown longjmp");

	a = _stackbase->nextframe;
	free((char *)_stackbase);
	_stackbase = a;
}

void AGOSEngine_PN::popstack(int type) {
	int i = 0;

	while ((_stackbase != NULL) && (_stackbase->classnum != type)) {
		dumpstack();
		++i;
	}

	if (_stackbase == NULL)
		error("popstack: Stack underflow or unknown longjmp");

	_linct = _stackbase->ll;
	_linebase = _stackbase->lbase;
	_workptr = _stackbase->linpos;
	_procnum = _stackbase->process;
	_linembr = _stackbase->linenum;
	for (i = 0; i < 6; ++i)
		_variableArray[i] = _stackbase->flag[i];
	for (i = 0; i < 8; ++i)
		_variableArray[24 + i] = _stackbase->param[i];
}

} // End of namespace AGOS