/* 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 3 of the License, or
 * 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 "glk/comprehend/game_opcodes.h"
#include "glk/comprehend/game_data.h"
#include "glk/comprehend/comprehend.h"
#include "glk/comprehend/debugger.h"
#include "common/algorithm.h"
#include "common/textconsole.h"

namespace Glk {
namespace Comprehend {

ComprehendGameOpcodes::ComprehendGameOpcodes() {
	Common::fill(&_opcodeMap[0], &_opcodeMap[0x100], OPCODE_UNKNOWN);
}

void ComprehendGameOpcodes::execute_opcode(const Instruction *instr, const Sentence *sentence,
		FunctionState *func_state) {
	byte verb = sentence ? sentence->_formattedWords[0] : 0;
	byte noun = sentence ? sentence->_formattedWords[2] : 0;
	Room *room = get_room(_currentRoom);
	Item *item;
	uint index;

	byte opcode = getOpcode(instr);
	switch (_opcodeMap[opcode]) {
	case OPCODE_CALL_FUNC:
	case OPCODE_CALL_FUNC2:
		// Note: CALL_FUNC2 in the original did some extra backing of data which is
		// redundant in the ScummVM version, so it can be handled the same as CALL_FUNC.
		index = instr->_operand[0];
		if (instr->_operand[1] == 0x81)
			index += 256;
		if (index >= _functions.size())
			error("Bad function %.4x >= %.4x\n", index, _functions.size());

		eval_function(index, sentence);
		break;

	case OPCODE_CLEAR_CAN_TAKE:
		item = get_item_by_noun(noun);
		item->_flags &= ~ITEMF_CAN_TAKE;
		break;

	case OPCODE_CLEAR_FLAG:
		_flags[instr->_operand[0]] = false;
		break;

	case OPCODE_CLEAR_FLAG40:
		item = getItem(instr);
		item->_flags &= ~ITEMF_UNKNOWN;
		break;

	case OPCODE_CLEAR_INVISIBLE:
		item = get_item_by_noun(noun);
		item->_flags &= ~ITEMF_INVISIBLE;
		break;

	case OPCODE_CLEAR_WORD:
		item = getItem(instr);
		item->_word = 0;
		break;

	case OPCODE_CURRENT_OBJECT_NOT_VALID:
		func_set_test_result(func_state, !noun);
		break;

	case OPCODE_CURRENT_IS_OBJECT:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state, item != nullptr);
		break;

	case OPCODE_ELSE:
		func_state->_testResult = func_state->_elseResult;
		break;

	case OPCODE_HAVE_CURRENT_OBJECT:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
		break;

	case OPCODE_HAVE_OBJECT:
		item = getItem(instr);
		func_set_test_result(func_state, item->_room == ROOM_INVENTORY);
		break;

	case OPCODE_IN_ROOM:
		func_set_test_result(func_state, _currentRoom == instr->_operand[0]);
		break;

	case OPCODE_INVENTORY: {
		uint count = num_objects_in_room(ROOM_INVENTORY);
		if (count == 0) {
			console_println(stringLookup(STRING_INVENTORY_EMPTY).c_str());
			break;
		}

		console_println(stringLookup(STRING_INVENTORY).c_str());
		for (uint i = 0; i < _items.size(); i++) {
			item = &_items[i];
			if (item->_room == ROOM_INVENTORY)
				g_comprehend->print("%s\n",
					stringLookup(item->_stringDesc).c_str());
		}
		break;
	}

	case OPCODE_MOVE_DEFAULT:
		// Move in the direction dictated by the current verb
		if (verb - 1 >= NR_DIRECTIONS)
			error("Bad verb %d in move", verb);

		if (room->_direction[verb - 1])
			move_to(room->_direction[verb - 1]);
		else
			console_println(stringLookup(STRING_CANT_GO).c_str());
		break;

	case OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM:
		item = getItem(instr);
		move_object(item, _currentRoom);
		break;

	case OPCODE_MOVE_OBJECT_TO_ROOM:
		item = getItem(instr);
		move_object(item, instr->_operand[1]);
		break;

	case OPCODE_MOVE_TO_ROOM:
		if (instr->_operand[0] != 0xff)
			move_to(instr->_operand[0]);
		break;

	case OPCODE_OBJECT_IN_ROOM:
		item = getItem(instr);
		func_set_test_result(func_state, item->_room == instr->_operand[1]);
		break;

	case OPCODE_OBJECT_IS_NOWHERE:
		item = getItem(instr);
		func_set_test_result(func_state, item->_room == ROOM_NOWHERE);
		break;

	case OPCODE_OBJECT_PRESENT:
		item = getItem(instr);
		func_set_test_result(func_state, item->_room == _currentRoom);
		break;

	case OPCODE_OR:
		if (func_state->_orCount) {
			func_state->_orCount += 2;
		} else {
			func_state->_testResult = false;
			func_state->_orCount += 3;
		}
		break;

	case OPCODE_PRINT:
		console_println(instrStringLookup(instr->_operand[0], instr->_operand[1]).c_str());
		break;

	case OPCODE_RANDOM_MSG: {
		int msgId = (instr->_operand[2] << 8 | instr->_operand[1]) +
			getRandomNumber(instr->_operand[0] - 1);
		console_println(stringLookup(msgId).c_str());
		break;
	}

	case OPCODE_REMOVE_OBJECT:
		item = getItem(instr);
		move_object(item, ROOM_NOWHERE);
		break;

	case OPCODE_SAVE_ACTION:
		// Causes the next sentence inputed to re-use the first word of the current one.
		// As far as I'm aware, this is only used for handling responses to questions
		_nounState = NOUNSTATE_QUERY;
		// fall-through

	case OPCODE_CLEAR_LINE:
		// Resets the input line, removing any pending further actions that were specified
		Common::fill(&_inputLine[0], &_inputLine[INPUT_LINE_SIZE], 0);
		_inputLineIndex = 0;
		break;

	case OPCODE_SET_CAN_TAKE:
		item = get_item_by_noun(noun);
		item->_flags |= ITEMF_CAN_TAKE;
		break;

	case OPCODE_SET_FLAG:
		_flags[instr->_operand[0]] = true;
		break;

	case OPCODE_SET_FLAG40:
		item = getItem(instr);
		item->_flags |= ITEMF_UNKNOWN;
		break;

	case OPCODE_SET_INVISIBLE:
		item = get_item_by_noun(noun);
		item->_flags |= ITEMF_INVISIBLE;
		break;

	case OPCODE_SET_OBJECT_DESCRIPTION:
		item = getItem(instr);
		item->_stringDesc = (instr->_operand[2] << 8) | instr->_operand[1];
		break;

	case OPCODE_SET_ROOM_DESCRIPTION:
		room = get_room(instr->_operand[0]);
		switch (instr->_operand[2]) {
		case 0x80:
			room->_stringDesc = instr->_operand[1];
			break;
		case 0x81:
			room->_stringDesc = instr->_operand[1] + 0x100;
			break;
		case 0x82:
			room->_stringDesc = instr->_operand[1] + 0x200;
			break;
		default:
			error("Bad string desc %.2x:%.2x\n", instr->_operand[1], instr->_operand[2]);
			break;
		}
		break;

	case OPCODE_SET_ROOM_GRAPHIC:
		room = get_room(instr->_operand[0]);
		room->_graphic = instr->_operand[1];
		if (instr->_operand[0] == _currentRoom)
			_updateFlags |= UPDATE_GRAPHICS;
		break;

	case OPCODE_SET_STRING_REPLACEMENT1:
		_currentReplaceWord = (instr->_operand[0] & 0x80) - 1;
		break;

	case OPCODE_SET_STRING_REPLACEMENT2:
		_currentReplaceWord = instr->_operand[0] - 1;
		break;

	case OPCODE_SET_WORD:
		item = getItem(instr);
		item->_word = instr->_operand[1];
		break;

	case OPCODE_SPECIAL:
		// Game specific opcode
		_specialOpcode = instr->_operand[0];
		break;

	case OPCODE_TAKE_CURRENT_OBJECT:
		item = get_item_by_noun(noun);
		if (!item)
			error("Attempt to take object failed\n");

		move_object(item, ROOM_INVENTORY);
		break;

	case OPCODE_TAKE_OBJECT:
		item = getItem(instr);
		move_object(item, ROOM_INVENTORY);
		break;

	case OPCODE_TEST_FLAG:
		func_set_test_result(func_state, _flags[instr->_operand[0]]);
		break;

	case OPCODE_TEST_ROOM_FLAG:
		func_set_test_result(func_state, room->_flags & instr->_operand[0]);
		break;

	case OPCODE_TURN_TICK:
		_variables[VAR_TURN_COUNT]++;
		break;

	case OPCODE_VAR_ADD:
		_variables[instr->_operand[0]] += _variables[instr->_operand[1]];
		break;

	case OPCODE_VAR_DEC:
		_variables[instr->_operand[0]]--;
		break;

	case OPCODE_VAR_EQ2:
		func_set_test_result(func_state,
			_variables[instr->_operand[0]] == _variables[instr->_operand[1]]);
		break;

	case OPCODE_VAR_GT1:
		func_set_test_result(func_state,
			_variables[0] >
			_variables[instr->_operand[0]]);
		break;

	case OPCODE_VAR_GT2:
		func_set_test_result(func_state, _variables[instr->_operand[0]] >
			_variables[instr->_operand[1]]);
		break;

	case OPCODE_VAR_GTE1:
		func_set_test_result(func_state,
			_variables[0] >=
			_variables[instr->_operand[0]]);
		break;

	case OPCODE_VAR_GTE2:
		func_set_test_result(func_state,
			_variables[instr->_operand[0]] >=
			_variables[instr->_operand[1]]);
		break;

	case OPCODE_VAR_EQ1:
		func_set_test_result(func_state,
			_variables[0] ==
			_variables[instr->_operand[0]]);
		break;

	case OPCODE_VAR_INC:
		_variables[instr->_operand[0]]++;
		break;

	case OPCODE_VAR_SUB:
		_variables[instr->_operand[0]] -= _variables[instr->_operand[1]];
		break;

	default:
		if (instr->_opcode & 0x80) {
			warning("Unhandled command opcode %.2x", opcode);
		} else {
			warning("Unhandled test opcode %.2x - returning false", opcode);
			func_set_test_result(func_state, false);
		}
		break;
	}
}

void ComprehendGameOpcodes::func_set_test_result(FunctionState *func_state, bool value) {
	if (func_state->_orCount == 0) {
		/* And */
		if (func_state->_and) {
			if (!value)
				func_state->_testResult = false;
		} else {
			func_state->_testResult = value;
			func_state->_and = true;
		}

	} else {
		/* Or */
		if (value)
			func_state->_testResult = value;
	}
}

bool ComprehendGameOpcodes::isItemPresent(Item *item) const {
	return item && (
		item->_room == _currentRoom || item->_room == ROOM_INVENTORY
		|| item->_room == ROOM_CONTAINER
		);
}

Item *ComprehendGameOpcodes::getItem(const Instruction *instr) {
	return get_item(instr->_operand[0] - 1);
}

/*-------------------------------------------------------*/

ComprehendGameV1::ComprehendGameV1() {
	_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
	_opcodeMap[0x02] = OPCODE_VAR_GT2;
	_opcodeMap[0x04] = OPCODE_OR;
	_opcodeMap[0x05] = OPCODE_IN_ROOM;
	_opcodeMap[0x06] = OPCODE_VAR_EQ2;
	_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
	_opcodeMap[0x09] = OPCODE_OBJECT_PRESENT;
	_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
	_opcodeMap[0x0c] = OPCODE_ELSE;
	_opcodeMap[0x0e] = OPCODE_OBJECT_IN_ROOM;
	_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
	_opcodeMap[0x18] = OPCODE_INVENTORY_FULL;
	_opcodeMap[0x19] = OPCODE_TEST_FLAG;
	_opcodeMap[0x1d] = OPCODE_CURRENT_OBJECT_IN_ROOM;
	_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
	_opcodeMap[0x21] = OPCODE_OBJECT_IS_NOT_NOWHERE;
	_opcodeMap[0x24] = OPCODE_CURRENT_OBJECT_PRESENT;
	_opcodeMap[0x25] = OPCODE_VAR_GT1;
	_opcodeMap[0x29] = OPCODE_VAR_EQ1;
	_opcodeMap[0x2d] = OPCODE_VAR_GTE1;
	_opcodeMap[0x31] = OPCODE_TEST_ROOM_FLAG;
	_opcodeMap[0x41] = OPCODE_NOT_HAVE_OBJECT;
	_opcodeMap[0x45] = OPCODE_NOT_IN_ROOM;
	_opcodeMap[0x48] = OPCODE_CURRENT_OBJECT_NOT_PRESENT;
	_opcodeMap[0x49] = OPCODE_OBJECT_NOT_IN_ROOM;
	_opcodeMap[0x4E] = OPCODE_TEST_FALSE;
	_opcodeMap[0x50] = OPCODE_CURRENT_OBJECT_IS_NOWHERE;
	_opcodeMap[0x59] = OPCODE_TEST_NOT_FLAG;
	_opcodeMap[0x5D] = OPCODE_TEST_FALSE;
	_opcodeMap[0x60] = OPCODE_NOT_HAVE_CURRENT_OBJECT;
	_opcodeMap[0x61] = OPCODE_OBJECT_IS_NOWHERE;
	_opcodeMap[0x64] = OPCODE_CURRENT_OBJECT_NOT_IN_ROOM;
	_opcodeMap[0x68] = OPCODE_CURRENT_OBJECT_NOT_TAKEABLE;
	_opcodeMap[0x71] = OPCODE_TEST_NOT_ROOM_FLAG;
	_opcodeMap[0x80] = OPCODE_INVENTORY;
	_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
	_opcodeMap[0x82] = OPCODE_MOVE_OBJECT_TO_ROOM;
	_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
	_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
	_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
	_opcodeMap[0x86] = OPCODE_VAR_ADD;
	_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
	_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
	_opcodeMap[0x89] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
	_opcodeMap[0x8a] = OPCODE_VAR_SUB;
	_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
	_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
	_opcodeMap[0x8d] = OPCODE_SET_CAN_TAKE;
	_opcodeMap[0x8e] = OPCODE_PRINT;
	_opcodeMap[0x91] = OPCODE_CLEAR_CAN_TAKE;
	_opcodeMap[0x95] = OPCODE_REMOVE_OBJECT;
	_opcodeMap[0x99] = OPCODE_SET_FLAG;
	_opcodeMap[0x92] = OPCODE_CALL_FUNC;
	_opcodeMap[0x98] = OPCODE_TURN_TICK;
	_opcodeMap[0x9a] = OPCODE_SET_WORD;
	_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
	_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
	_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
	_opcodeMap[0xa1] = OPCODE_SPECIAL;
	_opcodeMap[0xa4] = OPCODE_DROP_CURRENT_OBJECT;
	_opcodeMap[0xa2] = OPCODE_SET_ROOM_GRAPHIC;
	_opcodeMap[0xad] = OPCODE_CLEAR_WORD;
	_opcodeMap[0xb0] = OPCODE_REMOVE_CURRENT_OBJECT;
	_opcodeMap[0xb1] = OPCODE_MOVE_DIR;
	_opcodeMap[0xb5] = OPCODE_SET_STRING_REPLACEMENT1;
	_opcodeMap[0xb9] = OPCODE_SET_STRING_REPLACEMENT2;
	_opcodeMap[0xbd] = OPCODE_VAR_INC;
	_opcodeMap[0xc1] = OPCODE_VAR_DEC;
	_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
	_opcodeMap[0xc9] = OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM;
	_opcodeMap[0xcd] = OPCODE_CLEAR_INVISIBLE;
	_opcodeMap[0xd1] = OPCODE_SET_INVISIBLE;
	_opcodeMap[0xd5] = OPCODE_CLEAR_FLAG40;
	_opcodeMap[0xd9] = OPCODE_SET_FLAG40;
}

void ComprehendGameV1::execute_opcode(const Instruction *instr, const Sentence *sentence,
		FunctionState *func_state) {
	byte noun = sentence ? sentence->_formattedWords[2] : 0;
	Room *room = get_room(_currentRoom);
	Item *item;
	uint count;

	switch (_opcodeMap[getOpcode(instr)]) {
	case OPCODE_INVENTORY_FULL:
		item = get_item_by_noun(noun);

		if (g_debugger->_invLimit)
			func_set_test_result(func_state, _variables[VAR_INVENTORY_WEIGHT] +
			(item->_flags & ITEMF_WEIGHT_MASK) > _variables[VAR_INVENTORY_LIMIT]);
		else
			// Allow for an unlimited number of items in inventory
			func_set_test_result(func_state, false);
		break;

	case OPCODE_OBJECT_NOT_PRESENT:
		item = getItem(instr);
		func_set_test_result(func_state, !isItemPresent(item));
		break;

	case OPCODE_SET_STRING_REPLACEMENT3:
		_currentReplaceWord = instr->_operand[0] - 1;
		break;

	/*--------------------------------------*/

	case OPCODE_TEST_NOT_ROOM_FLAG:
		func_set_test_result(func_state,
			!(room->_flags & instr->_operand[0]));
		break;

	case OPCODE_NOT_IN_ROOM:
		func_set_test_result(func_state,
			_currentRoom != instr->_operand[0]);
		break;

	case OPCODE_OBJECT_NOT_IN_ROOM:
		item = getItem(instr);
		func_set_test_result(func_state, !item || item->_room != _currentRoom);
		break;

	case OPCODE_CURRENT_OBJECT_NOT_IN_ROOM:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state, !item || item->_room != _currentRoom);
		break;

	case OPCODE_DESCRIBE_CURRENT_OBJECT:
		/*
		 * This opcode is only used in version 2
		 * FIXME - unsure what the single operand is for.
		 */
		item = get_item_by_noun(noun);
		g_comprehend->print("%s\n", stringLookup(item->_longString).c_str());
		break;

	case OPCODE_CURRENT_OBJECT_IN_ROOM: {
		/* FIXME - use common code for these two ops */
		bool test = false;

		if (noun) {
			for (uint i = 0; i < _items.size(); i++) {
				Item *itemP = &_items[i];

				if (itemP->_word == noun && itemP->_room == instr->_operand[0]) {
					test = true;
					break;
				}
			}
		}

		func_set_test_result(func_state, test);
		break;
	}

	case OPCODE_CURRENT_OBJECT_PRESENT:
		item = get_item_by_noun(noun);
		if (item)
			func_set_test_result(func_state,
				item->_room == _currentRoom);
		else
			func_set_test_result(func_state, false);
		break;

	case OPCODE_NOT_HAVE_CURRENT_OBJECT:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state,
			!item || item->_room != ROOM_INVENTORY);
		break;

	case OPCODE_NOT_HAVE_OBJECT:
		item = getItem(instr);
		func_set_test_result(func_state,
			item->_room != ROOM_INVENTORY);
		break;

	case OPCODE_CURRENT_OBJECT_NOT_TAKEABLE:
		item = get_item_by_noun(noun);
		if (!item)
			func_set_test_result(func_state, true);
		else
			func_set_test_result(func_state,
				!(item->_flags & ITEMF_CAN_TAKE));
		break;

	case OPCODE_CURRENT_OBJECT_IS_NOWHERE:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state, item && item->_room == ROOM_NOWHERE);
		break;

	case OPCODE_OBJECT_IS_NOT_NOWHERE:
		item = getItem(instr);
		func_set_test_result(func_state, item->_room != ROOM_NOWHERE);
		break;

	case OPCODE_CURRENT_OBJECT_NOT_PRESENT:
		item = get_item_by_noun(noun);
		func_set_test_result(func_state, !isItemPresent(item));
		break;

	case OPCODE_REMOVE_CURRENT_OBJECT:
		item = get_item_by_noun(noun);
		move_object(item, ROOM_NOWHERE);
		break;

	case OPCODE_INVENTORY_ROOM:
		count = num_objects_in_room(instr->_operand[0]);
		if (count == 0) {
			console_println(stringLookup(instr->_operand[1] + 1).c_str());
			break;
		}

		console_println(stringLookup(instr->_operand[1]).c_str());
		for (uint i = 0; i < _items.size(); i++) {
			item = &_items[i];
			if (item->_room == instr->_operand[0])
				g_comprehend->print("%s\n",
					stringLookup(item->_stringDesc).c_str());
		}
		break;

	case OPCODE_MOVE_CURRENT_OBJECT_TO_ROOM:
		item = get_item_by_noun(noun);
		if (!item)
			error("Bad current object\n");

		move_object(item, instr->_operand[0]);
		break;

	case OPCODE_DROP_OBJECT:
		item = getItem(instr);
		move_object(item, _currentRoom);
		break;

	case OPCODE_DROP_CURRENT_OBJECT:
		item = get_item_by_noun(noun);
		if (!item)
			error("Attempt to take object failed\n");

		move_object(item, _currentRoom);
		break;

	case OPCODE_TEST_NOT_FLAG:
		func_set_test_result(func_state,
			!_flags[instr->_operand[0]]);
		break;

	case OPCODE_TEST_FALSE:
		// The original had two opcodes mapped to the same code that does
		// a test, but ignores the result, and is always false
		func_set_test_result(func_state, false);
		break;

	case OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT:
#if 1
		error("TODO: OPCODE_SET_CURRENT_NOUN_STRING_REPLACEMENT");
#else
		/*
		 * FIXME - Not sure what the operand is for,
		 * maybe capitalisation?
		 */
		if (noun && (noun->_type & WORD_TYPE_NOUN_PLURAL))
			_currentReplaceWord = 3;
		else if (noun && (noun->_type & WORD_TYPE_FEMALE))
			_currentReplaceWord = 0;
		else if (noun && (noun->_type & WORD_TYPE_MALE))
			_currentReplaceWord = 1;
		else
			_currentReplaceWord = 2;
#endif
		break;

	case OPCODE_MOVE_DIR:
		doMovementVerb(instr->_operand[0]);
		break;

	default:
		ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
		break;
	}
}

/*-------------------------------------------------------*/

ComprehendGameV2::ComprehendGameV2() {
	_opcodeMap[0x01] = OPCODE_HAVE_OBJECT;
	_opcodeMap[0x02] = OPCODE_VAR_GT2;
	_opcodeMap[0x04] = OPCODE_OR;
	_opcodeMap[0x05] = OPCODE_IN_ROOM;
	_opcodeMap[0x06] = OPCODE_VAR_EQ2;
	_opcodeMap[0x08] = OPCODE_CURRENT_IS_OBJECT;
	_opcodeMap[0x09] = OPCODE_VAR_GT1;
	_opcodeMap[0x0a] = OPCODE_VAR_GTE2;
	_opcodeMap[0x0c] = OPCODE_ELSE;
	_opcodeMap[0x0d] = OPCODE_VAR_EQ1;
	_opcodeMap[0x11] = OPCODE_OBJECT_IS_NOWHERE;
	_opcodeMap[0x14] = OPCODE_CURRENT_OBJECT_NOT_VALID;
	_opcodeMap[0x15] = OPCODE_INVENTORY_FULL_X;
	_opcodeMap[0x19] = OPCODE_TEST_FLAG;
	_opcodeMap[0x1d] = OPCODE_TEST_ROOM_FLAG;
	_opcodeMap[0x20] = OPCODE_HAVE_CURRENT_OBJECT;
	_opcodeMap[0x21] = OPCODE_OBJECT_PRESENT;
	_opcodeMap[0x22] = OPCODE_OBJECT_IN_ROOM;
	_opcodeMap[0x25] = OPCODE_OBJECT_TAKEABLE;
	_opcodeMap[0x29] = OPCODE_INVENTORY_FULL;
	_opcodeMap[0x2d] = OPCODE_OBJECT_CAN_TAKE;
	_opcodeMap[0x80] = OPCODE_INVENTORY;
	_opcodeMap[0x81] = OPCODE_TAKE_OBJECT;
	_opcodeMap[0x83] = OPCODE_RANDOM_MSG;
	_opcodeMap[0x84] = OPCODE_SAVE_ACTION;
	_opcodeMap[0x85] = OPCODE_MOVE_TO_ROOM;
	_opcodeMap[0x86] = OPCODE_VAR_ADD;
	_opcodeMap[0x87] = OPCODE_SET_ROOM_DESCRIPTION;
	_opcodeMap[0x88] = OPCODE_CLEAR_LINE;
	_opcodeMap[0x89] = OPCODE_SPECIAL;
	_opcodeMap[0x8a] = OPCODE_VAR_SUB;
	_opcodeMap[0x8b] = OPCODE_SET_OBJECT_DESCRIPTION;
	_opcodeMap[0x8c] = OPCODE_MOVE_DEFAULT;
	_opcodeMap[0x8e] = OPCODE_PRINT;
	_opcodeMap[0x8f] = OPCODE_SET_OBJECT_LONG_DESCRIPTION;
	_opcodeMap[0x90] = OPCODE_WAIT_KEY;
	_opcodeMap[0x92] = OPCODE_CALL_FUNC;
	_opcodeMap[0x95] = OPCODE_CLEAR_WORD;
	_opcodeMap[0x96] = OPCODE_CALL_FUNC2;
	_opcodeMap[0x98] = OPCODE_TURN_TICK;
	_opcodeMap[0x99] = OPCODE_SET_FLAG;
	_opcodeMap[0x9a] = OPCODE_SET_WORD;
	_opcodeMap[0x9d] = OPCODE_CLEAR_FLAG;
	_opcodeMap[0xa0] = OPCODE_TAKE_CURRENT_OBJECT;
	_opcodeMap[0xa1] = OPCODE_CLEAR_FLAG40;
	_opcodeMap[0xa2] = OPCODE_MOVE_OBJECT_TO_ROOM;
	_opcodeMap[0xa5] = OPCODE_SET_FLAG40;
	_opcodeMap[0xa9] = OPCODE_CLEAR_INVISIBLE;
	_opcodeMap[0xad] = OPCODE_SET_INVISIBLE;
	_opcodeMap[0xc1] = OPCODE_VAR_DEC;
	_opcodeMap[0xc2] = OPCODE_SET_ROOM_GRAPHIC;
	_opcodeMap[0xc5] = OPCODE_SET_STRING_REPLACEMENT3;
	_opcodeMap[0xc9] = OPCODE_SET_STRING_REPLACEMENT1;
	_opcodeMap[0xcd] = OPCODE_SET_STRING_REPLACEMENT2;
	_opcodeMap[0xd1] = OPCODE_MOVE_DIR;
	_opcodeMap[0xd5] = OPCODE_DRAW_ROOM;
	_opcodeMap[0xd9] = OPCODE_DRAW_OBJECT;
	_opcodeMap[0xdd] = OPCODE_VAR_INC;
	_opcodeMap[0xe1] = OPCODE_MOVE_OBJECT_TO_CURRENT_ROOM;
	_opcodeMap[0xe5] = OPCODE_SET_CAN_TAKE;
	_opcodeMap[0xe9] = OPCODE_CLEAR_CAN_TAKE;
	_opcodeMap[0xed] = OPCODE_REMOVE_OBJECT;

#if 0
	_opcodeMap[0x9e] = OPCODE_INVENTORY_ROOM;
	_opcodeMap[0xc6] = OPCODE_SET_OBJECT_GRAPHIC;
	_opcodeMap[0xf0] = OPCODE_DROP_CURRENT_OBJECT;
	_opcodeMap[0xfc] = OPCODE_REMOVE_CURRENT_OBJECT;
#endif
}

void ComprehendGameV2::execute_opcode(const Instruction *instr, const Sentence *sentence,
		FunctionState *func_state) {
	Instruction instrCopy;
	byte noun = sentence ? sentence->_formattedWords[2] : 0;
	Room *room = get_room(_currentRoom);
	Item *item;

	// In case a single opcode is being executed outside of a function, use a dummy function state
	FunctionState dummyState;
	if (!func_state)
		func_state = &dummyState;

	if ((instr->_opcode & 0x30) == 0x30) {
		// First operand comes from entered sentence noun, shifting out existing operands
		instrCopy = *instr;
		instrCopy._operand[2] = instrCopy._operand[1];
		instrCopy._operand[1] = instrCopy._operand[0];
		instrCopy._operand[0] = get_item_id(noun) + 1;
		instr = &instrCopy;
	}

	func_state->_notComparison = (instr->_opcode & 0x40) != 0;

	switch (_opcodeMap[getOpcode(instr)]) {
	case OPCODE_CLEAR_INVISIBLE:
		item = get_item_by_noun(noun);
		item->_flags &= ~ITEMF_INVISIBLE;
		break;

	case OPCODE_DRAW_OBJECT:
		g_comprehend->drawItemPicture(instr->_operand[0] - 1);
		break;

	case OPCODE_DRAW_ROOM:
		g_comprehend->drawLocationPicture(instr->_operand[0] - 1);
		g_comprehend->readChar();
		break;

	case OPCODE_INVENTORY_FULL:
		item = get_item_by_noun(noun);

		weighInventory();
		func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
			_variables[VAR_INVENTORY_LIMIT]);
		break;

	case OPCODE_INVENTORY_FULL_X:
		item = get_item_by_noun(noun);

		weighInventory();
		func_set_test_result(func_state, _totalInventoryWeight + (item->_flags & ITEMF_WEIGHT_MASK) >
			_variables[instr->_operand[1]]);
		break;

	case OPCODE_MOVE_DIR:
		if (room->_direction[instr->_operand[0] - 1])
			move_to(room->_direction[instr->_operand[0] - 1]);
		else
			console_println(stringLookup(STRING_CANT_GO).c_str());
		break;

	case OPCODE_OBJECT_TAKEABLE:
		// WORKAROUND: Trying to get non-items in OO-Topos
		func_set_test_result(func_state, instr->_operand[0]
			&& (getItem(instr)->_flags & ITEMF_WEIGHT_MASK) != ITEMF_WEIGHT_MASK);
		break;

	case OPCODE_OBJECT_CAN_TAKE:
		item = getItem(instr);
		func_set_test_result(func_state, item->_flags & ITEMF_CAN_TAKE);
		break;

	case OPCODE_SET_OBJECT_GRAPHIC:
		item = getItem(instr);
		item->_graphic = instr->_operand[1];
		if (item->_room == _currentRoom)
			_updateFlags |= UPDATE_GRAPHICS;
		break;

	case OPCODE_SET_OBJECT_LONG_DESCRIPTION:
		item = getItem(instr);
		item->_longString = (instr->_operand[2] << 8) | instr->_operand[1];
		break;

	case OPCODE_SET_STRING_REPLACEMENT3: {
		int articleNum, bits = _wordFlags;
		for (articleNum = 3; articleNum >= 0; --articleNum, bits <<= 1) {
			if (bits >= 0x100)
				break;
		}
		if (articleNum == -1)
			articleNum = 2;

		_currentReplaceWord = instr->_operand[0] + articleNum - 1;
		break;
	}

	case OPCODE_WAIT_KEY:
		console_get_key();
		break;

	default:
		ComprehendGameOpcodes::execute_opcode(instr, sentence, func_state);
		break;
	}
}

byte ComprehendGameV2::getOpcode(const Instruction *instr) {
	// Special pre-processing for opcodes
	byte opcode = instr->_opcode;
	if (!(opcode & 0x80))
		opcode &= 0x3f;
	if ((opcode & 0x30) == 0x30) {
		opcode = (opcode & ~0x10) + 1;
	}

	return opcode;
}

void ComprehendGameV2::func_set_test_result(FunctionState *func_state, bool value) {
	ComprehendGameOpcodes::func_set_test_result(func_state, value ^ func_state->_notComparison);
}


} // namespace Comprehend
} // namespace Glk
