// Tabs are 3 wide.

var debug = false;

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// StackFrame object //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

function StackFrame(nextIp, localsCount, storeReturn) {
	this.nextIp			= nextIp;
	this.localsCount	= localsCount;
	this.locals			= [];
	this.stack			= [];
	this.storeReturn	= storeReturn;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////// Memory ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

var	addrAbbrevTbl;
var	addrDict;
var	addrEntryPoint;
var	addrGlobalTbl;
var	addrHighMem;
var	addrObjTbl;
var	addrStaticMem;
var	memory;
var	memoryLength;

////////////////////////////////////////////////////////////////////////////////

function assert(e, why) {
	if (!e)
		throw "Assertion failure: " + why;
}

////////////////////////////////////////////////////////////////////////////////

function initMemory(data, len) {
	var	i1, i2, c1, c2, c3, c4, d1, d2, d3, off;

	memory			= new Array(len);
	memoryLength	= len;

	for (i1 = i2 = off = 0 ; off < len ; ) {
		c1 = data[i1].charCodeAt(i2++) - 0x30;
		c2 = data[i1].charCodeAt(i2++) - 0x30;
		c3 = data[i1].charCodeAt(i2++) - 0x30;
		c4 = data[i1].charCodeAt(i2++) - 0x30;
		if (i2 == 400)
			i2 = 0, ++i1;

		d1 = (c1 << 2) | (c2 >>> 4);
		d2 = ((c2 << 4) | (c3 >>> 2)) & 0xff;
		d3 = ((c3 << 6) | c4) & 0xff;

		memory[off++] = d1;
		if (off < len)
			memory[off++] = d2;
		if (off < len)
			memory[off++] = d3;
	}

	addrAbbrevTbl	= readWord(0x18);
	addrDict			= readWord(0x08);
	addrEntryPoint	= readWord(0x06);
	addrGlobalTbl	= readWord(0x0c);
	addrHighMem		= readWord(0x04);
	addrObjTbl		= readWord(0x0a) + 53;
	addrStaticMem	= readWord(0x0e);

	memory[1] |= 0x20;
}

////////////////////////////////////////////////////////////////////////////////

function makeSigned(v) {
	return (v & 0x8000) ? (v - 0x10000) : v;
}

////////////////////////////////////////////////////////////////////////////////

function getAddrObj(objNo) {
	return addrObjTbl + (objNo * 9);
}

////////////////////////////////////////////////////////////////////////////////

function getObjAttrib(objNo, attrNo) {
	return ((memory[addrObjTbl + (objNo * 9) + (attrNo >>> 3)] & (0x80 >>> (attrNo & 7))) != 0);
}

////////////////////////////////////////////////////////////////////////////////

function getObjChild(objNo) {
	return memory[addrObjTbl + (objNo * 9) + 6];
}

////////////////////////////////////////////////////////////////////////////////

function getObjName(objNo) {
	return getObjPropAddr(objNo) + 1;
}

////////////////////////////////////////////////////////////////////////////////

function getObjParent(objNo) {
	return memory[addrObjTbl + (objNo * 9) + 4];
}

////////////////////////////////////////////////////////////////////////////////

function getObjPropAddr(objNo) {
	return readWord(addrObjTbl + (objNo * 9) + 7);
}

////////////////////////////////////////////////////////////////////////////////

function getObjSibling(objNo) {
	return memory[addrObjTbl + (objNo * 9) + 5];
}

////////////////////////////////////////////////////////////////////////////////

function getProp(objNo, propNo) {
	var	addr;

	addr = getPropAddr(objNo, propNo);

	if (addr == 0) {
		assert(propNo > 0, "bad propNo");

		return readWord(addrObjTbl - 55 + propNo * 2);
	}

	switch (getPropLen(addr)) {
		case 1:
			return memory[addr];

		case 2:
			return readWord(addr);
	}

	throw "getProp: bad length";
}

////////////////////////////////////////////////////////////////////////////////

function getPropAddr(objNo, propNo) {
	var	addr, textLen, size, pnum, psize;

	addr		= getObjPropAddr(objNo);
	textLen	= memory[addr];
	addr		+= (textLen << 1) + 1;

	while ((size = memory[addr++]) != 0)
		if ((size & 0x1f) == propNo)
			return addr;
		else
			addr += ((size >>> 5) & 0x07) + 1;

	return 0;
}

////////////////////////////////////////////////////////////////////////////////

function getPropLen(addr) {
	return (((memory[addr - 1] >>> 5) & 0x07) + 1);
}

////////////////////////////////////////////////////////////////////////////////

function getPropNext(objNo, propNo) {
	var	addr, textLen;

	if (propNo == 0) {
		addr		= getObjPropAddr(objNo);
		textLen	= memory[addr];
		addr		+= (textLen << 1) + 1;
	} else {
		if ((addr = getPropAddr(objNo, propNo)) == 0)
			return 0;

		addr		+= getPropLen(addr);
	}

	return (memory[addr] & 0x1f)
}

////////////////////////////////////////////////////////////////////////////////

function insertObj(objNo, parent) {
	removeObj(objNo);

	setObjSibling(objNo, getObjChild(parent));
	setObjChild(parent, objNo);
	setObjParent(objNo, parent);
}

////////////////////////////////////////////////////////////////////////////////

function readWord(addr) {
	return ((memory[addr] << 8) | memory[addr + 1]);
}

////////////////////////////////////////////////////////////////////////////////

function removeObj(objNo) {
	var	parent, sibling, parentChild;

	parent = getObjParent(objNo);
	if (parent == 0)
		return;

	sibling		= getObjSibling(objNo);
	parentChild	= getObjChild(parent);

	if (parentChild == objNo)
		setObjChild(parent, sibling);
	else {
		var	prev, obj;

		prev	= parentChild;
		obj	= getObjSibling(prev);

		while (obj != 0) {
			if (obj == objNo)
				break;

			prev	= obj;
			obj	= getObjSibling(prev);
		}

		if (obj == 0) {
			throw "Bad object";
		}

		setObjSibling(prev, sibling);
	}

	setObjParent(objNo, 0);
	setObjSibling(objNo, 0);
}

////////////////////////////////////////////////////////////////////////////////

function setObjAttrib(objNo, attrNo, set) {
	var	addr, value;

	addr	= addrObjTbl + (objNo * 9) + (attrNo >>> 3);
	value	= memory[addr];

	if (set)
		value |= (0x80 >>> (attrNo & 7));
	else
		value &= ~(0x80 >>> (attrNo & 7));

	memory[addr] = value;
}

////////////////////////////////////////////////////////////////////////////////

function setObjChild(objNo, child) {
	memory[addrObjTbl + (objNo * 9) + 6] = child;
}

////////////////////////////////////////////////////////////////////////////////

function setObjParent(objNo, parent) {
	memory[addrObjTbl + (objNo * 9) + 4] = parent;
}

////////////////////////////////////////////////////////////////////////////////

function setObjSibling(objNo, sibling) {
	memory[addrObjTbl + (objNo * 9) + 5] = sibling;
}

////////////////////////////////////////////////////////////////////////////////

function setProp(objNo, propNo, value) {
	var	addr;

	addr = getPropAddr(objNo, propNo);
	if (addr == 0)
		throw "Bad property";

	if (getPropLen(addr) == 1)
		memory[addr] = value;
	else
		writeWord(addr, value);
}

////////////////////////////////////////////////////////////////////////////////

function writeWord(addr, data) {
	memory[addr] = data >> 8;
	memory[addr + 1] = data & 255;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////// Output handling ///////////////////////////////
////////////////////////////////////////////////////////////////////////////////

var	cmdSplit;
var	currentLine;
var	currentWord;
var	lines;
var	parseBuffer;
var	playerInput;
var	playerPrompt;
var	spaces			= "                                                                                ";
var	textBuffer;
var	theScreen;

////////////////////////////////////////////////////////////////////////////////

function drawScreen() {
	var	s, loc, score, l, pr;

	s		= "<pre><code>";

	loc	= getZString(getObjName(getVariable(0x10)));
	score	= "Score: " + getVariable(0x11) + " Turns: " + getVariable(0x12);

	if (loc.length + score.length + 5 > 80)
		loc = loc.substr(0, 72 - score.length) + "...";

	s += loc;
	s += spaces.substr(0, 80 - (loc.length + score.length));
	s += score;

	for (l = 0 ; l < lines.length ; ++l) {
		var	line = lines[l];

		if (line.length == 0)
			line = "&nbsp;";
		else
			line = line.replace(/&/g, "&amp;").replace(/</g, "&lt;");

		s += "<br>" + line;
	}

	s += "</code></pre>";

	theScreen.innerHTML = s;

	pr = currentLine + currentWord;
	pr = pr.replace(/&/g, "&amp;").replace(/</g, "&lt;");

	playerPrompt.innerHTML = pr;
}

////////////////////////////////////////////////////////////////////////////////

function findDictWord(word) {
	var	n, addr, entryLen, i;

	if (word.length > 6)
		word = word.substr(0, 6);

	n = memory[addrDict];
	addr = addrDict + 1 + n;

	entryLen = memory[addr++];
	n = readWord(addr);
	addr += 2;

	for (i = 0 ; i < n ; ++i) {
		if (getZString(addr) == word)
			return addr;

		addr += entryLen;
	}

	return 0;
}

////////////////////////////////////////////////////////////////////////////////

function initScreen() {
	var	l, splitString, n, i;

	lines = [];
	for (l = 0 ; l < (debug ? 500 : 24) ; ++l)
		lines[l] = "";

	abbrev			= 0;
	alphabet			= 0;
	currentLine		= "";
	currentWord		= "";
	triChar			= -1;
	triCharVal		= 0;

	playerInput		= document.forms[0].elements[0];
	playerPrompt	= document.getElementById("prompt");
	theScreen		= document.getElementById("screen");

	splitString = "[ ";
	n = memory[addrDict];
	for (i = 0 ; i < n ; ++i) {
		var	c = String.fromCharCode(memory[addrDict + 1 + i]);

		splitString += c;
	}
	splitString += "]";
	cmdSplit = new RegExp(splitString);
}

////////////////////////////////////////////////////////////////////////////////

function newLine() {
	var	l;

	for (l = 0 ; l < lines.length - 1 ; ++l)
		lines[l] = lines[l + 1];

	lines[lines.length - 1] = currentLine;
	currentLine = "";
}

////////////////////////////////////////////////////////////////////////////////

function print(string) {
	var	i;

	for (i = 0 ; i < string.length ; ++i)
		printChar(string.charAt(i));
}

////////////////////////////////////////////////////////////////////////////////

function printChar(c) {
	if (c == "^") {
		if (currentLine.length > 0 && currentLine.length + currentWord.length >= 80)
			newLine();

		currentLine += currentWord;
		currentWord = "";
		newLine();
	} else if (c == " ") {
		if (currentLine.length > 0 && currentLine.length + currentWord.length >= 80)
			newLine();

		currentLine += currentWord;
		currentWord = "";

		if (currentLine.length < 80)
			currentLine += " ";
		else
			newLine();
	} else {
		currentWord += c;
	}
}

////////////////////////////////////////////////////////////////////////////////

function processInput() {
	if (state == WAITINPUT) {
		var	inp = playerInput.value.toLowerCase(), wordOff, words = [], wordOffs = [], bufLen, i, setDebug = false;

		playerInput.value = "";

		inp = inp.replace(/\?/g, " ");

		if (inp.length > 0 && inp.charAt(0) == "!") {
			setDebug = true;
			inp = inp.substr(1);
		}

		print(" " + inp);
		printChar("^");

		bufLen = memory[textBuffer];
		if (bufLen > inp.len) {
			alert("Too many characters: input truncated");
			bufLen = inp.len;
		}
		for (i = 0 ; i < bufLen ; ++i)
			memory[textBuffer + 1 + i] = inp.charCodeAt(i);
		memory[textBuffer + 1 + bufLen] = 0;

		wordOff = 0;

		while (true) {
			for (i = 0 ; i < inp.length ; ++i)
				if (inp.charAt(i) != " ")
					break;

			if (i == inp.length)
				break;
			else if (i > 0) {
				inp = inp.substr(i);
				wordOff += i;
			}

			i = inp.search(cmdSplit);

			if (i > 0) {
				words[words.length] = inp.substr(0, i);
				wordOffs[wordOffs.length] = wordOff;
				inp = inp.substr(i);
				wordOff += i;
			} else if (i < 0) {
				words[words.length] = inp;
				wordOffs[wordOffs.length] = wordOff;
				inp = "";
			} else {
				words[words.length] = inp.charAt(0);
				wordOffs[wordOffs.length] = wordOff;
				inp = inp.substr(1);
				wordOff += 1;
			}
		}

		bufLen = memory[parseBuffer];
		if (words.length > bufLen) {
			alert("Too many words: input truncated");
			words.length = bufLen;
		} else if (bufLen > words.length)
			bufLen = words.length;

		memory[parseBuffer + 1] = bufLen;

		for (i = 0 ; i < bufLen ; ++i) {
			writeWord(parseBuffer + 2 + i * 4, findDictWord(words[i]));
			memory[parseBuffer + 4 + i * 4] = words[i].length;
			memory[parseBuffer + 5 + i * 4] = wordOffs[i] + 1;
		}

		curIp = nextIp;

		debug = setDebug;

		if (debug)
			for (i = lines.length ; i < 500 ; ++i)
				lines[i] = "";

		runZMachine();
	}
}

////////////////////////////////////////////////////////////////////////////////

function setInputStream(number) {
	alert("setInputStream(" + number + ")");
}

////////////////////////////////////////////////////////////////////////////////

function setOutputStream(number) {
	alert("setOutputStream(" + number + ")");
}

////////////////////////////////////////////////////////////////////////////////

function setWindow(window) {
	alert("setWindow(" + window + ")");
}

////////////////////////////////////////////////////////////////////////////////

function splitWindow(nLines) {
	alert("splitWindow(" + nLines + ")");
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////// ZMachine //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

var	EXECUTE			= 1;
var	WAITINPUT		= 2;
var	WAITENTER		= 3;

var	abbrev;
var	alphabet;
var	branchReversed;
var	branchOff;
var	curIp;
var	hasBranch		= [];
var	hasStoreByte	= [];
var	instructions	= [];
var	nextIp;
var	opCode;
var	opCodeStore;
var	operandCount;
var	operands			= [ 0, 0, 0, 0 ];
var	operandTypes	= [ 0, 0, 0, 0 ];
var	printAddr;
var	stack				= [];
var	triChar;
var	triCharValue;
var	zscii				= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ  0123456789.,!?_#'\"/\\-:()";

function initZMachine(data, len) {
	var	i;

	initMemory(data, len);

	initScreen();

	instructions[0x00]	= i0_rtrue;
	instructions[0x01]	= i0_rfalse;
	instructions[0x02]	= i0_print;
	instructions[0x03]	= i0_print_ret;
	instructions[0x04]	= i0_nop;
	instructions[0x05]	= i0_save;
	instructions[0x06]	= i0_restore;
	instructions[0x07]	= i0_restart;
	instructions[0x08]	= i0_ret_popped;
	instructions[0x09]	= i0_pop;
	instructions[0x0a]	= i0_quit;
	instructions[0x0b]	= i0_new_line;
	instructions[0x0c]	= i0_show_status;
	instructions[0x0d]	= i0_verify;
	instructions[0x0f]	= i0_piracy;
	instructions[0x20]	= i1_jz;
	instructions[0x21]	= i1_get_sibling;
	instructions[0x22]	= i1_get_child;
	instructions[0x23]	= i1_get_parent;
	instructions[0x24]	= i1_get_prop_len;
	instructions[0x25]	= i1_inc;
	instructions[0x26]	= i1_dec;
	instructions[0x27]	= i1_print_addr;
	instructions[0x29]	= i1_remove_obj;
	instructions[0x2a]	= i1_print_obj;
	instructions[0x2b]	= i1_ret;
	instructions[0x2c]	= i1_jump;
	instructions[0x2d]	= i1_print_paddr;
	instructions[0x2e]	= i1_load;
	instructions[0x2f]	= i1_not;
	instructions[0x41]	= i2_je;
	instructions[0x42]	= i2_jl;
	instructions[0x43]	= i2_jg;
	instructions[0x44]	= i2_dec_chk;
	instructions[0x45]	= i2_inc_chk;
	instructions[0x46]	= i2_jin;
	instructions[0x47]	= i2_test;
	instructions[0x48]	= i2_or;
	instructions[0x49]	= i2_and;
	instructions[0x4a]	= i2_test_attr;
	instructions[0x4b]	= i2_set_attr;
	instructions[0x4c]	= i2_clear_attr;
	instructions[0x4d]	= i2_store;
	instructions[0x4e]	= i2_insert_obj;
	instructions[0x4f]	= i2_loadw;
	instructions[0x50]	= i2_loadb;
	instructions[0x51]	= i2_get_prop;
	instructions[0x52]	= i2_get_prop_addr;
	instructions[0x53]	= i2_get_next_prop;
	instructions[0x54]	= i2_add;
	instructions[0x55]	= i2_sub;
	instructions[0x56]	= i2_mul;
	instructions[0x57]	= i2_div;
	instructions[0x58]	= i2_mod;
	instructions[0x60]	= iV_call;
	instructions[0x61]	= iV_storew;
	instructions[0x62]	= iV_storeb;
	instructions[0x63]	= iV_put_prop;
	instructions[0x64]	= iV_sread;
	instructions[0x65]	= iV_print_char;
	instructions[0x66]	= iV_print_num;
	instructions[0x67]	= iV_random;
	instructions[0x68]	= iV_push;
	instructions[0x69]	= iV_pull;
	instructions[0x6a]	= iV_split_window;
	instructions[0x6b]	= iV_set_window;
	instructions[0x73]	= iV_output_stream;
	instructions[0x74]	= iV_input_stream;

	hasStoreByte[0x21]	= true;
	hasStoreByte[0x22]	= true;
	hasStoreByte[0x23]	= true;
	hasStoreByte[0x24]	= true;
	hasStoreByte[0x28]	= true;
	hasStoreByte[0x2e]	= true;
	hasStoreByte[0x2f]	= true;
	hasStoreByte[0x48]	= true;
	hasStoreByte[0x49]	= true;
	hasStoreByte[0x4f]	= true;
	hasStoreByte[0x50]	= true;
	hasStoreByte[0x51]	= true;
	hasStoreByte[0x52]	= true;
	hasStoreByte[0x53]	= true;
	hasStoreByte[0x54]	= true;
	hasStoreByte[0x55]	= true;
	hasStoreByte[0x56]	= true;
	hasStoreByte[0x57]	= true;
	hasStoreByte[0x58]	= true;
	hasStoreByte[0x59]	= true;
	hasStoreByte[0x60]	= true;
	hasStoreByte[0x67]	= true;

	hasBranch[0x05]		= true;
	hasBranch[0x06]		= true;
	hasBranch[0x0d]		= true;
	hasBranch[0x0f]		= true;
	hasBranch[0x20]		= true;
	hasBranch[0x21]		= true;
	hasBranch[0x22]		= true;
	hasBranch[0x41]		= true;
	hasBranch[0x42]		= true;
	hasBranch[0x43]		= true;
	hasBranch[0x44]		= true;
	hasBranch[0x45]		= true;
	hasBranch[0x46]		= true;
	hasBranch[0x47]		= true;
	hasBranch[0x4a]		= true;
	hasBranch[0x77]		= true;
	hasBranch[0x7f]		= true;

	curIp						= addrEntryPoint;
	stack[0]					= new StackFrame(-1, 0, -1);
	stack.length			= 1;
	stackTop					= stack[0];

	playerInput.focus();
}

////////////////////////////////////////////////////////////////////////////////

function decodeInstruction() {
	var	addr = curIp, b1, b2, opCodeForm, o;

	opCode = memory[addr++];

	if (debug)
		print("ip = " + curIp + ", opCode = " + opCode);

	if ((opCode & 0xc0) == 0xc0) {
		// variable form opcode

		opCodeForm = ((opCode & 0x20) == 0x20) ? 3 : 2;

		opCode &= 0x1f;

		b1 = memory[addr++];
		operandTypes[0]	= (b1 >> 6) & 3;
		operandTypes[1]	= (b1 >> 4) & 3;
		operandTypes[2]	= (b1 >> 2) & 3;
		operandTypes[3]	= b1 & 3;

		for (operandCount = 0 ; operandCount < 4 ; ++operandCount)
			if (operandTypes[operandCount] == 3)
				break;
	} else if ((opCode & 0xc0) == 0x80) {
		// short form

		operandTypes[0]	= (opCode >> 4) & 3;
		operandCount		= (operandTypes[0] == 3) ? 0 : 1;
		opCodeForm			= operandCount;

		opCode &= 0x0f;
	} else {
		// long form

		operandCount		= 2;
		operandTypes[0]	= ((opCode & 0x40) == 0x40) ? 2 : 1;
		operandTypes[1]	= ((opCode & 0x20) == 0x20) ? 2 : 1;
		opCodeForm			= 2;

		opCode &= 0x1f;
	}

	opCode |= (opCodeForm << 5);

	if (debug)
		print(", index = " + opCode + "^");

	for (o = 0 ; o < operandCount ; ++o) {
		switch (operandTypes[o]) {
			case 0:	// long constant
				operands[o] = readWord(addr);
				addr += 2;
				break;

			case 1:	// short constant
				operands[o] = memory[addr++];
				break;

			case 2:	// value of variable
				operands[o] = getVariable(memory[addr++]);
				break;
		}
	}

	if (hasStoreByte[opCode])
		opCodeStore = memory[addr++];

	if (hasBranch[opCode]) {
		b1 = memory[addr++];
		branchReversed = ((b1 & 0x80) == 0);
		if (b1 & 0x40)
			branchOff = (b1 & 0x3f);
		else {
			b2 = memory[addr++];
			branchOff = (((b1 & 0x3f) << 8) | b2);
			if (branchOff & 0x2000)
				branchOff |= 0xc000;
		}
	}

	if (opCode == 2 || opCode == 3) {	// print || print_ret
		printAddr = addr;

		while ((memory[addr] & 0x80) == 0)
			addr += 2;

		addr += 2;
	}

	if (debug && operandCount > 0) {
		print("operands = [ ");
		for (o = 0 ; o < operandCount ; ++o) {
			if (o > 0)
				print(", ");

			if (operands[o] || operands[o] == 0)
				print(operands[o].toString());
		}
		print(" ]");

		if (hasStoreByte[opCode])
			print(" (store=" + opCodeStore + ")");

		if (hasBranch[opCode])
			print(" (" + (branchReversed ? "!" : "") + "branch=" + makeSigned(branchOff) + ")");

		newLine();
	}

	nextIp = addr;
}

////////////////////////////////////////////////////////////////////////////////

function doBranch() {
	if (branchReversed) {
		if (debug) {
			print("!doBranch");
			newLine();
		}
	} else {
		if (debug) {
			print("doBranch " + branchOff);
			newLine();
		}

		switch (branchOff) {
			case 0:
				doReturn(0);
				break;

			case 1:
				doReturn(1);
				break;

			default:
				curIp = nextIp + makeSigned(branchOff) - 2;
				break;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////

function dontBranch() {
	if (branchReversed) {
		switch (branchOff) {
			case 0:
				doReturn(0);
				break;

			case 1:
				doReturn(1);
				break;

			default:
				curIp = nextIp + makeSigned(branchOff) - 2;
				break;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////

function doReturn(returnValue) {
	var	top;
	
	top = stackTop;

	stack.length -= 1;
	stackTop = stack[stack.length - 1];

	setVariable(top.storeReturn, returnValue);

	curIp = top.nextIp;
}

////////////////////////////////////////////////////////////////////////////////

function getVariable(v) {
	if (v == 0) {
		if (stackTop.stack.length == 0)
			throw "Stack underflow";

		v = stackTop.stack[stackTop.stack.length - 1];
		stackTop.stack.length -= 1;

		if (debug) {
			print("getVariable(" + v + ") = " + v.toString());

			if (stackTop.stack.length == 0)
				print(" (stack empty)");

			newLine();
		}

		return v;
	} else if (v < 0x10) {
		if (debug) {
			print("getVariable(" + v + ") = " + stackTop.locals[v - 1]);
			newLine();
		}

		return stackTop.locals[v - 1];
	} else {
		if (debug) {
			print("getVariable(" + v + ") = " + readWord(addrGlobalTbl + (v - 0x10) * 2));
			newLine();
		}

		return readWord(addrGlobalTbl + (v - 0x10) * 2);
	}
}

////////////////////////////////////////////////////////////////////////////////

function getZChar(c) {
	if (abbrev > 0) {
		var	abbrAddr, s;

		assert(triChar < 0, "triChar < 0");

		abbrAddr = ((abbrev - 1) << 5) + c;
		abbrev = 0;

		s = getZString(readWord(addrAbbrevTbl + (abbrAddr * 2)) * 2);

		alphabet = 0;

		return s;
	} else if (triChar == 0) {
		triCharValue = c << 5;
		triChar = 1;
	} else if (triChar == 1) {
		triCharValue += c;
		triChar = -1;

		return String.fromCharCode(triCharValue);
	} else if (c == 0)
		return " ";
	else if (c == 1 || c == 2 || c == 3)
		abbrev = c;
	else if (c == 4)
		alphabet = 1;
	else if (c == 5)
		alphabet = 2;
	else if (alphabet == 2 && c == 6)
		triChar = 0;
	else if (alphabet == 2 && c == 7)
		return "^";
	else {
		c = zscii.charAt((alphabet * 26) + (c - 6));
		
		alphabet = 0;
		
		return c;
	}

	return "";
}

////////////////////////////////////////////////////////////////////////////////

function getZString(addr) {
	var	s = "", w = 0;

	alphabet = 0;

	while ((w & 0x8000) == 0) {
		w = readWord(addr);
		addr += 2;

		s += getZChar((w >>> 10) & 0x1f);
		s += getZChar((w >>> 5) & 0x1f);
		s += getZChar(w & 0x1f);
	}

	return s;
}

////////////////////////////////////////////////////////////////////////////////

function i0_new_line() {
	if (!debug)
		printChar("^");
}

////////////////////////////////////////////////////////////////////////////////

function i0_nop() {
}

////////////////////////////////////////////////////////////////////////////////

function i0_piracy() {
	doBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i0_pop() {
	getVariable(0);
}

////////////////////////////////////////////////////////////////////////////////

function i0_print() {
	if (!debug)
		print(getZString(printAddr));
}

////////////////////////////////////////////////////////////////////////////////

function i0_print_ret() {
	if (!debug) {
		print(getZString(printAddr));
		printChar("^");
	}

	doReturn(1);
}

////////////////////////////////////////////////////////////////////////////////

function i0_quit() {
	alert("quit");
}

////////////////////////////////////////////////////////////////////////////////

function i0_restart() {
	alert("restart");
}

////////////////////////////////////////////////////////////////////////////////

function i0_restore() {
	alert("restore");
}

////////////////////////////////////////////////////////////////////////////////

function i0_ret_popped() {
	doReturn(getVariable(0));
}

////////////////////////////////////////////////////////////////////////////////

function i0_rfalse() {
	doReturn(0);
}

////////////////////////////////////////////////////////////////////////////////

function i0_rtrue() {
	doReturn(1);
}

////////////////////////////////////////////////////////////////////////////////

function i0_save() {
	alert("save");
}

////////////////////////////////////////////////////////////////////////////////

function i0_show_status() {
	drawScreen();
}

////////////////////////////////////////////////////////////////////////////////

function i0_verify() {
	// TODO: do verification
	doBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i1_dec() {
	setVariable(operands[0], (getVariable(operands[0]) - 1) & 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i1_get_child() {
	var	c;

	c = getObjChild(operands[0]);

	setVariable(opCodeStore, c);

	if (c != 0)
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i1_get_parent() {
	setVariable(opCodeStore, getObjParent(operands[0]));
}

////////////////////////////////////////////////////////////////////////////////

function i1_get_prop_len() {
	setVariable(opCodeStore, getPropLen(operands[0]));
}

////////////////////////////////////////////////////////////////////////////////

function i1_get_sibling() {
	var	c;

	c = getObjSibling(operands[0]);

	setVariable(opCodeStore, c);

	if (c != 0)
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i1_inc() {
	setVariable(operands[0], (getVariable(operands[0]) + 1) & 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i1_jump() {
	curIp = nextIp + makeSigned(operands[0]) - 2;
}

////////////////////////////////////////////////////////////////////////////////

function i1_jz() {
	if (operands[0] == 0)
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i1_load() {
	setVariable(opCodeStore, getVariable(operands[0]));
}

////////////////////////////////////////////////////////////////////////////////

function i1_not() {
	setVariable(opCodeStore, operands[0] ^ 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i1_remove_obj() {
	removeObj(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function i1_print_addr() {
	if (!debug)
		print(getZString(operands[0]));
}

////////////////////////////////////////////////////////////////////////////////

function i1_print_obj() {
	if (!debug)
		print(getZString(getObjName(operands[0])));
}

////////////////////////////////////////////////////////////////////////////////

function i1_print_paddr() {
	if (!debug)
		print(getZString(operands[0] * 2));
}

////////////////////////////////////////////////////////////////////////////////

function i1_ret() {
	doReturn(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_add() {
	setVariable(opCodeStore, (operands[0] + operands[1]) & 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i2_and() {
	setVariable(opCodeStore, operands[0] & operands[1]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_clear_attr() {
	setObjAttrib(operands[0], operands[1], false);
}

////////////////////////////////////////////////////////////////////////////////

function i2_dec_chk() {
	var	val, chk;

	val = (getVariable(operands[0]) - 1) & 0xffff;
	setVariable(operands[0], val);

	if (makeSigned(val) < makeSigned(operands[1]))
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_div() {
	var	val, div, isSigned = false;

	val = operands[0];
	if (val & 0x8000) {
		val = (val ^ 0xffff) + 1;
		isSigned = !isSigned;
	}
	div = operands[1];
	if (div & 0x8000) {
		div = (div & 0xffff) + 1;
		isSigned = !isSigned;
	}

	val = Math.floor(val / div);
	if (isSigned)
		val = ((val ^ 0xffff) + 1);

	setVariable(opCodeStore, val);
}

////////////////////////////////////////////////////////////////////////////////

function i2_get_next_prop() {
	setVariable(opCodeStore, getPropNext(operands[0], operands[1]));
}

////////////////////////////////////////////////////////////////////////////////

function i2_get_prop() {
	setVariable(opCodeStore, getProp(operands[0], operands[1]));
}

////////////////////////////////////////////////////////////////////////////////

function i2_get_prop_addr() {
	setVariable(opCodeStore, getPropAddr(operands[0], operands[1]));
}

////////////////////////////////////////////////////////////////////////////////

function i2_inc_chk() {
	var	val, chk;

	val = (getVariable(operands[0]) + 1) & 0xffff;
	setVariable(operands[0], val);

	if (makeSigned(val) > makeSigned(operands[1]))
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_insert_obj() {
	insertObj(operands[0], operands[1]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_je() {
	var	o;

	for (o = 1 ; o < operandCount ; ++o)
		if (operands[0] == operands[o]) {
			doBranch();
			return;
		}

	dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_jg() {
	if (makeSigned(operands[0]) > makeSigned(operands[1]))
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_jin() {
	if (getObjParent(operands[0]) == operands[1])
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_jl() {
	if (makeSigned(operands[0]) < makeSigned(operands[1]))
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_loadb() {
	setVariable(opCodeStore, memory[operands[0] + operands[1]]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_loadw() {
	setVariable(opCodeStore, readWord(operands[0] + operands[1] * 2));
}

////////////////////////////////////////////////////////////////////////////////

function i2_mod() {
		var	val, div, isSigned = false;

	val = operands[0];
	if (val & 0x8000) {
		val = (val ^ 0xffff) + 1;
		isSigned = !isSigned;
	}
	div = operands[1];
	if (div & 0x8000) {
		div = (div & 0xffff) + 1;
		isSigned = !isSigned;
	}

	val %= div;
	if (isSigned)
		val = ((val ^ 0xffff) + 1);

	setVariable(opCodeStore, val);
}

////////////////////////////////////////////////////////////////////////////////

function i2_mul() {
	setVariable(opCodeStore, (operands[0] * operands[1]) & 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i2_or() {
	setVariable(opCodeStore, operands[0] | operands[1]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_set_attr() {
	setObjAttrib(operands[0], operands[1], true);
}

////////////////////////////////////////////////////////////////////////////////

function i2_store() {
	setVariable(operands[0], operands[1]);
}

////////////////////////////////////////////////////////////////////////////////

function i2_sub() {
	setVariable(opCodeStore, (operands[0] - operands[1]) & 0xffff);
}

////////////////////////////////////////////////////////////////////////////////

function i2_test() {
	if ((operands[0] & operands[1]) == operands[1])
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function i2_test_attr() {
	if (getObjAttrib(operands[0], operands[1]))
		doBranch();
	else
		dontBranch();
}

////////////////////////////////////////////////////////////////////////////////

function iV_call() {
	var	addr, nLocs, frame, v;

	assert(operandCount > 0, "bad operandCount for call");
	addr = operands[0] * 2;

	if (addr != 0) {
		nLocs	= memory[addr++];
		frame	= new StackFrame(nextIp, nLocs, opCodeStore);

		for (v = 0 ; v < nLocs ; ++v)
			if (v + 1 < operandCount)
				frame.locals[v] = operands[v + 1];
			else
				frame.locals[v] = readWord(addr + v * 2);
		addr += nLocs * 2;

		stack[stack.length] = frame;
		stackTop = frame;

		curIp = addr;
	} else
		setVariable(opCodeStore, 0);
}

////////////////////////////////////////////////////////////////////////////////

function iV_input_stream() {
	setInputStream(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function iV_output_stream() {
	setOutputStream(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function iV_print_char() {
	if (!debug)
		print(String.fromCharCode(operands[0]));
}

////////////////////////////////////////////////////////////////////////////////

function iV_print_num() {
	if (!debug) {
		var	v = operands[0];

		if (v & 0x8000)
			v -= 0x10000;

		print(v.toString());
	}
}

////////////////////////////////////////////////////////////////////////////////

function iV_pull() {
	setVariable(operands[0], getVariable(0));
}

////////////////////////////////////////////////////////////////////////////////

function iV_push() {
	setVariable(0, operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function iV_put_prop() {
	setProp(operands[0], operands[1], operands[2]);
}

////////////////////////////////////////////////////////////////////////////////

var	rnd = 78;

function iV_random() {
	if (operands[0] & 0x8000) {
		rnd = operands[0];
		setVariable(opCodeStore, 78);
	} else
		setVariable(opCodeStore, (rnd++ % operands[0]) + 1);
}

////////////////////////////////////////////////////////////////////////////////

function iV_set_window() {
	setWindow(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function iV_split_window() {
	splitWindow(operands[0]);
}

////////////////////////////////////////////////////////////////////////////////

function iV_sread() {
	drawScreen();

	textBuffer	= operands[0];
	parseBuffer	= operands[1];

	state		= WAITINPUT;
}

////////////////////////////////////////////////////////////////////////////////

function iV_storeb() {
	memory[operands[0] + operands[1]] = operands[2];
}

////////////////////////////////////////////////////////////////////////////////

function iV_storew() {
	writeWord(operands[0] + operands[1] * 2, operands[2]);
}

////////////////////////////////////////////////////////////////////////////////

function runZMachine() {
	var	count = 0;

	state = EXECUTE;

	try {
		while (true) {
			var	ip;

			decodeInstruction();

			ip = curIp;

			if (!instructions[opCode])
				throw "bad instruction";

			instructions[opCode]();

			if (state != EXECUTE)
				break;
			else if (curIp == ip)
				curIp = nextIp;
		}
	} catch (e) {
		print(e + ", curIp = " + curIp);
		printChar("^");
	}

	drawScreen();
}

////////////////////////////////////////////////////////////////////////////////

function setVariable(v, data) {
	if (debug) {
		print("setVariable(" + v + ", " + data + ")");
		newLine();
	}

	if (v == 0)
		stackTop.stack[stackTop.stack.length] = data;
	else if (v < 0x10)
		stackTop.locals[v - 1] = data;
	else
		writeWord(addrGlobalTbl + (v - 0x10) * 2, data);
}

////////////////////////////////////////////////////////////////////////////////

