/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001-2006 The ScummVM project
 *
 * 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.
 *
 * $URL$
 * $Id$
 *
 */

// Item script opcodes for Simon1/Simon2

#include "common/stdafx.h"
#include "simon/simon.h"
#include "simon/intern.h"

#include "common/system.h"

#ifdef _WIN32_WCE
extern bool isSmartphone(void);
#endif

namespace Simon {

int SimonEngine::runScript() {
	byte opcode;
	bool flag, condition;

	do {
		if (_continousMainScript)
			dumpOpcode(_codePtr);

		opcode = getByte();
		if (opcode == 0xFF)
			return 0;

		if (_runScriptReturn1)
			return 1;

		/* Invert condition? */
		flag = false;
		if (opcode == 0) {
			flag = true;
			opcode = getByte();
			if (opcode == 0xFF)
				return 0;
		}

		condition = true;

		switch (opcode) {
		case 1:{										/* ptrA parent is */
				condition = (getItem1Ptr()->parent == getNextItemID());
			}
			break;

		case 2:{										/* ptrA parent is not */
				condition = (getItem1Ptr()->parent != getNextItemID());
			}
			break;

		case 5:{										/* parent is 1 */
				condition = (getNextItemPtr()->parent == getItem1ID());
			}
			break;

		case 6:{										/* parent isnot 1 */
				condition = (getNextItemPtr()->parent != getItem1ID());
			}
			break;

		case 7:{										/* parent is */
				Item *item = getNextItemPtr();
				condition = (item->parent == getNextItemID());
			}
			break;

		case 11:{									/* is zero */
				condition = (getNextVarContents() == 0);
			}
			break;

		case 12:{									/* isnot zero */
				condition = (getNextVarContents() != 0);
			}
			break;

		case 13:{									/* equal */
				uint tmp = getNextVarContents();
				condition = (tmp == getVarOrWord());
			}
			break;

		case 14:{									/* not equal */
				uint tmp = getNextVarContents();
				condition = (tmp != getVarOrWord());
			}
			break;

		case 15:{									/* is greater */
				uint tmp = getNextVarContents();
				condition = (tmp > getVarOrWord());
			}
			break;

		case 16:{									/* is less */
				uint tmp = getNextVarContents();
				condition = (tmp < getVarOrWord());
			}
			break;

		case 17:{									/* is eq f */
				uint tmp = getNextVarContents();
				condition = (tmp == getNextVarContents());
			}
			break;

		case 18:{									/* is not equal f */
				uint tmp = getNextVarContents();
				condition = (tmp != getNextVarContents());
			}
			break;

		case 19:{									/* is greater f */
				uint tmp = getNextVarContents();
				condition = (tmp < getNextVarContents());
			}
			break;

		case 20:{									/* is less f */
				uint tmp = getNextVarContents();
				condition = (tmp > getNextVarContents());
			}
			break;

		case 23:{
				condition = o_chance(getVarOrWord());
			}
			break;

		case 25:{									/* is room */
				condition = isRoom(getNextItemPtr());
			}
			break;

		case 26:{									/* is object */
				condition = isObject(getNextItemPtr());
			}
			break;

		case 27:{									/* item state is */
				Item *item = getNextItemPtr();
				condition = ((uint) item->state == getVarOrWord());
			}
			break;

		case 28:{									/* item has prop */
				Child2 *child = (Child2 *)findChildOfType(getNextItemPtr(), 2);
				byte num = getVarOrByte();
				condition = child != NULL && (child->avail_props & (1 << num)) != 0;
			} break;

		case 31:{									/* set no parent */
				setItemParent(getNextItemPtr(), NULL);
			}
			break;

		case 33:{									/* set item parent */
				Item *item = getNextItemPtr();
				setItemParent(item, getNextItemPtr());
			}
			break;

		case 36:{									/* copy var */
				uint value = getNextVarContents();
				writeNextVarContents(value);
			}
			break;

		case 41:{									/* zero var */
				writeNextVarContents(0);
			}
			break;

		case 42:{									/* set var */
				uint var = getVarOrByte();
				writeVariable(var, getVarOrWord());
			}
			break;

		case 43:{									/* add */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) + getVarOrWord());
			}
			break;

		case 44:{									/* sub */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) - getVarOrWord());
			}
			break;

		case 45:{									/* add f */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) + getNextVarContents());
			}
			break;

		case 46:{									/* sub f */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) - getNextVarContents());
			}
			break;

		case 47:{									/* mul */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) * getVarOrWord());
			}
			break;

		case 48:{									/* div */
				uint var = getVarOrByte();
				int value = getVarOrWord();
				if (value == 0)
					error("Division by zero in div");
				writeVariable(var, readVariable(var) / value);
			}
			break;

		case 49:{									/* mul f */
				uint var = getVarOrByte();
				writeVariable(var, readVariable(var) * getNextVarContents());
			}
			break;

		case 50:{									/* div f */
				uint var = getVarOrByte();
				int value = getNextVarContents();
				if (value == 0)
					error("Division by zero in div f");
				writeVariable(var, readVariable(var) / value);
			}
			break;

		case 51:{									/* mod */
				uint var = getVarOrByte();
				int value = getVarOrWord();
				if (value == 0)
					error("Division by zero in mod");
				writeVariable(var, readVariable(var) % value);
			}
			break;

		case 52:{									/* mod f */
				uint var = getVarOrByte();
				int value = getNextVarContents();
				if (value == 0)
					error("Division by zero in mod f");
				writeVariable(var, readVariable(var) % value);
			}
			break;

		case 53:{									/* random */
				uint var = getVarOrByte();
				uint value = (uint16)getVarOrWord();

				// Disable random in simon1amiga for now
				// Since copy protection screen is currently unreadable
				if (getPlatform() == Common::kPlatformAmiga)
					writeVariable(var, 4);
				else
					writeVariable(var, _rnd.getRandomNumber(value - 1));
			}
			break;

		case 55:{									/* set itemA parent */
				setItemParent(getItem1Ptr(), getNextItemPtr());
			}
			break;

		case 56:{									/* set child2 fr bit */
				Child2 *child = (Child2 *)findChildOfType(getNextItemPtr(), 2);
				int value = getVarOrByte();
				if (child != NULL && value >= 0x10)
					child->avail_props |= 1 << value;
			}
			break;

		case 57:{									/* clear child2 fr bit */
				Child2 *child = (Child2 *)findChildOfType(getNextItemPtr(), 2);
				int value = getVarOrByte();
				if (child != NULL && value >= 0x10)
					child->avail_props &= ~(1 << value);
			}
			break;

		case 58:{									/* make siblings */
				Item *item = getNextItemPtr();
				setItemParent(item, derefItem(getNextItemPtr()->parent));
			}
			break;

		case 59:{									/* item inc state */
				Item *item = getNextItemPtr();
				if (item->state <= 30000)
					setItemState(item, item->state + 1);
			}
			break;

		case 60:{									/* item dec state */
				Item *item = getNextItemPtr();
				if (item->state >= 0)
					setItemState(item, item->state - 1);
			}
			break;

		case 61:{									/* item set state */
				Item *item = getNextItemPtr();
				int value = getVarOrWord();
				if (value < 0)
					value = 0;
				if (value > 30000)
					value = 30000;
				setItemState(item, value);
			}
			break;

		case 62:{									/* show int */
				showMessageFormat("%d", getNextVarContents());
			}
			break;

		case 63:{									/* show string nl */
				showMessageFormat("%s\n", getStringPtrByID(getNextStringID()));
			}
			break;

		case 64:{									/* show string */
				showMessageFormat("%s", getStringPtrByID(getNextStringID()));
			}
			break;

		case 65:{									/* add hit area */
				int id = getVarOrWord();
				int x = getVarOrWord();
				int y = getVarOrWord();
				int w = getVarOrWord();
				int h = getVarOrWord();
				int number = getVarOrByte();
				if (number < 20)
					addNewHitArea(id, x, y, w, h, (number << 8) + 129, 0xD0, _dummyItem2);
			}
			break;

		case 66:{									/* set item name */
				uint var = getVarOrByte();
				uint string_id = getNextStringID();
				if (var < 20)
					_stringIdArray2[var] = string_id;
			}
			break;

		case 67:{									/* set item description */
				uint var = getVarOrByte();
				uint string_id = getNextStringID();
				if (getFeatures() & GF_TALKIE) {
					uint speechId = getNextWord();
					if (var < 20) {
						_stringIdArray3[var] = string_id;
						_speechIdArray4[var] = speechId;
					}
				} else {
					if (var < 20) {
						_stringIdArray3[var] = string_id;
					}
				}
			}
			break;

		case 68:{									/* exit interpreter */
				shutdown();
			}
			break;

		case 69:{									/* return 1 */
				return 1;
			}

		case 70:{									/* show string from array */
				const char *str = (const char *)getStringPtrByID(_stringIdArray3[getVarOrByte()]);

				if (getGameType() == GType_SIMON2) {
					writeVariable(51, strlen(str) / 53 * 8 + 8);
				}

				showMessageFormat("%s\n", str);
			}
			break;

		case 71:{									/* start subroutine */
				Subroutine *sub = getSubroutineByID(getVarOrWord());
				if (sub != NULL)
					startSubroutine(sub);
			}
			break;

		case 76:{									/* add timeout */
				uint timeout = getVarOrWord();
				addTimeEvent(timeout, getVarOrWord());
			}
			break;

		case 77:{									/* has item minus 1 */
				condition = _subjectItem != NULL;
			}
			break;

		case 78:{									/* has item minus 3 */
				condition = _objectItem != NULL;
			}
			break;

		case 79:{									/* childstruct fr2 is */
				Child2 *child = (Child2 *)findChildOfType(getNextItemPtr(), 2);
				uint string_id = getNextStringID();
				condition = (child != NULL) && child->string_id == string_id;
			}
			break;

		case 80:{									/* item equal */
				condition = getNextItemPtr() == getNextItemPtr();
			}
			break;

		case 82:{									/* debug opcode */
				getVarOrByte();
			}
			break;

		case 83:{									/* restart subroutine */
				if (getGameType() == GType_SIMON2 || getGameType() == GType_FF)
					o_83_helper();
				return -10;
			}

		case 87:{									/* comment */
				getNextStringID();
			}
			break;

		case 88:{									/* stop animation */
				_lockWord |= 0x10;
			}
			break;

		case 89:{									/* restart animation */
				_lockWord &= ~0x10;
			}
			break;

		case 90:{									/* set minusitem to parent */
				Item *item = derefItem(getNextItemPtr()->parent);
				switch (getVarOrByte()) {
				case 0:
					_objectItem = item;
					break;
				case 1:
					_subjectItem = item;
					break;
				default:
					error("set minusitem to parent, invalid subcode");
				}
			}
			break;

		case 91:{									/* set minusitem to sibling */
				Item *item = derefItem(getNextItemPtr()->sibling);
				switch (getVarOrByte()) {
				case 0:
					_objectItem = item;
					break;
				case 1:
					_subjectItem = item;
					break;
				default:
					error("set minusitem to sibling, invalid subcode");
				}
			}
			break;

		case 92:{									/* set minusitem to child */
				Item *item = derefItem(getNextItemPtr()->child);
				switch (getVarOrByte()) {
				case 0:
					_objectItem = item;
					break;
				case 1:
					_subjectItem = item;
					break;
				default:
					error("set minusitem to child, invalid subcode");
				}
			}
			break;

		case 96:{
				uint val = getVarOrWord();
				o_set_video_mode(getVarOrByte(), val);
			}
			break;

		case 97:{									/* load vga */
				o_loadZone(getVarOrWord());
			}
			break;

		case 98:{									/* start vga */
				uint vga_res, vgaSpriteId, windowNum, x, y, palette;
				if (getGameType() == GType_SIMON1) {
					vgaSpriteId = getVarOrWord();
					vga_res = vgaSpriteId / 100;
				} else {
					vga_res = getVarOrWord();
					vgaSpriteId = getVarOrWord();
				}
				windowNum = getVarOrByte();
				x = getVarOrWord();
				y = getVarOrWord();
				palette = getVarOrWord();
				loadSprite(windowNum, vga_res, vgaSpriteId, x, y, palette);
			}
			break;

		case 99:{									/* kill sprite */
				if (getGameType() == GType_SIMON1) {
					o_kill_sprite_simon1(getVarOrWord());
				} else {
					uint a = getVarOrWord();
					uint b = getVarOrWord();
					o_kill_sprite_simon2(a, b);
				}
			}
			break;

		case 100:{									/* vga reset */
				o_vga_reset();
			}
			break;

		case 101:{
				uint a = getVarOrByte();
				uint b = getVarOrWord();
				uint c = getVarOrWord();
				uint d = getVarOrWord();
				uint e = getVarOrWord();
				uint f = getVarOrWord();
				uint g = getVarOrWord();
				o_defineWindow(a, b, c, d, e, f, g, 0);
			}
			break;

		case 102:{
				changeWindow(getVarOrByte() & 7);
			}
			break;

		case 103:{
				o_unk_103();
			}
			break;

		case 104:{
				closeWindow(getVarOrByte() & 7);
			}
			break;

		case 107:{									/* add item hitarea */
				uint flags = 0;
				uint id = getVarOrWord();
				uint params = id / 1000;
				uint x, y, w, h, unk3;
				Item *item;

				id = id % 1000;

				if (params & 1)
					flags |= 8;
				if (params & 2)
					flags |= 4;
				if (params & 4)
					flags |= 0x80;
				if (params & 8)
					flags |= 1;
				if (params & 16)
					flags |= 0x10;

				x = getVarOrWord();
				y = getVarOrWord();
				w = getVarOrWord();
				h = getVarOrWord();
				item = getNextItemPtrStrange();
				unk3 = getVarOrWord();
				if (x >= 1000) {
					unk3 += 0x4000;
					x -= 1000;
				}
				addNewHitArea(id, x, y, w, h, flags, unk3, item);
			}
			break;

		case 108:{									/* delete hitarea */
				delete_hitarea(getVarOrWord());
			}
			break;

		case 109:{									/* clear hitarea bit 0x40 */
				clear_hitarea_bit_0x40(getVarOrWord());
			}
			break;

		case 110:{									/* set hitarea bit 0x40 */
				set_hitarea_bit_0x40(getVarOrWord());
			}
			break;

		case 111:{									/* set hitarea xy */
				uint hitarea_id = getVarOrWord();
				uint x = getVarOrWord();
				uint y = getVarOrWord();
				set_hitarea_x_y(hitarea_id, x, y);
			}
			break;

		case 114:{
				Item *item = getNextItemPtr();
				uint fcs_index = getVarOrByte();
				mouseOff();
				drawIconArray(fcs_index, item, 0, 0);
				mouseOn();
			}
			break;

		case 115:{									/* item has flag */
				Item *item = getNextItemPtr();
				condition = (item->classFlags & (1 << getVarOrByte())) != 0;
			}
			break;

		case 116:{									/* item set flag */
				Item *item = getNextItemPtr();
				item->classFlags |= (1 << getVarOrByte());
			}
			break;

		case 117:{									/* item clear flag */
				Item *item = getNextItemPtr();
				item->classFlags &= ~(1 << getVarOrByte());
			}
			break;

		case 119:{									/* wait vga */
				uint var = getVarOrWord();
				_scriptVar2 = (var == 200);

				if (var != 200 || !_skipVgaWait)
					o_waitForSync(var);
				_skipVgaWait = false;
			}
			break;

		case 120:{
				o_sync(getVarOrWord());
			}
			break;

		case 121:{									/* set vga item */
				uint slot = getVarOrByte();
				_vcItemArray[slot] = getNextItemPtr();
			}
			break;

		case 122:{									/* oracle text down */
				if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2)
					goto invalid_opcode;

				warning("STUB: script opcode 122");
			}
			break;

		case 123:{									/* oracle text down */
				if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2)
					goto invalid_opcode;

				warning("STUB: script opcode 123");
			}
			break;

		case 124:{									/* if time */
				if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2)
					goto invalid_opcode;

				uint time = getVarOrWord();
				condition = 1;
				warning("STUB: script opcode 124 (%d)", time);
			}
			break;

		case 125:{									/* item is sibling with item 1 */
				Item *item = getNextItemPtr();
				condition = (getItem1Ptr()->parent == item->parent);
			}
			break;

		case 126:{
				Item *item = getNextItemPtr();
				uint fcs_index = getVarOrByte();
				uint a = 1 << getVarOrByte();
				mouseOff();
				drawIconArray(fcs_index, item, 1, a);
				mouseOn();
			}
			break;

		case 127:{									/* deals with music */
				o_playMusic();
			}
			break;

		case 128:{									/* dummy instruction */
				getVarOrWord();
			}
			break;

		case 129:{									/* dummy instruction */
				getVarOrWord();
				condition = true;
			}
			break;

		case 130:{									/* set adj noun */
				uint var = getVarOrByte();
				if (var == 1) {
					_scriptAdj1 = getNextWord();
					_scriptNoun1 = getNextWord();
				} else {
					_scriptAdj2 = getNextWord();
					_scriptNoun2 = getNextWord();
				}
			}
			break;

		case 132:{									/* save game */
				_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
				o_saveGame();
				_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
			}
			break;

		case 133:{									/* load game */
				if (getGameType() == GType_FF) {
					loadGame(readVariable(55));
				} else {
					_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
					o_loadGame();
					_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
				}
			}
			break;

		case 134:{									/* dummy opcode? */
				if (getGameType() == GType_FF) {
					warning("STUB: script opcode 134");
				} else {
					midi.stop();
					_lastMusicPlayed = -1;
				}
			}
			break;

		case 135:{									/* quit if user presses y */
				if (getGameType() == GType_FF) {
					// Switch CD
					debug(1, "Switch to CD number %d", readVariable(97));
				} else {
					_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
					o_confirmQuit();
					_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
				}
			}
			break;

		case 136:{									/* set var to item unk3 */
				Item *item = getNextItemPtr();
				writeNextVarContents(item->state);
			}
			break;

		case 137:{
				o_restoreIconArray(getVarOrByte());
			}
			break;

		case 138:{									/* vga pointer op 4 */
				o_freezeBottom();
			}
			break;

		case 139:{									/* set parent special */
				Item *item = getNextItemPtr();
				_noParentNotify = true;
				setItemParent(item, getNextItemPtr());
				_noParentNotify = false;
			}
			break;

		case 140:{									/* del te and add one */
				killAllTimers();
				addTimeEvent(3, 0xA0);
			}
			break;

		case 141:{									/* set m1 or m3 */
				uint which = getVarOrByte();
				Item *item = getNextItemPtr();
				if (which == 1) {
					_subjectItem = item;
				} else {
					_objectItem = item;
				}
			}
			break;

		case 142:{									/* is hitarea 0x40 clear */
				condition = is_hitarea_0x40_clear(getVarOrWord());
			}
			break;

		case 143:{									/* start item sub */
				Child1 *child = (Child1 *)findChildOfType(getNextItemPtr(), 1);
				if (child != NULL) {
					Subroutine *sub = getSubroutineByID(child->subroutine_id);
					if (sub)
						startSubroutine(sub);
				}
			}
			break;

		case 151:{									/* set array6 to item */
				uint var = getVarOrByte();
				Item *item = getNextItemPtr();
				_itemArray6[var] = item;
			}
			break;

		case 152:{									/* set m1 or m3 to array6 */
				Item *item = _itemArray6[getVarOrByte()];
				uint var = getVarOrByte();
				if (var == 1) {
					_subjectItem = item;
				} else {
					_objectItem = item;
				}
			}
			break;

		case 153:{									/* set bit */
				uint bit = getVarOrByte();
				_bitArray[bit / 16] |= 1 << (bit & 15);
				break;
			}

		case 154:{									/* clear bit */
				uint bit = getVarOrByte();
				_bitArray[bit / 16] &= ~(1 << (bit & 15));
				break;
			}

		case 155:{									/* is bit clear */
				uint bit = getVarOrByte();
				condition = (_bitArray[bit / 16] & (1 << (bit & 15))) == 0;
			}
			break;

		case 156:{									/* is bit set */
				uint bit = getVarOrByte();
				if (getGameType() == GType_SIMON1 && _subroutine == 2962 && bit == 63) {
					bit = 50;
				}
				condition = (_bitArray[bit / 16] & (1 << (bit & 15))) != 0;
			}
			break;

		case 157:{									/* get item int prop */
				Item *item = getNextItemPtr();
				Child2 *child = (Child2 *)findChildOfType(item, 2);
				uint prop = getVarOrByte();

				if (child != NULL && child->avail_props & (1 << prop) && prop < 16) {
					uint offs = getOffsetOfChild2Param(child, 1 << prop);
					writeNextVarContents(child->array[offs]);
				} else {
					writeNextVarContents(0);
				}
			}
			break;

		case 158:{									/* set item prop */
				Item *item = getNextItemPtr();
				Child2 *child = (Child2 *)findChildOfType(item, 2);
				uint prop = getVarOrByte();
				int value = getVarOrWord();

				if (child != NULL && child->avail_props & (1 << prop) && prop < 16) {
					uint offs = getOffsetOfChild2Param(child, 1 << prop);
					child->array[offs] = value;
				}
			}
			break;

		case 160:{
				o_unk_160(getVarOrByte());
			}
			break;

		case 161:{									/* setup text */
				TextLocation *tl = getTextLocation(getVarOrByte());

				tl->x = getVarOrWord();
				tl->y = getVarOrByte();
				tl->width = getVarOrWord();
			}
			break;

		case 162:{									/* print string */
				o_printStr();
			}
			break;

		case 163:{									/* play sound */
				o_playSFX(getVarOrWord());
			}
			break;

		case 164:{
				_showPreposition = true;
				o_setup_cond_c();
				_showPreposition = false;
			}
			break;

		case 165:{									/* item unk1 unk2 is */
				Item *item = getNextItemPtr();
				int16 a = getNextWord(), b = getNextWord();
				condition = (item->adjective == a && item->noun == b);
			} break;

		case 166:{									/* set bit2 */
				uint bit = getVarOrByte();
				_bitArray[(bit / 16) + 16] |= 1 << (bit & 15);
			}
			break;

		case 167:{									/* clear bit2 */
				uint bit = getVarOrByte();
				_bitArray[(bit / 16) + 16] &= ~(1 << (bit & 15));
			}
			break;

		case 168:{									/* is bit2 clear */
				uint bit = getVarOrByte();
				condition = (_bitArray[(bit / 16) + 16] & (1 << (bit & 15))) == 0;
			}
			break;

		case 169:{									/* is bit2 set */
				uint bit = getVarOrByte();
				condition = (_bitArray[(bit / 16) + 16] & (1 << (bit & 15))) != 0;
			}
			break;

		case 171:{									/* oracle hyperlink on */
				if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2)
					goto invalid_opcode;

				hyperLinkOn(getVarOrWord());
			}
			break;

		case 172:{									/* oracle hyperlink off */
				if (getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2)
					goto invalid_opcode;

				hyperLinkOff();
			}
			break;

		case 175:{									/* vga pointer op 1 */
				o_lockZone();
			}
			break;

		case 176:{									/* vga pointer op 2 */
				o_unlockZone();
			}
			break;

		case 177:{									/* inventory descriptions */
				o_inventory_descriptions();
			}
			break;

		case 178:{									/* path find */
				uint a = getVarOrWord();
				uint b = getVarOrWord();
				uint c = getVarOrByte();
				uint d = getVarOrByte();
				o_pathfind(a, b, c, d);
			}
			break;

		case 179:{									/* conversation responses */
				uint vgaSpriteId = getVarOrByte();				/* and room descriptions */
				uint color = getVarOrByte();
				uint string_id = getVarOrByte();
				uint speechId = 0;

				const char *string_ptr = (const char *)getStringPtrByID(_stringIdArray3[string_id]);
				TextLocation *tl = getTextLocation(vgaSpriteId);
				if (getFeatures() & GF_TALKIE)
					speechId = _speechIdArray4[string_id];

				if (_speech && speechId != 0)
					playSpeech(speechId, vgaSpriteId);
				if (string_ptr != NULL && _subtitles)
					printText(vgaSpriteId, color, string_ptr, tl->x, tl->y, tl->width);
			}
			break;

		case 180:{									/* force mouseOn */
				o_mouseOn();
			}
			break;

		case 181:{									/* force mouseOff */
				o_mouseOff();
				if (getGameType() == GType_SIMON2) {
					changeWindow(1);
					showMessageFormat("\xC");
				}
			}
			break;

		case 182:{									/* load beard */
				if (getGameType() == GType_FF) {
					// Load video file
					debug(1,"Load video file: %s", getStringPtrByID(getNextStringID()));
				} else if (getGameType() == GType_SIMON2) {
					goto invalid_opcode;
				} else {
					o_loadBeard();
				}
			}
			break;

		case 183:{									/* unload beard */
				if (getGameType() == GType_FF) {
					// Play video
					debug(1, "Play video");
				} else if (getGameType() == GType_SIMON2) {
					goto invalid_opcode;
				} else {
					o_unloadBeard();
				}
			}
			break;

		case 184:{									/* clear vgapointer entry */
				o_unloadZone(getVarOrWord());
			}
			break;

		case 185:{									/* load sound files */
				if (getGameType() == GType_SIMON2)
					goto invalid_opcode;

				_soundFileId = getVarOrWord();
				if (getPlatform() == Common::kPlatformAmiga && getFeatures() & GF_TALKIE) {
					char buf[10];
					sprintf(buf, "%d%s", _soundFileId, "Effects");
					_sound->readSfxFile(buf);
					sprintf(buf, "%d%s", _soundFileId, "simon");
					_sound->readVoiceFile(buf);
				}

			}
			break;

		case 186:{									/* vga pointer op 3 */
				o_unfreezeBottom();
			}
			break;

		case 187:{									/* fade to black */
				if (getGameType() == GType_SIMON2)
					goto invalid_opcode;
				o_fadeToBlack();
			}
			break;

		case 188:									/* string2 is */
			if (getGameType() == GType_SIMON1)
				goto invalid_opcode;
			{
				uint i = getVarOrByte();
				uint str = getNextStringID();
				condition = (str < 20 && _stringIdArray2[i] == str);
			}
			break;

		case 189:{									/* clear_op189_flag */
				if (getGameType() == GType_SIMON1)
					goto invalid_opcode;
				_marks = 0;
			}
			break;

		case 190:{
				uint i;
				if (getGameType() == GType_SIMON1)
					goto invalid_opcode;
				i = getVarOrByte();
				if (!(_marks & (1 << i)))
					o_waitForMark(i);
			}
			break;

		// Feeble opcodes
		case 191:
			if (_bitArray[5] & 0x0008) {
				_PVCount1 = 0;
				_GPVCount1 = 0;
			} else {
				_PVCount = 0;
				_GPVCount = 0;
			}
			break;

		case 192:{
				uint8 a = getVarOrByte();
				uint8 b = getVarOrByte();
				uint8 c = getVarOrByte();
				uint8 d = getVarOrByte();
				if (_bitArray[5] & 0x0008) {
					_pathValues1[_PVCount1++] = a;
					_pathValues1[_PVCount1++] = b;
					_pathValues1[_PVCount1++] = c;
					_pathValues1[_PVCount1++] = d;
				} else {
					_pathValues[_PVCount++] = a;
					_pathValues[_PVCount++] = b;
					_pathValues[_PVCount++] = c;
					_pathValues[_PVCount++] = d;
				}
			}
			break;

		case 193:
			// pause clock
			warning("STUB: script opcode 193");
			break;

		case 194:
			// resume clock
			warning("STUB: script opcode 194");
			break;

		case 195:{
				// Set palette colour?
				uint blue = getVarOrByte();
				uint green = getVarOrByte();
				uint red = getVarOrByte();
				uint color = getVarOrByte();
				warning("STUB: script opcode 195 (%d, %d, %d, %d)", blue, green, red, color);
			}
			break;

		case 196:{									/* set bit3 */
				uint bit = getVarOrByte();
				_bitArray[(bit / 16) + 32] |= 1 << (bit & 15);
			}
			break;

		case 197:{									/* clear bit3 */
				uint bit = getVarOrByte();
				_bitArray[(bit / 16) + 32] &= ~(1 << (bit & 15));
			}
			break;

		case 198:{									/* is bit3 clear */
				uint bit = getVarOrByte();
				condition = (_bitArray[(bit / 16) + 32] & (1 << (bit & 15))) == 0;
			}
			break;

		case 199:{									/* is bit3 set */
				uint bit = getVarOrByte();
				condition = (_bitArray[(bit / 16) + 32] & (1 << (bit & 15))) != 0;
			}
			break;

		default:
		invalid_opcode:;
			error("Invalid opcode '%d'", opcode);
		}

	} while (condition != flag);

	return 0;
}

int SimonEngine::startSubroutine(Subroutine *sub) {
	int result = -1;
	SubroutineLine *sl;
	const byte *old_code_ptr;

	if (_startMainScript)
		dumpSubroutine(sub);

	old_code_ptr = _codePtr;

	if (++_recursionDepth > 40)
		error("Recursion error");

	sl = (SubroutineLine *)((byte *)sub + sub->first);

	while ((byte *)sl != (byte *)sub) {
		if (checkIfToRunSubroutineLine(sl, sub)) {
			result = 0;
			_codePtr = (byte *)sl;
			if (sub->id)
				_codePtr += 2;
			else
				_codePtr += 8;

			if (_continousMainScript)
				fprintf(_dumpFile, "; %d\n", sub->id);
			result = runScript();
			if (result != 0) {
				/* result -10 means restart subroutine */
				if (result == -10) {
					delay(0);							/* maybe leave control to the VGA */
					sl = (SubroutineLine *)((byte *)sub + sub->first);
					continue;
				}
				break;
			}
		}
		sl = (SubroutineLine *)((byte *)sub + sl->next);
	}

	_codePtr = old_code_ptr;

	_recursionDepth--;
	return result;
}

int SimonEngine::startSubroutineEx(Subroutine *sub) {
	return startSubroutine(sub);
}

bool SimonEngine::checkIfToRunSubroutineLine(SubroutineLine *sl, Subroutine *sub) {
	if (sub->id)
		return true;

	if (sl->verb != -1 && sl->verb != _scriptVerb &&
			(sl->verb != -2 || _scriptVerb != -1))
		return false;

	if (sl->noun1 != -1 && sl->noun1 != _scriptNoun1 &&
			(sl->noun1 != -2 || _scriptNoun1 != -1))
		return false;

	if (sl->noun2 != -1 && sl->noun2 != _scriptNoun2 &&
			(sl->noun2 != -2 || _scriptNoun2 != -1))
		return false;

	return true;
}

void SimonEngine::o_83_helper() {
		if (_exitCutscene) {
			if (vcGetBit(9)) {
				endCutscene();
			}
		} else {
			processSpecialKeys();
		}
}

void SimonEngine::o_waitForMark(uint i) {
	_exitCutscene = false;
	while (!(_marks & (1 << i))) {
		if (_exitCutscene) {
			if (vcGetBit(9)) {
				endCutscene();
				break;
			}
		} else {
			processSpecialKeys();
		}

		delay(10);
	}
}


bool SimonEngine::o_chance(uint a) {
	if (a == 0)
		return 0;

	if (a == 100)
		return 1;

	a += _scriptUnk1;
	if (a <= 0) {
		_scriptUnk1 = 0;
		return 0;
	}

	if ((uint)_rnd.getRandomNumber(99) < a) {
		if (_scriptUnk1 <= 0)
			_scriptUnk1 -= 5;
		else
			_scriptUnk1 = 0;
		return 1;
	}

	if (_scriptUnk1 >= 0)
		_scriptUnk1 += 5;
	else
		_scriptUnk1 = 0;

	return 0;
}

void SimonEngine::o_inventory_descriptions() {
	uint vgaSpriteId = getVarOrByte();
	uint color = getVarOrByte();
	const char *string_ptr = NULL;
	TextLocation *tl = NULL;
	char buf[256];

	Child2 *child = (Child2 *)findChildOfType(getNextItemPtr(), 2);
	if (child != NULL && child->avail_props & 1) {
		string_ptr = (const char *)getStringPtrByID(child->array[0]);
		tl = getTextLocation(vgaSpriteId);
	}

	if ((getGameType() == GType_SIMON2) && (getFeatures() & GF_TALKIE)) {
		if (child != NULL && child->avail_props & 0x200) {
			uint speechId = child->array[getOffsetOfChild2Param(child, 0x200)];

			if (child->avail_props & 0x100) {
				uint speechIdOffs = child->array[getOffsetOfChild2Param(child, 0x100)];

				if (speechId == 116)
					speechId = speechIdOffs + 115;
				if (speechId == 92)
					speechId = speechIdOffs + 98;
				if (speechId == 99)
					speechId = 9;
				if (speechId == 97) {
					switch (speechIdOffs) {
					case 12:
						speechId = 109;
						break;
					case 14:
						speechId = 108;
						break;
					case 18:
						speechId = 107;
						break;
					case 20:
						speechId = 106;
						break;
					case 22:
						speechId = 105;
						break;
					case 28:
						speechId = 104;
						break;
					case 90:
						speechId = 103;
						break;
					case 92:
						speechId = 102;
						break;
					case 100:
						speechId = 51;
						break;
					default:
						error("o_177: invalid case %d", speechIdOffs);
					}
				}
			}

			if (_speech)
				playSpeech(speechId, vgaSpriteId);
		}

	} else if (getFeatures() & GF_TALKIE) {
		if (child != NULL && child->avail_props & 0x200) {
			uint offs = getOffsetOfChild2Param(child, 0x200);
			playSpeech(child->array[offs], vgaSpriteId);
		} else if (child != NULL && child->avail_props & 0x100) {
			uint offs = getOffsetOfChild2Param(child, 0x100);
			playSpeech(child->array[offs] + 3550, vgaSpriteId);
		}
	}

	if (child != NULL && (child->avail_props & 1) && _subtitles) {
		if (child->avail_props & 0x100) {
			sprintf(buf, "%d%s", child->array[getOffsetOfChild2Param(child, 0x100)], string_ptr);
			string_ptr = buf;
		}
		if (string_ptr != NULL)
			printText(vgaSpriteId, color, string_ptr, tl->x, tl->y, tl->width);
	}
}

void SimonEngine::o_confirmQuit() {
	// If all else fails, use English as fallback.
	byte keyYes = 'y';
	byte keyNo = 'n';

	switch (_language) {
	case Common::RU_RUS:
		break;
	case Common::PL_POL:
		keyYes = 't';
		break;
	case Common::HB_ISR:
		keyYes = 'f';
		break;
	case Common::ES_ESP:
		keyYes = 's';
		break;
	case Common::IT_ITA:
		keyYes = 's';
		break;
	case Common::FR_FRA:
		keyYes = 'o';
		break;
	case Common::DE_DEU:
		keyYes = 'j';
		break;
	default:
		break;
	}

	for (;;) {
		delay(1);
#ifdef _WIN32_WCE
		if (isSmartphone()) {
			if (_keyPressed) {
				if (_keyPressed == 13)
					shutdown();
				else
					break;
			}
		}
#endif
		if (_keyPressed == keyYes)
			shutdown();
		else if (_keyPressed == keyNo)
			break;
	}
}

void SimonEngine::o_restoreIconArray(uint fcs_index) {
	FillOrCopyStruct *fcs;

	fcs = _windowArray[fcs_index & 7];
	if (fcs->fcs_data == NULL)
		return;
	drawIconArray(fcs_index, fcs->fcs_data->item_ptr, fcs->fcs_data->unk1, fcs->fcs_data->unk2);
}

void SimonEngine::o_freezeBottom() {
	_vgaBufStart = _vgaBufFreeStart;
	_vgaFileBufOrg = _vgaBufFreeStart;
}

void SimonEngine::o_unfreezeBottom() {
	_vgaBufFreeStart = _vgaFileBufOrg2;
	_vgaBufStart = _vgaFileBufOrg2;
	_vgaFileBufOrg = _vgaFileBufOrg2;
}

void SimonEngine::o_lockZone() {
	_vgaBufStart = _vgaBufFreeStart;
}

void SimonEngine::o_unlockZone() {
	_vgaBufFreeStart = _vgaFileBufOrg;
	_vgaBufStart = _vgaFileBufOrg;
}

int SimonEngine::o_unk_132_helper(bool *b, char *buf) {
	HitArea *ha;
	*b = true;

	if (!_saveLoadFlag) {
	strange_jump:;
		_saveLoadFlag = false;
		saveGameDialog(buf);
	}

start_over:;
	_keyPressed = 0;

start_over_2:;
	_lastHitArea = _lastHitArea3 = 0;

	do {
		if (_keyPressed != 0) {
			if (_saveLoadFlag) {
				*b = false;
				return _keyPressed;
			}
			goto start_over;
		}
		delay(100);
	} while (_lastHitArea3 == 0);

	ha = _lastHitArea;

	if (ha == NULL || ha->id < 205)
		goto start_over_2;

	if (ha->id == 205)
		return ha->id;

	if (ha->id == 206) {
		if (_saveLoadRowCurPos == 1)
			goto start_over_2;
		if (_saveLoadRowCurPos < 7)
			_saveLoadRowCurPos = 1;
		else
			_saveLoadRowCurPos -= 6;

		goto strange_jump;
	}

	if (ha->id == 207) {
		if (!_saveDialogFlag)
			goto start_over_2;
		_saveLoadRowCurPos += 6;
		if (_saveLoadRowCurPos >= _numSaveGameRows)
			_saveLoadRowCurPos = _numSaveGameRows;
		goto strange_jump;
	}

	if (ha->id >= 214)
		goto start_over_2;
	return ha->id - 208;
}

void SimonEngine::o_unk_132_helper_3() {
	for (int i = 208; i != 208 + 6; i++)
		set_hitarea_bit_0x40(i);
}

void SimonEngine::o_clearCharacter(FillOrCopyStruct *fcs, int x, byte b) {
	byte old_text;

	video_putchar(fcs, x, b);
	old_text = fcs->text_color;
	fcs->text_color = fcs->fill_color;

	if (_language == Common::HB_ISR) { //Hebrew
		x = 128;
	} else {
		x += 120;
		if (x != 128)
			x = 129;

	}

	video_putchar(fcs, x);

	fcs->text_color = old_text;
	video_putchar(fcs, 8);
}

void SimonEngine::o_playMusic() {
	int music = getVarOrWord();
	int track = getVarOrWord();

	// Jamieson630:
	// This appears to be a "load or play music" command.
	// The music resource is specified, and optionally
	// a track as well. Normally we see two calls being
	// made, one to load the resource and another to
	// actually start a track (so the resource is
	// effectively preloaded so there's no latency when
	// starting playback).
	if (getGameType() == GType_SIMON2) {
		int loop = getVarOrByte();

		midi.setLoop (loop != 0);
		if (_lastMusicPlayed != music)
			_nextMusicToPlay = music;
		else
			midi.startTrack (track);
	} else {
		if (music != _lastMusicPlayed) {
			_lastMusicPlayed = music;
			loadMusic (music);
			midi.startTrack (track);
		}
	}
}

void SimonEngine::o_sync(uint a) {
	uint16 id = to16Wrapper(a);
	_lockWord |= 0x8000;
	_vcPtr = (byte *)&id;
	vc15_wakeup_id();
	_lockWord &= ~0x8000;
}

void SimonEngine::o_playSFX(uint sound_id) {
	if (getGameId() == GID_SIMON1DOS)
		playSting(sound_id);
	else
		_sound->playEffects(sound_id);
}

void SimonEngine::o_unk_160(uint a) {
	fcs_setTextColor(_windowArray[_curWindow], a);
}

void SimonEngine::o_unk_103() {
	mouseOff();
	removeIconArray(_curWindow);
	if (getGameType() == GType_FF)
		showMessageFormat("\x0E");
	else
		showMessageFormat("\x0C");
	mouseOn();
}

void SimonEngine::o_kill_sprite_simon1(uint a) {
	uint16 b = to16Wrapper(a);
	_lockWord |= 0x8000;
	_vcPtr = (byte *)&b;
	vc60_killSprite();
	_lockWord &= ~0x8000;
}

void SimonEngine::o_kill_sprite_simon2(uint a, uint b) {
	uint16 items[2];

	items[0] = to16Wrapper(a);
	items[1] = to16Wrapper(b);

	_lockWord |= 0x8000;
	_vcPtr = (byte *)&items;
	vc60_killSprite();
	_lockWord &= ~0x8000;
}

/* OK */
void SimonEngine::o_defineWindow(uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h) {
	a &= 7;

	if (_windowArray[a])
		closeWindow(a);

	_windowArray[a] = openWindow(b, c, d, e, f, g, h);

	if (a == _curWindow) {
		_textWindow = _windowArray[a];
		showmessage_helper_3(_textWindow->textLength, _textWindow->textMaxLength);
	}
}

} // End of namespace Simon