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

// Script debugger functionality. Absolutely not threadsafe.

#include "sci/sci.h"
#include "sci/console.h"
#include "sci/debug.h"
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#include "sci/engine/script.h"
#include "sci/engine/scriptdebug.h"

#include "common/algorithm.h"

namespace Sci {

//#define VM_DEBUG_SEND

// This table is only used for debugging. Don't include it for devices
// with not enough available memory (e.g. phones), where REDUCE_MEMORY_USAGE
// is defined
#ifndef REDUCE_MEMORY_USAGE
const char *opcodeNames[] = {
	   "bnot",       "add",      "sub",      "mul",      "div",
		"mod",       "shr",      "shl",      "xor",      "and",
		 "or",       "neg",      "not",      "eq?",      "ne?",
		"gt?",       "ge?",      "lt?",      "le?",     "ugt?",
	   "uge?",      "ult?",     "ule?",       "bt",      "bnt",
		"jmp",       "ldi",     "push",    "pushi",     "toss",
		"dup",      "link",     "call",    "callk",    "callb",
	  "calle",       "ret",     "send",    "dummy",    "dummy",
	  "class",     "dummy",     "self",    "super",    "&rest",
		"lea",    "selfID",    "dummy",    "pprev",     "pToa",
	   "aTop",      "pTos",     "sTop",    "ipToa",    "dpToa",
	  "ipTos",     "dpTos",    "lofsa",    "lofss",    "push0",
	  "push1",     "push2", "pushSelf",     "line",      "lag",
		"lal",       "lat",      "lap",      "lsg",      "lsl",
		"lst",       "lsp",     "lagi",     "lali",     "lati",
	   "lapi",      "lsgi",     "lsli",     "lsti",     "lspi",
		"sag",       "sal",      "sat",      "sap",      "ssg",
		"ssl",       "sst",      "ssp",     "sagi",     "sali",
	   "sati",      "sapi",     "ssgi",     "ssli",     "ssti",
	   "sspi",       "+ag",      "+al",      "+at",      "+ap",
		"+sg",       "+sl",      "+st",      "+sp",     "+agi",
	   "+ali",      "+ati",     "+api",     "+sgi",     "+sli",
	   "+sti",      "+spi",      "-ag",      "-al",      "-at",
		"-ap",       "-sg",      "-sl",      "-st",      "-sp",
	   "-agi",      "-ali",     "-ati",     "-api",     "-sgi",
	   "-sli",      "-sti",     "-spi"
};
#endif	// REDUCE_MEMORY_USAGE

void DebugState::updateActiveBreakpointTypes() {
	int type = 0;
	for (Common::List<Breakpoint>::iterator bp = _breakpoints.begin(); bp != _breakpoints.end(); ++bp) {
		if (bp->_action != BREAK_NONE)
			type |= bp->_type;
	}

	_activeBreakpointTypes = type;
}

// Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered.
reg_t disassemble(EngineState *s, reg32_t pos, const Object *obj, bool printBWTag, bool printBytecode) {
	SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
	Script *script_entity = NULL;
	reg_t retval;
	retval.setSegment(pos.getSegment());
	retval.setOffset(pos.getOffset() + 1);
	uint16 param_value = 0xffff; // Suppress GCC warning by setting default value, chose value as invalid to getKernelName etc.
	uint i = 0;
	Kernel *kernel = g_sci->getKernel();

	if (!mobj) {
		warning("Disassembly failed: Segment %04x non-existent or not a script", pos.getSegment());
		return retval;
	} else
		script_entity = (Script *)mobj;

	uint scr_size = script_entity->getBufSize();

	if (pos.getOffset() >= scr_size) {
		warning("Trying to disassemble beyond end of script");
		return NULL_REG;
	}

	const byte *scr = script_entity->getBuf();

	int16 opparams[4];
	byte opsize;
	uint bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams);
	const byte opcode = opsize >> 1;

	debugN("%04x:%04x: ", PRINT_REG(pos));

	if (printBytecode) {
		if (pos.getOffset() + bytecount > scr_size) {
			warning("Operation arguments extend beyond end of script");
			return retval;
		}

		for (i = 0; i < bytecount; i++)
			debugN("%02x ", scr[pos.getOffset() + i]);

		for (i = bytecount; i < 5; i++)
			debugN("   ");
	}

	opsize &= 1; // byte if true, word if false

	if (printBWTag)
		debugN("[%c] ", opsize ? 'B' : 'W');

	if (opcode == op_pushSelf && opsize && g_sci->getGameId() != GID_FANMADE) { // 0x3e (62)
		// Debug opcode op_file
		debugN("file \"%s\"\n", scr + pos.getOffset() + 1);	// +1: op_pushSelf size
		retval.incOffset(bytecount - 1);
		return retval;
	}

#ifndef REDUCE_MEMORY_USAGE
	debugN("%-5s", opcodeNames[opcode]);
#endif

	static const char *defaultSeparator = "\t\t; ";

	i = 0;
	while (g_sci->_opcode_formats[opcode][i]) {
		switch (g_sci->_opcode_formats[opcode][i++]) {
		case Script_Invalid:
			warning("-Invalid operation-");
			break;

		case Script_SByte:
		case Script_Byte:
			param_value = scr[retval.getOffset()];
			debugN("\t%02x", param_value);
			if (param_value > 9) {
				debugN("%s%u", defaultSeparator, param_value);
			}
			retval.incOffset(1);
			break;

		case Script_Word:
		case Script_SWord:
			param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
			debugN("\t%04x", param_value);
			if (param_value > 9) {
				debugN("%s%u", defaultSeparator, param_value);
			}

			retval.incOffset(2);
			break;

		case Script_SVariable:
		case Script_Variable:
		case Script_Property:
		case Script_Global:
		case Script_Local:
		case Script_Temp:
		case Script_Param:
			if (opsize) {
				param_value = scr[retval.getOffset()];
				retval.incOffset(1);
			} else {
				param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
			}

			if (opcode == op_callk) {
				debugN("\t%s[%x],", (param_value < kernel->_kernelFuncs.size()) ?
							((param_value < kernel->getKernelNamesSize()) ? kernel->getKernelName(param_value).c_str() : "[Unknown(postulated)]")
							: "<invalid>", param_value);
			} else if (opcode == op_class || opcode == op_super) {
				const reg_t classAddr = s->_segMan->getClassAddress(param_value, SCRIPT_GET_DONT_LOAD, retval.getSegment());
				if (!classAddr.isNull()) {
					debugN("\t%s", s->_segMan->getObjectName(classAddr));
					debugN(opsize ? "[%02x]" : "[%04x]", param_value);
				} else {
					debugN(opsize ? "\t%02x" : "\t%04x", param_value);
				}

				debugN(", ");
#ifdef ENABLE_SCI32
			} else if (
				opcode == op_pToa || opcode == op_aTop ||
				opcode == op_pTos || opcode == op_sTop ||
				opcode == op_ipToa || opcode == op_dpToa ||
				opcode == op_ipTos || opcode == op_dpTos) {

				const char *selectorName;

				if (getSciVersion() == SCI_VERSION_3) {
					if (param_value < kernel->getSelectorNamesSize()) {
						selectorName = kernel->getSelectorName(param_value).c_str();
					} else {
						selectorName = "<invalid>";
					}
				} else {
					if (obj != nullptr) {
						const Object *const super = obj->getClass(s->_segMan);
						assert(super);
						selectorName = kernel->getSelectorName(super->getVarSelector(param_value / 2)).c_str();
					} else {
						selectorName = "<unavailable>";
					}
				}

				debugN("\t%s[%x]", selectorName, param_value);
#endif
			} else {
				const char *separator = defaultSeparator;

				debugN(opsize ? "\t%02x" : "\t%04x", param_value);
				if (param_value > 9) {
					debugN("%s%u", separator, param_value);
					separator = ", ";
				}

				if (param_value >= 0x20 && param_value <= 0x7e) {
					debugN("%s'%c'", separator, param_value);
					separator = ", ";
				}

				if (opcode == op_pushi && param_value < kernel->getSelectorNamesSize()) {
					debugN("%s%s", separator, kernel->getSelectorName(param_value).c_str());
				}
			}

			break;

		case Script_Offset: {
			assert(opcode == op_lofsa || opcode == op_lofss);

			if (opsize) {
				param_value = scr[retval.getOffset()];
				retval.incOffset(1);
			} else {
				param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
			}

			const uint32 offset = findOffset(param_value, script_entity, pos.getOffset() + bytecount);
			reg_t addr;
			addr.setSegment(retval.getSegment());
			addr.setOffset(offset);
			if (!s->_segMan->isObject(addr)) {
				debugN("\t\"%s\"", s->_segMan->derefString(addr));
			} else {
				debugN("\t%s", s->_segMan->getObjectName(addr));
			}
			debugN(opsize ? "[%02x]" : "[%04x]", offset);
			break;
		}

		case Script_SRelative:
			if (opsize) {
				int8 offset = (int8)scr[retval.getOffset()];
				retval.incOffset(1);
				debugN("\t%02x  [%04x]", 0xff & offset, kOffsetMask & (pos.getOffset() + bytecount + offset));
			} else {
				int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
				debugN("\t%04x  [%04x]", 0xffff & offset, kOffsetMask & (pos.getOffset() + bytecount + offset));
			}
			break;

		case Script_End:
			retval = NULL_REG;
			break;

		default:
			error("Internal assertion failed in disassemble()");

		}
	}

	if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
		if ((opcode == op_pTos) || (opcode == op_sTop) || (opcode == op_pToa) || (opcode == op_aTop) ||
		        (opcode == op_dpToa) || (opcode == op_ipToa) || (opcode == op_dpTos) || (opcode == op_ipTos)) {
			obj = s->_segMan->getObject(s->xs->objp);
			if (!obj) {
				warning("Attempted to reference on non-object at %04x:%04x", PRINT_REG(s->xs->objp));
			} else {
				if (getSciVersion() == SCI_VERSION_3)
					debugN("\t(%s)", g_sci->getKernel()->getSelectorName(param_value).c_str());
				else
					debugN("\t(%s)", g_sci->getKernel()->getSelectorName(obj->propertyOffsetToId(s->_segMan, param_value)).c_str());
			}
		}
	}

	debugN("\n");

	if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
		if (opcode == op_callk) {
			int stackframe = (scr[pos.getOffset() + 2] >> 1) + (s->r_rest);
			int argc = ((s->xs->sp)[- stackframe - 1]).getOffset();
			bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);

			if (!oldScriptHeader)
				argc += (s->r_rest);

			debugN(" Kernel params: (");

			for (int j = 0; j < argc; j++) {
				debugN("%04x:%04x", PRINT_REG((s->xs->sp)[j - stackframe]));
				if (j + 1 < argc)
					debugN(", ");
			}
			debugN(")\n");
		} else if ((opcode == op_send) || (opcode == op_self)) {
			int restmod = s->r_rest;
			int stackframe = (scr[pos.getOffset() + 1] >> 1) + restmod;
			reg_t *sb = s->xs->sp;
			uint16 selector;
			reg_t fun_ref;

			while (stackframe > 0) {
				int argc = sb[- stackframe + 1].getOffset();
				const char *name = NULL;
				reg_t called_obj_addr = s->xs->objp;

				if (opcode == op_send)
					called_obj_addr = s->r_acc;
				else if (opcode == op_self)
					called_obj_addr = s->xs->objp;

				selector = sb[- stackframe].getOffset();

				name = s->_segMan->getObjectName(called_obj_addr);

				if (!name)
					name = "<invalid>";

				debugN("  %s::%s[", name, g_sci->getKernel()->getSelectorName(selector).c_str());

				if (!s->_segMan->getObject(called_obj_addr)) {
					debugN("INVALID_OBJ");
				} else {
					switch (lookupSelector(s->_segMan, called_obj_addr, selector, 0, &fun_ref)) {
					case kSelectorMethod:
						debugN("FUNCT");
						argc += restmod;
						restmod = 0;
						break;
					case kSelectorVariable:
						debugN("VAR");
						break;
					case kSelectorNone:
						debugN("INVALID");
						break;
					}
				}

				debugN("](");

				while (argc--) {
					debugN("%04x:%04x", PRINT_REG(sb[- stackframe + 2]));
					if (argc)
						debugN(", ");
					stackframe--;
				}

				debugN(")\n");
				stackframe -= 2;
			} // while (stackframe > 0)
		} // Send-like opcodes
	} // (heappos == *p_pc)

	return retval;
}

bool isJumpOpcode(EngineState *s, reg_t pos, reg_t& jumpTarget) {
	SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
	if (!mobj)
		return false;
	Script *script_entity = (Script *)mobj;

	uint scr_size = script_entity->getScriptSize();

	if (pos.getOffset() >= scr_size)
		return false;

	const byte *scr = script_entity->getBuf();

	int16 opparams[4];
	byte opsize;
	int bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams);
	const byte opcode = opsize >> 1;

	switch (opcode) {
	case op_bt:
	case op_bnt:
	case op_jmp:
		{
		reg_t jmpTarget = pos + bytecount + opparams[0];
		// QFG2 has invalid jumps outside the script buffer in script 260
		if (jmpTarget.getOffset() >= scr_size)
			return false;
		jumpTarget = jmpTarget;
		}
		return true;
	default:
		return false;
	}
}


void SciEngine::scriptDebug() {
	EngineState *s = _gamestate;
	if (_debugState.seeking && !_debugState.breakpointWasHit) { // Are we looking for something special?
		if (_debugState.seeking == kDebugSeekStepOver) {
			// are we above seek-level? resume then
			if (_debugState.seekLevel < (int)s->_executionStack.size())
				return;
			_debugState.seeking = kDebugSeekNothing;
		}

		if (_debugState.seeking != kDebugSeekNothing) {
			const reg32_t pc = s->xs->addr.pc;
			SegmentObj *mobj = s->_segMan->getSegment(pc.getSegment(), SEG_TYPE_SCRIPT);

			if (mobj) {
				Script *scr = (Script *)mobj;
				const byte *code_buf = scr->getBuf();
				uint16 code_buf_size = scr->getBufSize();	// TODO: change to a 32-bit integer for large SCI3 scripts
				int opcode = pc.getOffset() >= code_buf_size ? 0 : code_buf[pc.getOffset()];
				int op = opcode >> 1;
				uint16 paramb1 = pc.getOffset() + 1 >= code_buf_size ? 0 : code_buf[pc.getOffset() + 1];
				uint16 paramf1 = (opcode & 1) ? paramb1 : (pc.getOffset() + 2 >= code_buf_size ? 0 : (int16)READ_SCI11ENDIAN_UINT16(code_buf + pc.getOffset() + 1));

				switch (_debugState.seeking) {
				case kDebugSeekSpecialCallk:
					if (paramb1 != _debugState.seekSpecial)
						return;

				case kDebugSeekCallk:
					if (op != op_callk)
						return;
					break;

				case kDebugSeekLevelRet:
					if ((op != op_ret) || (_debugState.seekLevel < (int)s->_executionStack.size()-1))
						return;
					break;

				case kDebugSeekGlobal:
					if (op < op_sag)
						return;
					if ((op & 0x3) > 1)
						return; // param or temp
					if ((op & 0x3) && s->_executionStack.back().local_segment > 0)
						return; // locals and not running in script.000
					if (paramf1 != _debugState.seekSpecial)
						return; // CORRECT global?
					break;

				default:
					break;
				}

				_debugState.seeking = kDebugSeekNothing;
			}
		}
		// OK, found whatever we were looking for
	}

	debugN("Step #%d\n", s->scriptStepCounter);
	disassemble(s, s->xs->addr.pc, s->_segMan->getObject(s->xs->objp), false, true);

	if (_debugState.runningStep) {
		_debugState.runningStep--;
		return;
	}

	_debugState.debugging = false;

	_console->attach();
}

void Kernel::dumpScriptObject(const SciSpan<const byte> &script, SciSpan<const byte> object) {
	const int16 species = object.getInt16SEAt(8);
	const int16 superclass = object.getInt16SEAt(10);
	const int16 namepos = object.getInt16SEAt(14);
	int i = 0;

	debugN("Object\n");

	//-4 because the size includes the two-word header
	Common::hexdump(object.getUnsafeDataAt(0, object.size() - 4), object.size() - 4, 16, object.sourceByteOffset());

	debugN("Name: %s\n", namepos ? script.getStringAt(namepos).c_str() : "<unknown>");
	debugN("Superclass: %x\n", superclass);
	debugN("Species: %x\n", species);
	debugN("-info-: %x\n", object.getInt16SEAt(12) & 0xFFFF);

	debugN("Function area offset: %x\n", object.getInt16SEAt(4));

	int16 selectors = object.getInt16SEAt(6);
	debugN("Selectors [%x]:\n", selectors);

	object += 8;

	while (selectors--) {
		debugN("  [#%03x] = 0x%x\n", i++, object.getInt16SEAt(0) & 0xFFFF);
		object += 2;
	}

	selectors = object.getInt16SEAt(0);
	int16 overloads = selectors;
	debugN("Overridden functions: %x\n", overloads);

	object += 2;

	if (overloads < 100) {
		while (overloads--) {
			const int16 selector = object.getInt16SEAt(0);

			debugN("  [%03x] %s: @", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ? _selectorNames[selector].c_str() : "<?>");
			debugN("%04x\n", object.getInt16SEAt(selectors * 2 + 2) & 0xFFFF);

			object += 2;
		}
	}
}

void Kernel::dumpScriptClass(const SciSpan<const byte> &script, SciSpan<const byte> clazz) {
	const int16 species = clazz.getInt16SEAt(8);
	const int16 superclass = clazz.getInt16SEAt(10);
	const int16 namepos = clazz.getInt16SEAt(14);

	debugN("Class\n");

	Common::hexdump(clazz.getUnsafeDataAt(0, clazz.size() - 4), clazz.size() - 4, 16, clazz.sourceByteOffset());

	debugN("Name: %s\n", namepos ? script.getStringAt(namepos).c_str() : "<unknown>");
	debugN("Superclass: %x\n", superclass);
	debugN("Species: %x\n", species);
	debugN("-info-: %x\n", clazz.getInt16SEAt(12) & 0xFFFF);

	debugN("Function area offset: %x\n", clazz.getInt16SEAt(4));

	int16 selectors = clazz.getInt16SEAt(6);
	int16 selectorsize = selectors;
	debugN("Selectors [%x]:\n", selectors);

	clazz += 8;
	selectorsize <<= 1;

	while (selectors--) {
		const int16 selector = clazz.getInt16SEAt(selectorsize);

		debugN("  [%03x] %s = 0x%x\n", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ? _selectorNames[selector].c_str() : "<?>", clazz.getInt16SEAt(0) & 0xFFFF);

		clazz += 2;
	}

	clazz += selectorsize;

	int16 overloads = clazz.getInt16SEAt(0);
	selectors = overloads;
	debugN("Overloaded functions: %x\n", overloads);

	clazz += 2;

	while (overloads--) {
		int16 selector = clazz.getInt16SEAt(0);
		debugN("selector=%d; selectorNames.size() =%d\n", selector, _selectorNames.size());
		debugN("  [%03x] %s: @", selector & 0xFFFF, (selector >= 0 && selector < (int)_selectorNames.size()) ?
		          _selectorNames[selector].c_str() : "<?>");
		debugN("%04x\n", clazz.getInt16SEAt(selectors * 2 + 2) & 0xFFFF);

		clazz += 2;
	}
}

void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) {
	int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	uint32 _seeker = 0;
	Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), false);

	if (!script) {
		warning("dissectScript(): Script not found!\n");
		return;
	}

	while (_seeker < script->size()) {
		int objType = script->getInt16SEAt(_seeker);
		int objsize;
		uint32 seeker = _seeker + 4;

		if (!objType) {
			debugN("End of script object (#0) encountered.\n");
			debugN("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)\n",
			          objectctr[6], objectctr[1], objectctr[7], objectctr[10]);
			return;
		}

		debugN("\n");

		objsize = script->getInt16SEAt(_seeker + 2);

		debugN("Obj type #%x, size 0x%x: ", objType, objsize);

		_seeker += objsize;

		if (objType >= 0 && objType < ARRAYSIZE(objectctr))
			objectctr[objType]++;

		switch (objType) {
		case SCI_OBJ_OBJECT:
			dumpScriptObject(*script, script->subspan(seeker, objsize));
			break;

		case SCI_OBJ_CODE:
			debugN("Code\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		case SCI_OBJ_SYNONYMS:
			debugN("Synonyms\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		case SCI_OBJ_SAID:
			debugN("Said\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);

			debugN("%04x: ", seeker);
			vocab->debugDecipherSaidBlock(script->subspan(seeker));
			debugN("\n");
			break;

		case SCI_OBJ_STRINGS:
			debugN("Strings\n");
			while (script->getUint8At(seeker)) {
				const Common::String string = script->getStringAt(seeker);
				debugN("%04x: %s", seeker, string.c_str());
				seeker += string.size() + 1;
				if (seeker > script->size()) {
					debugN("[TRUNCATED]");
				}
				debugN("\n");
			}
			seeker++; // the ending zero byte
			break;

		case SCI_OBJ_CLASS:
			dumpScriptClass(*script, script->subspan(seeker, objsize));
			break;

		case SCI_OBJ_EXPORTS:
			debugN("Exports\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		case SCI_OBJ_POINTERS:
			debugN("Pointers\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		case 9:
			debugN("<unknown>\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		case SCI_OBJ_LOCALVARS:
			debugN("Local vars\n");
			Common::hexdump(script->getUnsafeDataAt(seeker, objsize - 4), objsize - 4, 16, seeker);
			break;

		default:
			debugN("Unsupported!\n");
			return;
		}

	}

	debugN("Script ends without terminator\n");
}

bool SciEngine::checkSelectorBreakpoint(BreakpointType breakpointType, reg_t send_obj, int selector) {
	Common::String methodName = _gamestate->_segMan->getObjectName(send_obj);
	methodName += "::" + getKernel()->getSelectorName(selector);

	bool found = false;

	Common::List<Breakpoint>::const_iterator bp;
	for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
		if (bp->_action == BREAK_NONE || bp->_type != breakpointType)
			continue;

		if (bp->_name == methodName ||
		    (bp->_name.hasSuffix("::") && methodName.hasPrefix(bp->_name))) {
			if (!found) // Show message once, but allow multiple actions
				_console->debugPrintf("Break on %s (in [%04x:%04x])\n", methodName.c_str(), PRINT_REG(send_obj));
			found = true;

			if (bp->_action == BREAK_BREAK) {
				_debugState.debugging = true;
				_debugState.breakpointWasHit = true;
			} else if (bp->_action == BREAK_BACKTRACE) {
				logBacktrace();
			} else if (bp->_action == BREAK_INSPECT) {
				printObject(send_obj);
			}
		}
	}
	return found;
}

bool SciEngine::checkExportBreakpoint(uint16 script, uint16 pubfunct) {

	if (!(_debugState._activeBreakpointTypes & BREAK_EXPORT))
		return false;

	bool found = false;
	uint32 bpaddress = (script << 16 | pubfunct);

	Common::List<Breakpoint>::const_iterator bp;
	for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
		if (bp->_action == BREAK_NONE || bp->_type != BREAK_EXPORT)
			continue;

		if (bp->_address == bpaddress) {
			if (!found) // Show message once, but allow multiple actions
				_console->debugPrintf("Break on script %d, export %d\n", script, pubfunct);
			found = true;

			if (bp->_action == BREAK_BREAK) {
				_debugState.debugging = true;
				_debugState.breakpointWasHit = true;
			} else if (bp->_action == BREAK_BACKTRACE) {
				logBacktrace();
			} else if (bp->_action == BREAK_INSPECT) {
				// Ignoring this mode, to make it identical to BREAK_LOG
			}
		}
	}

	return found;
}

bool SciEngine::checkAddressBreakpoint(const reg32_t &address) {
	if (!(_debugState._activeBreakpointTypes & BREAK_ADDRESS))
		return false;

	bool found = false;

	Common::List<Breakpoint>::const_iterator bp;
	for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
		if (bp->_action == BREAK_NONE || bp->_type != BREAK_ADDRESS)
			continue;

		if (bp->_regAddress == address) {
			if (!found)
				_console->debugPrintf("Break at %04x:%04x\n", PRINT_REG(address));
			found = true;

			if (bp->_action == BREAK_BREAK) {
				_debugState.debugging = true;
				_debugState.breakpointWasHit = true;
			} else if (bp->_action == BREAK_BACKTRACE) {
				logBacktrace();
			} else if (bp->_action == BREAK_INSPECT) {
				// Ignoring this mode, to make it identical to BREAK_LOG
			}
		}
	}

	return found;
}

bool matchKernelBreakpointPattern(const Common::String &pattern, const Common::String &name) {
	// Pattern:
	// A comma-separated list of atoms.
	// An atom is a (possibly empty) word, optionally with a ! prefix (for
	// a negative-match), and/or a * suffix (for a prefix-match).

	// The last matching atom in the pattern takes effect.

	// Examples:
	// FrameOut : matches only FrameOut
	// * : matches everything
	// *,!FrameOut : matches everything except FrameOut
	// InitBresen,DoBresen : matches InitBresen and DoBresen
	// DoSound*,!DoSoundUpdateCues : matches all DoSound sub-functions except
	//                               DoSoundUpdateCues

	bool result = false;

	Common::String::const_iterator i = pattern.begin();
	while (i != pattern.end()) {
		Common::String::const_iterator next = Common::find(i, pattern.end(), ',');
		bool negative = *i == '!';

		if (negative)
			i++;

		Common::String atom(i, next - i);

		bool wildcard = atom.lastChar() == '*';
		if (wildcard)
			atom.deleteLastChar();

		if ((!wildcard && atom == name) || (wildcard && name.hasPrefix(atom)))
			result = !negative;

		i = next;
		if (i != pattern.end())
			++i; // skip comma
	}

	return result;
}

bool SciEngine::checkKernelBreakpoint(const Common::String &name) {
	if (!(_debugState._activeBreakpointTypes & BREAK_KERNEL))
		return false;

	bool found = false;

	Common::List<Breakpoint>::const_iterator bp;
	for (bp = _debugState._breakpoints.begin(); bp != _debugState._breakpoints.end(); ++bp) {
		if (bp->_action == BREAK_NONE || bp->_type != BREAK_KERNEL)
			continue;

		if (matchKernelBreakpointPattern(bp->_name, name)) {
			if (bp->_action == BREAK_BREAK) {
				if (!found)
					_console->debugPrintf("Break on k%s\n", name.c_str());
				_debugState.debugging = true;
				_debugState.breakpointWasHit = true;
			} else if (bp->_action == BREAK_BACKTRACE) {
				if (!found)
					_console->debugPrintf("Break on k%s\n", name.c_str());
				logBacktrace();
			} else if (bp->_action == BREAK_INSPECT) {
				// Ignoring this mode, to make it identical to BREAK_LOG
			}
			found = true;
		}
	}

	return found;
}


void debugSelectorCall(reg_t send_obj, Selector selector, int argc, StackPtr argp, ObjVarRef &varp, reg_t funcp, SegManager *segMan, SelectorType selectorType) {
	int activeBreakpointTypes = g_sci->_debugState._activeBreakpointTypes;
	const char *objectName = segMan->getObjectName(send_obj);
	const char *selectorName = g_sci->getKernel()->getSelectorName(selector).c_str();
	Console *con = g_sci->getSciDebugger();

#ifdef VM_DEBUG_SEND
		debugN("Send to %04x:%04x (%s), selector %04x (%s):", PRINT_REG(send_obj),
			segMan->getObjectName(send_obj), selector,
			g_sci->getKernel()->getSelectorName(selector).c_str());
#endif // VM_DEBUG_SEND

	switch (selectorType) {
	case kSelectorNone:
		debugN("\n");
		break;
	case kSelectorVariable:
#ifdef VM_DEBUG_SEND
		if (argc)
			debugN("Varselector: Write %04x:%04x\n", PRINT_REG(argp[1]));
		else
			debugN("Varselector: Read\n");
#endif // VM_DEBUG_SEND

		// argc == 0: read selector
		// argc == 1: write selector
		// argc can be bigger than 1 in some cases, because of a script bug.
		// Usually, these aren't fatal.
		if ((activeBreakpointTypes & BREAK_SELECTORREAD) ||
			(activeBreakpointTypes & BREAK_SELECTORWRITE) ||
			argc > 1) {

			reg_t selectorValue = *varp.getPointer(segMan);
			if (!argc && (activeBreakpointTypes & BREAK_SELECTORREAD)) {
				if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORREAD, send_obj, selector))
					con->debugPrintf("Read from selector (%s:%s): %04x:%04x\n",
							objectName, selectorName,
							PRINT_REG(selectorValue));
			} else if (argc && (activeBreakpointTypes & BREAK_SELECTORWRITE)) {
				if (g_sci->checkSelectorBreakpoint(BREAK_SELECTORWRITE, send_obj, selector))
					con->debugPrintf("Write to selector (%s:%s): change %04x:%04x to %04x:%04x\n",
							objectName, selectorName,
							PRINT_REG(selectorValue), PRINT_REG(argp[1]));
			}

			if (argc > 1)
				debug(kDebugLevelScripts, "Write to selector (%s:%s): change %04x:%04x to %04x:%04x, argc == %d\n",
							objectName, selectorName,
							PRINT_REG(selectorValue), PRINT_REG(argp[1]), argc);
		}
		break;
	case kSelectorMethod:
#ifndef VM_DEBUG_SEND
			if (activeBreakpointTypes & BREAK_SELECTOREXEC) {
				if (g_sci->checkSelectorBreakpoint(BREAK_SELECTOREXEC, send_obj, selector)) {
#else
			if (true) {
				if (true) {
#endif
					con->debugPrintf("%s::%s(", objectName, selectorName);
					for (int i = 0; i < argc; i++) {
						con->debugPrintf("%04x:%04x", PRINT_REG(argp[i+1]));
						if (i + 1 < argc)
							con->debugPrintf(", ");
					}
					con->debugPrintf(") at %04x:%04x\n", PRINT_REG(funcp));
				}
			}
		break;
	}	// switch
}

void debugPropertyAccess(Object *obj, reg_t objp, unsigned int index, reg_t curValue, reg_t newValue, SegManager *segMan, BreakpointType breakpointType) {
	const Object *var_container = obj;
	if (!obj->isClass() && getSciVersion() != SCI_VERSION_3)
		var_container = segMan->getObject(obj->getSuperClassSelector());

	uint16 varSelector;
	if (getSciVersion() == SCI_VERSION_3) {
		varSelector = index;
	} else {
		index >>= 1;

		if (index >= var_container->getVarCount()) {
			// TODO: error, warning, debug?
			return;
		}

		varSelector = var_container->getVarSelector(index);
	}

	if (g_sci->checkSelectorBreakpoint(breakpointType, objp, varSelector)) {
		// checkSelectorBreakpoint has already triggered the breakpoint.
		// We just output the relevant data here.

		Console *con = g_sci->getSciDebugger();
		const char *objectName = segMan->getObjectName(objp);
		const char *selectorName = g_sci->getKernel()->getSelectorName(varSelector).c_str();
		if (breakpointType == BREAK_SELECTORWRITE) {
			con->debugPrintf("Write to selector (%s:%s): change %04x:%04x to %04x:%04x\n",
								objectName, selectorName,
								PRINT_REG(curValue), PRINT_REG(newValue));
		} else if (breakpointType == BREAK_SELECTORREAD) {
			con->debugPrintf("Read from selector (%s:%s): %04x:%04x\n",
								objectName, selectorName,
								PRINT_REG(curValue));

		} else {
			assert(false);
		}
	}
}

void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunction *kernelSubCall, EngineState *s, int argc, reg_t *argv, reg_t result) {
	if (s->abortScriptProcessing != kAbortNone) {
		return;
	}

	Kernel *kernel = g_sci->getKernel();
	if (!kernelSubCall) {
		debugN("k%s: ", kernelCall->name);
	} else {
		int callNameLen = strlen(kernelCall->name);
		if (strncmp(kernelCall->name, kernelSubCall->name, callNameLen) == 0) {
			const char *subCallName = kernelSubCall->name + callNameLen;
			debugN("k%s(%s): ", kernelCall->name, subCallName);
		} else {
			debugN("k%s(%s): ", kernelCall->name, kernelSubCall->name);
		}
	}
	for (int parmNr = 0; parmNr < argc; parmNr++) {
		if (parmNr)
			debugN(", ");
		uint16 regType = kernel->findRegType(argv[parmNr]);
		if (regType & SIG_TYPE_NULL)
			debugN("0");
		else if (regType & SIG_TYPE_UNINITIALIZED)
			debugN("UNINIT");
		else if (regType & SIG_IS_INVALID)
			debugN("INVALID");
		else if (regType & SIG_TYPE_INTEGER)
			debugN("%d", argv[parmNr].getOffset());
		else {
			debugN("%04x:%04x", PRINT_REG(argv[parmNr]));
			switch (regType) {
			case SIG_TYPE_OBJECT:
				debugN(" (%s)", s->_segMan->getObjectName(argv[parmNr]));
				break;
			case SIG_TYPE_REFERENCE:
			{
				SegmentObj *mobj = s->_segMan->getSegmentObj(argv[parmNr].getSegment());
				if (mobj) {
					switch (mobj->getType()) {
					case SEG_TYPE_HUNK:
					{
						HunkTable &ht = *(HunkTable *)mobj;
						int index = argv[parmNr].getOffset();
						if (ht.isValidEntry(index)) {
							// NOTE: This ", deleted" isn't as useful as it could
							// be because it prints the status _after_ the kernel
							// call.
							debugN(" ('%s' hunk%s)", ht[index].type, ht[index].mem ? "" : ", deleted");
						} else
							debugN(" (INVALID hunk ref)");
						break;
					}
					default:
						// TODO: Any other segment types which could
						// use special handling?

						if (kernelCall->function == kSaid) {
							SegmentRef saidSpec = s->_segMan->dereference(argv[parmNr]);
							if (saidSpec.isRaw) {
								debugN(" ('");
								g_sci->getVocabulary()->debugDecipherSaidBlock(SciSpan<const byte>(saidSpec.raw, saidSpec.maxSize, Common::String::format("said %04x:%04x", PRINT_REG(argv[parmNr]))));
								debugN("')");
							} else {
								debugN(" (non-raw said-spec)");
							}
						} else {
							debugN(" ('%s')", s->_segMan->getString(argv[parmNr]).c_str());
						}
						break;
					}
				}
			}
			default:
				break;
			}
		}
	}
	if (result.isPointer())
		debugN(" = %04x:%04x\n", PRINT_REG(result));
	else
		debugN(" = %d\n", result.getOffset());
}


void logBacktrace() {
	Console *con = g_sci->getSciDebugger();
	EngineState *s = g_sci->getEngineState();

	con->debugPrintf("Call stack (current base: 0x%x):\n", s->executionStackBase);
	Common::List<ExecStack>::const_iterator iter;
	uint i = 0;


	for (iter = s->_executionStack.begin();
	     iter != s->_executionStack.end(); ++iter, ++i) {
		const ExecStack &call = *iter;
		const char *objname = s->_segMan->getObjectName(call.sendp);
		int paramc, totalparamc;

		switch (call.type) {
		case EXEC_STACK_TYPE_CALL: // Normal function
			if (call.type == EXEC_STACK_TYPE_CALL)
			con->debugPrintf(" %x: script %d - ", i, s->_segMan->getScript(call.addr.pc.getSegment())->getScriptNumber());
			if (call.debugSelector != -1) {
				con->debugPrintf("%s::%s(", objname, g_sci->getKernel()->getSelectorName(call.debugSelector).c_str());
			} else if (call.debugExportId != -1) {
				con->debugPrintf("export %d (", call.debugExportId);
			} else if (call.debugLocalCallOffset != -1) {
				con->debugPrintf("call %x (", call.debugLocalCallOffset);
			}
			break;

		case EXEC_STACK_TYPE_KERNEL: // Kernel function
			if (call.debugKernelSubFunction == -1)
				con->debugPrintf(" %x:[%x]  k%s(", i, call.debugOrigin, g_sci->getKernel()->getKernelName(call.debugKernelFunction).c_str());
			else
				con->debugPrintf(" %x:[%x]  k%s(", i, call.debugOrigin, g_sci->getKernel()->getKernelName(call.debugKernelFunction, call.debugKernelSubFunction).c_str());
			break;

		case EXEC_STACK_TYPE_VARSELECTOR:
			con->debugPrintf(" %x:[%x] vs%s %s::%s (", i, call.debugOrigin, (call.argc) ? "write" : "read",
			          objname, g_sci->getKernel()->getSelectorName(call.debugSelector).c_str());
			break;
		}

		totalparamc = call.argc;

		if (totalparamc > 16)
			totalparamc = 16;

		for (paramc = 1; paramc <= totalparamc; paramc++) {
			con->debugPrintf("%04x:%04x", PRINT_REG(call.variables_argp[paramc]));

			if (paramc < call.argc)
				con->debugPrintf(", ");
		}

		if (call.argc > 16)
			con->debugPrintf("...");

		con->debugPrintf(")\n     ");
		if (call.debugOrigin != -1)
			con->debugPrintf("by %x ", call.debugOrigin);
		con->debugPrintf("obj@%04x:%04x", PRINT_REG(call.objp));
		if (call.type == EXEC_STACK_TYPE_CALL) {
			con->debugPrintf(" pc=%04x:%04x", PRINT_REG(call.addr.pc));
			if (call.sp == CALL_SP_CARRY)
				con->debugPrintf(" sp,fp:carry");
			else {
				con->debugPrintf(" sp=ST:%04x", (unsigned)(call.sp - s->stack_base));
				con->debugPrintf(" fp=ST:%04x", (unsigned)(call.fp - s->stack_base));
			}
		} else
			con->debugPrintf(" pc:none");

		con->debugPrintf(" argp:ST:%04x", (unsigned)(call.variables_argp - s->stack_base));
		con->debugPrintf("\n");
	}
}

bool printObject(reg_t pos) {
	Console *con = g_sci->getSciDebugger();
	EngineState *s = g_sci->getEngineState();
	const Object *obj = s->_segMan->getObject(pos);
	const Object *var_container = obj;
	uint i;

	if (!obj) {
		con->debugPrintf("[%04x:%04x]: Not an object.\n", PRINT_REG(pos));
		return false;
	}

	// Object header
	con->debugPrintf("[%04x:%04x] %s : %3d vars, %3d methods\n", PRINT_REG(pos), s->_segMan->getObjectName(pos),
	                 obj->getVarCount(), obj->getMethodCount());

	if (!obj->isClass())
		var_container = s->_segMan->getObject(obj->getSuperClassSelector());
	con->debugPrintf("  -- member variables:\n");

	if (getSciVersion() == SCI_VERSION_3) {
		con->debugPrintf("    (----) [---] -size- = 0000:%04x (%d)\n", obj->getVarCount(), obj->getVarCount());
		con->debugPrintf("    (----) [---] -classScript- = %04x:%04x (%d)\n", PRINT_REG(obj->getClassScriptSelector()), obj->getClassScriptSelector().getOffset());
		con->debugPrintf("    (----) [---] -species- = %04x:%04x (%s)\n", PRINT_REG(obj->getSpeciesSelector()), s->_segMan->getObjectName(obj->getSpeciesSelector()));
		con->debugPrintf("    (----) [---] -super- = %04x:%04x (%s)\n", PRINT_REG(obj->getSuperClassSelector()), s->_segMan->getObjectName(obj->getSuperClassSelector()));
		con->debugPrintf("    (----) [---] -info- = %04x:%04x (%d)\n", PRINT_REG(obj->getInfoSelector()), obj->getInfoSelector().getOffset());
	}

	for (i = 0; (uint)i < obj->getVarCount(); i++) {
		con->debugPrintf("    ");
		if (var_container && i < var_container->getVarCount()) {
			uint16 varSelector = var_container->getVarSelector(i);
			// Times two commented out for now for easy parsing of vocab.994
			con->debugPrintf("(%04x) [%03x] %s = ", i /* *2 */, varSelector, g_sci->getKernel()->getSelectorName(varSelector).c_str());
		} else
			con->debugPrintf("p#%x = ", i);

		reg_t val = obj->getVariable(i);
		con->debugPrintf("%04x:%04x", PRINT_REG(val));

		if (!val.getSegment())
			con->debugPrintf(" (%d)", val.getOffset());

		const Object *ref = s->_segMan->getObject(val);
		if (ref)
			con->debugPrintf(" (%s)", s->_segMan->getObjectName(val));

		con->debugPrintf("\n");
	}
	con->debugPrintf("  -- methods:\n");
	Common::Array<Selector> foundMethods;
	const Object *protoObj = obj;
	do {
		for (i = 0; i < protoObj->getMethodCount(); i++) {
			const Selector selector = protoObj->getFuncSelector(i);
			if (Common::find(foundMethods.begin(), foundMethods.end(), selector) == foundMethods.end()) {
				reg_t fptr = protoObj->getFunction(i);
				con->debugPrintf("    [%03x] ", selector);
				if (protoObj != obj) {
					con->debugPrintf("%s::", s->_segMan->getObjectName(protoObj->getPos()));
				}
				con->debugPrintf("%s = %04x:%04x\n", g_sci->getKernel()->getSelectorName(selector).c_str(), PRINT_REG(fptr));
				foundMethods.push_back(selector);
			}
		}
	} while ((protoObj = s->_segMan->getObject(protoObj->getSuperClassSelector())));

	Script *scr = s->_segMan->getScriptIfLoaded(pos.getSegment());
	if (scr)
		con->debugPrintf("\nOwner script: %d\n", scr->getScriptNumber());

	return true;
}






} // End of namespace Sci
