/* 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
 * (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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "dragons/actorresource.h"
#include "dragons/cursor.h"
#include "dragons/dragons.h"
#include "dragons/dragonflg.h"
#include "dragons/dragonini.h"
#include "dragons/dragonimg.h"
#include "dragons/dragonobd.h"
#include "dragons/inventory.h"
#include "dragons/scene.h"
#include "dragons/scriptopcodes.h"
#include "dragons/specialopcodes.h"
#include "dragons/actor.h"
#include "dragons/sound.h"
#include "dragons/talk.h"
#include "dragons/screen.h"


namespace Dragons {

// Convenience macros
#define ARG_SKIP(x) scriptOpCall.skip(x);
#define ARG_BYTE(name) byte name = scriptOpCall.readByte(); debug(5, "ARG_BYTE(" #name " = %d)", name);
#define ARG_INT8(name) int8 name = scriptOpCall.readByte(); debug(5, "ARG_INT8(" #name " = %d)", name);
#define ARG_INT16(name) int16 name = scriptOpCall.readSint16(); debug(5, "ARG_INT16(" #name " = %d)", name);
#define ARG_UINT32(name) uint32 name = scriptOpCall.readUint32(); debug(5, "ARG_UINT32(" #name " = %08X)", name);

// ScriptOpCall

void ScriptOpCall::skip(uint size) {
	_code += size;
}

byte ScriptOpCall::readByte() {
	return *_code++;
}

int16 ScriptOpCall::readSint16() {
	int16 value = READ_LE_UINT16(_code);
	_code += 2;
	return value;
}

uint32 ScriptOpCall::readUint32() {
	uint32 value = READ_LE_UINT32(_code);
	_code += 4;
	return value;
}

ScriptOpCall::ScriptOpCall(byte *start, uint32 length): _op(0), _result(0), _field8(0) {
	_code = _base = start;
	_codeEnd = _code + length;
}

// ScriptOpcodes

ScriptOpcodes::ScriptOpcodes(DragonsEngine *vm, DragonFLG *dragonFLG)
	: _vm(vm), _dragonFLG(dragonFLG), _numDialogStackFramesToPop(0) {
	_specialOpCodes = new SpecialOpcodes(_vm);
	initOpcodes();
	_scriptTargetINI = 0;
}

ScriptOpcodes::~ScriptOpcodes() {
	freeOpcodes();
	delete _specialOpCodes;
}

void ScriptOpcodes::execOpcode(ScriptOpCall &scriptOpCall) {
	if (!_opcodes[scriptOpCall._op])
		error("ScriptOpcodes::execOpcode() Unimplemented opcode %d (0x%X)", scriptOpCall._op, scriptOpCall._op);
	debug(1, "execScriptOpcode(0x%X) @%X  %s", scriptOpCall._op, (int)(scriptOpCall._code - scriptOpCall._base), _opcodeNames[scriptOpCall._op].c_str());
	(*_opcodes[scriptOpCall._op])(scriptOpCall);
}

typedef Common::Functor1Mem<ScriptOpCall&, void, ScriptOpcodes> ScriptOpcodeI;
#define OPCODE(op, func) \
	_opcodes[op] = new ScriptOpcodeI(this, &ScriptOpcodes::func); \
	_opcodeNames[op] = #func;

void ScriptOpcodes::initOpcodes() {
	// First clear everything
	for (uint i = 0; i < DRAGONS_NUM_SCRIPT_OPCODES; ++i) {
		_opcodes[i] = nullptr;
	}
	// Register opcodes
	OPCODE(1, opUnk1);
	OPCODE(2, opAddDialogChoice);
	OPCODE(3, opPopDialogStack);
	OPCODE(4, opExecuteScript);
	OPCODE(5, opSetActorDirection);
	OPCODE(6, opPerformActionOnObject);
	OPCODE(7, opMoveObjectToScene);
	OPCODE(8, opActorLoadSequence);

	OPCODE(0xA, opSetVariable);
	OPCODE(0xB, opRunSpecialOpCode);
	OPCODE(0xC, opPlayOrStopSound);
	OPCODE(0xD, opDelay);
	OPCODE(0xE, opMoveActorToPoint);
	OPCODE(0xF, opMoveActorToXY);
	OPCODE(0x10, opMoveActorToObject);
	OPCODE(0x11, opUnk11FlickerTalk);
	OPCODE(0x12, opLoadScene);
	OPCODE(0x13, opIfStatement);
	OPCODE(0x14, opIfElseStatement);
	OPCODE(0x15, opUnk15PropertiesRelated); //Is this used?
	OPCODE(0x16, opUnk16);
	OPCODE(0x17, opWaitForActorSequenceToFinish);
	OPCODE(0x18, opDialogAtPoint);
	OPCODE(0x19, opExecuteObjectSceneScript);
	OPCODE(0x1A, opUpdatePaletteCycling);
	OPCODE(0x1B, opWaitForActorToFinishWalking);
	OPCODE(0x1C, opSetActorFlag0x1000);
	OPCODE(0x1D, opShowActor);
	OPCODE(0x1E, opHideActor);
	OPCODE(0x1F, opPlayMusic);
	OPCODE(0x20, opPreLoadSceneData);
	OPCODE(0x21, opPauseCurrentSpeechAndFetchNextDialog);
	OPCODE(0x22, opCodeActorTalk);
}

#undef OPCODE

void ScriptOpcodes::freeOpcodes() {
	for (uint i = 0; i < DRAGONS_NUM_SCRIPT_OPCODES; ++i) {
		delete _opcodes[i];
	}
}

void ScriptOpcodes::updateReturn(ScriptOpCall &scriptOpCall, uint16 size) {
	// scriptOpCall._deltaOfs = size * 2 + 2;
}


void ScriptOpcodes::runScript(ScriptOpCall &scriptOpCall) {
	scriptOpCall._field8 = 0;
	scriptOpCall._result = 0;
	_numDialogStackFramesToPop = 0;
	executeScriptLoop(scriptOpCall);
}


void ScriptOpcodes::runScript3(ScriptOpCall &scriptOpCall) {
	scriptOpCall._field8 = 3;
	scriptOpCall._result = 0;
	_numDialogStackFramesToPop = 0;
	executeScriptLoop(scriptOpCall);
}

bool ScriptOpcodes::runScript4(ScriptOpCall &scriptOpCall) {
	scriptOpCall._field8 = 4;
	scriptOpCall._result = 0;
	_numDialogStackFramesToPop = 0;
	executeScriptLoop(scriptOpCall);
	return scriptOpCall._result;
}

void ScriptOpcodes::executeScriptLoop(ScriptOpCall &scriptOpCall) {
//
//	if (scriptOpCall._code >= scriptOpCall._codeEnd || scriptOpCall._result & 1) {
//		return;
//	}
//
//	if (_vm->isFlagSet(ENGINE_FLAG_100000)) {
//		return;
//	}
//
//	if (_vm->isFlagSet(ENGINE_FLAG_80000)) {
//		//TODO
////		if (IsPressedStart(0)) {
////			getEngine()->setFlags(Dragons::ENGINE_FLAG_100000);
////		}
//	}
//
//	uint16 opcode = READ_LE_UINT16(scriptOpCall._code) & 0x7fff;
//
//	scriptOpCall._op = (byte) opcode;
//	if (opcode < DRAGONS_NUM_SCRIPT_OPCODES) {
//		execOpcode(scriptOpCall);
//	}
//
	while (scriptOpCall._code < scriptOpCall._codeEnd && !(scriptOpCall._result & 1) && !_vm->shouldQuit()) {

		if (_vm->isFlagSet(ENGINE_FLAG_100000)) {
			return;
		}

		if (_vm->isFlagSet(ENGINE_FLAG_80000)) {
			//TODO
//		if (IsPressedStart(0)) {
//			getEngine()->setFlags(Dragons::ENGINE_FLAG_100000);
//		}
		}

		uint16 opcode = READ_LE_UINT16(scriptOpCall._code) & 0x7fff;

		if (opcode >= DRAGONS_NUM_SCRIPT_OPCODES) {
			return; //TODO should continue here.
		}
		scriptOpCall._op = (byte) opcode;
		execOpcode(scriptOpCall);

		if (_numDialogStackFramesToPop != 0) {
			scriptOpCall._result |= 1;
			break;
		}
	}
}

// Opcodes

void ScriptOpcodes::opUnk1(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(field2);
	ARG_INT16(field4);
	ARG_INT16(field6);

	if ((field2 >> _vm->_cursor->_data_800728b0_cursor_seqID) & 1
			&& (_vm->_cursor->_data_800728b0_cursor_seqID < 5 || field4 == _scriptTargetINI)
			&& scriptOpCall._field8 == 1) {
		scriptOpCall._code -= 8;
		scriptOpCall._result |= 1;
	} else {
		scriptOpCall._code += field6;
	}
}

void ScriptOpcodes::opAddDialogChoice(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_UINT32(field2);
	ARG_UINT32(field6);
	ARG_INT16(fieldA);
	ARG_INT16(fieldC);
	ARG_INT16(fieldE);

	if (scriptOpCall._field8 == 2) {
		TalkDialogEntry *talkDialogEntry = new TalkDialogEntry();

		talkDialogEntry->hasText = _vm->_talk->loadText(field2, (uint16 *)(&talkDialogEntry->dialogText[10]), 295);

		talkDialogEntry->textIndex = field2;
		talkDialogEntry->textIndex1 = field6;
		talkDialogEntry->scriptCodeStartPtr = scriptOpCall._code;
		talkDialogEntry->flags = 0;
		talkDialogEntry->scriptCodeEndPtr = scriptOpCall._code + fieldA;
		if ((field0 & 0x8000U) != 0) {
			talkDialogEntry->flags = 2;
		}
		talkDialogEntry->field_26c = fieldC;
		talkDialogEntry->iniId = fieldE;
		_vm->_talk->addTalkDialogEntry(talkDialogEntry);
	}
	scriptOpCall._code += fieldA;
}

// The number of dialog frames to pop off the stack. this returns up from nested conversation trees.
void ScriptOpcodes::opPopDialogStack(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(field2);
	if (scriptOpCall._field8 == 0) {
		_numDialogStackFramesToPop = field2;
	}
}

void ScriptOpcodes::opExecuteScript(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_UINT32(obdOffset);
	byte *data =_vm->_dragonOBD->getObdAtOffset(obdOffset);

	ScriptOpCall newScriptOpCall(data + 4, READ_LE_UINT32(data));
	newScriptOpCall._field8 = scriptOpCall._field8;
	newScriptOpCall._result = 0;

	executeScriptLoop(newScriptOpCall);
}

void ScriptOpcodes::opSetActorDirection(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniIndex);
	ARG_SKIP(2);
	ARG_INT16(direction);

	if (scriptOpCall._field8 == 0) {
		_vm->getINI(iniIndex - 1)->actor->_direction = direction;
	}
}

void ScriptOpcodes::opPerformActionOnObject(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(verb);
	ARG_INT16(srcINI);
	ARG_INT16(targetINI);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	int16 uVar6 = _scriptTargetINI;
	int16 uVar5 = _vm->_cursor->_data_800728b0_cursor_seqID;
	int16 uVar4 = _vm->_cursor->_performActionTargetINI;
	int16 uVar3 = _vm->_cursor->_iniUnderCursor;
	int32 uVar2 = _vm->_cursor->_sequenceID;
	bool isEngineFlag8Set = _vm->isFlagSet(ENGINE_FLAG_8);
	_vm->clearFlags(ENGINE_FLAG_8);
//	DisableVSyncEvent();
	_vm->_cursor->_iniUnderCursor = srcINI;
	_vm->_cursor->_sequenceID = 0;

	for (int16 i = verb >> 1; i != 0; i = i >> 1) {
		_vm->_cursor->_sequenceID++;
	}

	_scriptTargetINI = targetINI;
	_vm->_cursor->_data_800728b0_cursor_seqID = _vm->_cursor->_sequenceID;
	_vm->_cursor->_performActionTargetINI = _vm->_cursor->_iniUnderCursor;
//	EnableVSyncEvent();
	_vm->performAction();
	if (isEngineFlag8Set) {
		_vm->setFlags(ENGINE_FLAG_8);
	}
	_vm->_cursor->_sequenceID = uVar2;
	_vm->_cursor->_iniUnderCursor = uVar3;
	_vm->_cursor->_performActionTargetINI = uVar4;
	_vm->_cursor->_data_800728b0_cursor_seqID = uVar5;
	_scriptTargetINI = uVar6;
}

void ScriptOpcodes::opMoveObjectToScene(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(field2);
	ARG_INT16(sceneId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	uint16 currentScene = _vm->getCurrentSceneId();
	DragonINI *ini = _vm->getINI(field2 - 1);
	if (!(field0 & 0x8000)) {

		if (ini->flags & 1) {
			if (ini->sceneId == currentScene) {
				assert(ini->actor);
				ini->actor->reset_maybe();
			}
			if (sceneId == currentScene) {
				ini->actor = _vm->_actorManager->loadActor(ini->actorResourceId, ini->sequenceId, ini->x, ini->y, 0);
				ini->actor->_direction = ini->direction2;
				if (ini->flags & 2) {
					ini->actor->_flags |= ACTOR_FLAG_80;
				} else {
					ini->actor->_flags &= ~ACTOR_FLAG_80;
				}

				if (ini->flags & 0x20) {
					ini->actor->_flags |= ACTOR_FLAG_100;
				} else {
					ini->actor->_flags &= ~ACTOR_FLAG_100;
				}

				if (ini->flags & 4) {
					ini->actor->_flags |= ACTOR_FLAG_8000;
				} else {
					ini->actor->_flags &= ~ACTOR_FLAG_8000;
				}

				if (ini->flags & 0x100) {
					ini->actor->_flags |= ACTOR_FLAG_4000;
				} else {
					ini->actor->_flags &= ~ACTOR_FLAG_4000;
				}
			}
		} else {
			if (ini->sceneId == currentScene && ini->iptIndex_maybe != -1) {
				_vm->_scene->removeImageOverlay(ini->iptIndex_maybe);
			}
			if (sceneId == currentScene && ini->iptIndex_maybe != -1) {
				_vm->_scene->loadImageOverlay(ini->iptIndex_maybe);
			}
		}

		if (ini->sceneId == 1) {
			if ((uint)_vm->_cursor->_iniItemInHand - 1 == ini->id) {
				_vm->_cursor->_data_800728b0_cursor_seqID = 0;
				_vm->_cursor->_sequenceID = 0;
				_vm->_cursor->_iniItemInHand = 0;
			} else {
				if (_vm->_inventory->hasItem(ini->id + 1)) {
					Actor *actor = _vm->_inventory->getInventoryItemActor(ini->id + 1);
					_vm->_inventory->clearItem(ini->id + 1);
					if (_vm->_inventory->getState() == InventoryOpen) {
						actor->clearFlag(ACTOR_FLAG_40);
					}
				}
			}
		}

		if (sceneId == 1) {
			if (_vm->_cursor->_iniItemInHand != 0) {
				_vm->_inventory->addItem(_vm->_cursor->_iniItemInHand);
				if (_vm->_inventory->getState() == InventoryOpen) {
					Actor *actor = _vm->_inventory->getInventoryItemActor(_vm->_cursor->_iniItemInHand);
					actor->_flags = 0;
					actor->_priorityLayer = 0;
					actor->_scale = DRAGONS_ENGINE_SPRITE_100_PERCENT_SCALE;
					actor->updateSequence((_vm->getINI(_vm->_cursor->_iniItemInHand - 1)->inventorySequenceId * 2 + 10) & 0xfffe);
					actor->setFlag(ACTOR_FLAG_40);
					actor->setFlag(ACTOR_FLAG_80);
					actor->setFlag(ACTOR_FLAG_100);
					actor->setFlag(ACTOR_FLAG_200);
					actor->_priorityLayer = 6;
				}
			}
			DragonINI *flicker = _vm->_dragonINIResource->getFlickerRecord();
			_vm->_cursor->updatePosition(flicker->actor->_x_pos - _vm->_scene->_camera.x,
										 flicker->actor->_y_pos - (_vm->_scene->_camera.y + 0x1e));
			_vm->_cursor->_data_800728b0_cursor_seqID = 5;
			_vm->_cursor->_sequenceID = 5;
			_vm->_cursor->_objectInHandSequenceID = _vm->getINI(field2 - 1)->inventorySequenceId * 2 + 10;
			_vm->_cursor->_iniItemInHand = field2;
		}
	}
	ini->sceneId = sceneId;
}

void ScriptOpcodes::opActorLoadSequence(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(field2);
	ARG_INT16(sequenceId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	DragonINI *ini = _vm->getINI(field2 - 1);

	bool isFlicker = _vm->_dragonINIResource->isFlicker(field2 - 1);
	if (isFlicker) {
		ini->actor->_flags |= ACTOR_FLAG_2000;
	}

	if (!ini->actor->_actorResource || ini->actor->_actorResource->_id != (uint32)ini->actorResourceId) {
		ini->actor->_actorResource = _vm->_actorManager->getActorResource(ini->actorResourceId);
	}

	ini->actor->updateSequence(sequenceId);

	if (field0 & 0x8000) {
		ini->actor->waitUntilFlag8And4AreSet();
	}

	if (isFlicker) {
		ini->actor->_flags &= ~ACTOR_FLAG_2000;
	}
}

void ScriptOpcodes::opPlayMusic(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(songNumber);
	if (scriptOpCall._field8 == 0) {
		_vm->_sound->playMusic(songNumber);
	}
}

void ScriptOpcodes::opIfStatement(ScriptOpCall &scriptOpCall) {
	if (evaluateExpression(scriptOpCall)) {
		scriptOpCall._code += 4;
	} else {
		scriptOpCall._code += 4 + READ_LE_UINT16(scriptOpCall._code);
	}
}

void ScriptOpcodes::opIfElseStatement(ScriptOpCall &scriptOpCall) {
	if (evaluateExpression(scriptOpCall)) {
		ScriptOpCall localScriptOpCall(scriptOpCall._code + 4, READ_LE_UINT16(scriptOpCall._code));
		localScriptOpCall._field8 = scriptOpCall._field8;
		localScriptOpCall._result = 0;

		executeScriptLoop(localScriptOpCall);

		if (scriptOpCall._field8 == 1) {
			scriptOpCall._result = localScriptOpCall._result;
			if ((localScriptOpCall._result & 1) != 0) {
				scriptOpCall._code = localScriptOpCall._code;
				return;
			}
		}

		scriptOpCall._code = localScriptOpCall._code + READ_LE_UINT16(scriptOpCall._code + 2);
	} else {
		scriptOpCall._code += 4 + READ_LE_UINT16(scriptOpCall._code);
	}
}

void ScriptOpcodes::opUnk15PropertiesRelated(ScriptOpCall &scriptOpCall) {
	while (true) {
		if (evaluateExpression(scriptOpCall)) {
			ScriptOpCall localScriptOpCall(scriptOpCall._code + 4, READ_LE_UINT32(scriptOpCall._code));

			runScript(localScriptOpCall);

			scriptOpCall._code = (scriptOpCall._code - ((uint)*(scriptOpCall._code + 2) + 2));
		} else {
			break;
		}
	}

	scriptOpCall._code += 4 + READ_LE_UINT16(scriptOpCall._code);
}

void ScriptOpcodes::opPreLoadSceneData(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(sceneId);

	_vm->_sound->resumeMusic();
	_vm->_isLoadingDialogAudio = true;

	if (sceneId >= 2) {
		//TODO do we need this? It looks like it is pre-loading the next scene's data.
	}
}

void ScriptOpcodes::opPauseCurrentSpeechAndFetchNextDialog(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_UINT32(textIndex);

	if (scriptOpCall._field8 == 0) {
		_vm->_sound->resumeMusic();
		//The original starts seeking the CD-ROM here for the `textIndex` dialog but we don't need to do that.
	}
}

bool ScriptOpcodes::evaluateExpression(ScriptOpCall &scriptOpCall) {
	byte *codePtrOffsetA = scriptOpCall._code + 0xA;
	byte *codePtrOffset2 = scriptOpCall._code + 2;

	uint16 status = 0;
	uint16 result = 0;

	int16 t2 = 0;
	int16 t0 = 0;

	for (;;) {
		byte value = 0;
		if (*codePtrOffsetA & 1) {
			uint32 propId = READ_LE_UINT16(codePtrOffset2) * 8 + READ_LE_UINT16(codePtrOffsetA - 6);
			value = _dragonFLG->get(propId) ? 1 : 0;
		} else {
			debug(3, "Op13 get here!!");
			if (*codePtrOffsetA & 2) {
				debug(3, "Op13 get here!! & 2");
				t2 = _vm->getVar(READ_LE_UINT16(codePtrOffset2));
			}

			if (*codePtrOffsetA & 4) {
				t2 = getINIField(READ_LE_INT16(codePtrOffsetA - 6) - 1, READ_LE_INT16(codePtrOffset2));
				debug(3, "Op13 get here!! & 4 read ini field ini: %X fieldOffset: %X value: %d", READ_LE_INT16(codePtrOffsetA - 6) - 1, READ_LE_INT16(codePtrOffset2), t2);

			}

			if (!(*codePtrOffsetA & 7)) {
				debug(3, "Op13 get here!! & 7");
				t2 = READ_LE_UINT16(codePtrOffsetA - 6);
			}

			if (*codePtrOffsetA & 8) {
				debug(3, "Op13 get here!! & 8");
				t0 = _vm->getVar(READ_LE_UINT16(codePtrOffsetA - 4));
			}

			if (*codePtrOffsetA & 0x10) {
				debug(3, "Op13 get here!! & 0x10");
				t0 = getINIField(READ_LE_INT16(codePtrOffsetA - 2) - 1, READ_LE_INT16(codePtrOffsetA - 4));
			}

			if (!(*codePtrOffsetA & 0x18)) {
				t0 = READ_LE_UINT16(codePtrOffsetA - 2);
				debug(3, "Op13 get here!! & 0x18 t0 == %d", t0);
			}

			if (*(codePtrOffsetA + 1) == 0 && t0 == t2) {
				value = 1;
			}

			if (*(codePtrOffsetA + 1) == 1 && t2 < t0) {
				value = 1;
			}

			if (*(codePtrOffsetA + 1) == 2 && t0 < t2) {
				value = 1;
			}

		}

		if (*codePtrOffsetA & 0x20) {
			value ^= 1;
		}

		if (!(status & 0xffff)) {
			result |= value;
		} else {
			result &= value;
		}

		status = (*codePtrOffsetA >> 6) & 1;

		if (!(*codePtrOffsetA & 0x80)) {
			break;
		}

		codePtrOffset2 += 0xa;
		codePtrOffsetA += 0xa;
	}

	scriptOpCall._code = codePtrOffset2 + 0xa;

	return (result & 0xffff) != 0;
}

void ScriptOpcodes::opSetVariable(ScriptOpCall &scriptOpCall) {
	if (scriptOpCall._field8 == 0) {
		setVariable(scriptOpCall);
	} else {
		scriptOpCall._code += 0xC;
	}
}

void ScriptOpcodes::opRunSpecialOpCode(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(specialOpCode);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	if (specialOpCode >= 140) {
		error("Invalid Special OpCode %d", specialOpCode);
	}

	debug(1, "Special opCode %X", specialOpCode);
	_specialOpCodes->run(specialOpCode);
}

void ScriptOpcodes::opPlayOrStopSound(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(soundId);

	if (scriptOpCall._field8 == 0) {
		_vm->playOrStopSound((uint16) soundId);
	}
}

void ScriptOpcodes::opDelay(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(delay);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	_vm->waitForFrames((uint16)delay);
}

void ScriptOpcodes::opMoveActorToPoint(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(iniId);
	ARG_INT16(walkSpeed);
	ARG_INT16(sequenceId);
	ARG_INT16(pointIndex);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	bool waitForWalkToComplete = true;
	DragonINI *ini = _vm->getINI(iniId - 1);

	if (sequenceId & 0x8000) {
		waitForWalkToComplete = sequenceId == -1;
	}
	Common::Point point = _vm->_scene->getPoint(pointIndex);

	if (walkSpeed != -1) {
		if (sequenceId != -1) {
			if (!(field0 & 0x8000)) {
				assert(ini->actor);
				ini->actor->_flags |= ACTOR_FLAG_800;
				ini->actor->updateSequence(sequenceId & 0x7fff);
			}
			ini->actor->_walkSpeed = walkSpeed & 0x8000 ? (walkSpeed & 0x7fff) << 7 : walkSpeed << 0x10;
		}

		bool isFlicker = _vm->_dragonINIResource->isFlicker(ini);
		ini->actor->startWalk(point.x, point.y, isFlicker ? 0 : 1);

		if (waitForWalkToComplete) {
			ini->actor->waitForWalkToFinish();
		}
		ini->x = point.x;
		ini->y = point.y;
		ini->actor->clearFlag(ACTOR_FLAG_800);

	} else {
		ini->x = point.x;
		ini->actor->_x_pos = point.x;
		ini->y = point.y;
		ini->actor->_y_pos = point.y;

		if (sequenceId != -1) {
			ini->actor->_walkSpeed = -1;
			ini->actor->updateSequence(sequenceId & 0x7fff);
		}
	}
}

void ScriptOpcodes::opMoveActorToXY(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(iniId);
	ARG_INT16(walkSpeed);
	ARG_INT16(sequenceId);
	ARG_INT16(destX);
	ARG_INT16(destY);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	bool waitForWalkToComplete = true;
	DragonINI *ini = _vm->getINI(iniId - 1);

	if (sequenceId & 0x8000) {
		waitForWalkToComplete = sequenceId == -1;
	}

	if (walkSpeed != -1) {
		if (sequenceId != -1) {
			if (!(field0 & 0x8000)) {
				assert(ini->actor);
				ini->actor->setFlag(ACTOR_FLAG_800);
				ini->actor->updateSequence(sequenceId & 0x7fff);
			}
			ini->actor->_walkSpeed = walkSpeed & 0x8000 ? (walkSpeed & 0x7fff) << 7 : walkSpeed << 0x10;
		}
		bool isFlicker = _vm->_dragonINIResource->isFlicker(ini);
		ini->actor->startWalk(destX, destY, isFlicker ? 0 : 1);

		if (waitForWalkToComplete) {
			ini->actor->waitForWalkToFinish();
		}
		ini->x = destX;
		ini->y = destY;
		ini->actor->_flags &= ~ACTOR_FLAG_800;

	} else {
		assert(ini->actor);
		ini->x = destX;
		ini->actor->_x_pos = destX;
		ini->y = destY;
		ini->actor->_y_pos = destY;

		if (sequenceId != -1) {
			ini->actor->_walkSpeed = -1;
			ini->actor->updateSequence(sequenceId & 0x7fff);
		}
	}
}

void ScriptOpcodes::opMoveActorToObject(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(field2);
	ARG_INT16(field4);
	ARG_INT16(field6);
	ARG_INT16(field8);

	//TODO these should be passed in as arguments and xparam should be set correctly.
	int16 someXParam = 0;
	int16 someYParam = _scriptTargetINI;
	uint someBooleanFlag = 0;
	if (scriptOpCall._field8 != 0) {
		return;
	}

	bool bVar1 = false;
	DragonINI *firstIni = _vm->getINI(field4 - 1);
	DragonINI *secondIni = _vm->getINI(field2 - 1);
//	secondIndexIntoINI = (uint)field2 - 1;
//	firstIndexIntoINI = (uint)field4 - 1;
	if ((field6 & 0x8000) != 0) {
		bVar1 = (field6 != -1);
	}
	if (field8 == -1) {
		if ((firstIni->flags & 1) == 0) {
			if ((int)firstIni->imgId != -1) {
				Img *firstDragonImg1 = _vm->_dragonImg->getImg(firstIni->imgId);
				int16 newXPos1 = firstDragonImg1->field_a + firstIni->baseXOffset;
				secondIni->x = newXPos1;
				secondIni->actor->_x_pos = newXPos1;
				int16 newYPos1 = firstDragonImg1->field_c + firstIni->baseYOffset;
				secondIni->y = newYPos1;
				secondIni->actor->_y_pos = newYPos1;
			}
		} else {
			int16 newYPos2 = firstIni->actor->_y_pos + firstIni->baseYOffset;
			firstIni->y = newYPos2;
			secondIni->actor->_y_pos = newYPos2;
			someXParam = firstIni->actor->_x_pos + firstIni->baseXOffset;
			secondIni->x = someXParam;
			secondIni->actor->_x_pos = someXParam;
		}
		if (field6 != -1) {
			secondIni->actor->_walkSpeed = -1;
			secondIni->actor->updateSequence(field6 & 0x7fff);
		}
		secondIni->x = someXParam;
		secondIni->y = someYParam;
		return;
	}
	if ((field8 & 0x8000) == 0) {
		someBooleanFlag = (uint)field8 << 0x10;
	} else {
		someBooleanFlag = ((uint)field8 & 0x7fff) << 7;
	}
	if (field6 != -1) {
		if (((int)(short)field0 & 0x8000) == 0) {
			secondIni->actor->setFlag(ACTOR_FLAG_800);
			secondIni->actor->updateSequence(field6 & 0x7fff);
		}
		secondIni->actor->_walkSpeed =
				someBooleanFlag;
	}
	int16 newXPosAgain = 0;
	int16 newYPosAgain = 0;
	someBooleanFlag = 1;
	if ((firstIni->flags & 1) == 0) {
		if ((int)firstIni->imgId == -1) {
			return;
		}
		Img *firstDragonImg2 = _vm->_dragonImg->getImg(firstIni->imgId);

		newXPosAgain = firstDragonImg2->field_a + firstIni->baseXOffset;
		newYPosAgain = firstDragonImg2->field_c + firstIni->baseYOffset;
		if (_vm->_dragonINIResource->isFlicker(secondIni)) {
			someBooleanFlag = 0;
		}
	} else {
		newXPosAgain = firstIni->actor->_x_pos + firstIni->baseXOffset;
		newYPosAgain = firstIni->actor->_y_pos + firstIni->baseYOffset;
		if (_vm->_dragonINIResource->isFlicker(secondIni)) {
			someBooleanFlag = 0;
		}
	}
	secondIni->actor->startWalk(newXPosAgain, newYPosAgain, someBooleanFlag);
	if (!bVar1) {
		secondIni->actor->waitForWalkToFinish();
	}

	secondIni->actor->_direction = firstIni->direction;

	secondIni->x = newXPosAgain;
	secondIni->y = newYPosAgain;
	secondIni->actor->clearFlag(ACTOR_FLAG_800);
}

void ScriptOpcodes::opUnk11FlickerTalk(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId)
	ARG_UINT32(textIndex)

	if (scriptOpCall._field8 != 0) {
		return;
	}

	_vm->_talk->talkFromIni(iniId, textIndex);
}

void ScriptOpcodes::opLoadScene(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(newSceneID);
	ARG_INT16(cameraPointID);
	ARG_INT16(flickerDirection);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	_vm->fadeToBlack();
	_vm->clearSceneUpdateFunction();
	_vm->_sound->resumeMusic();

	if (newSceneID != 0) {
		_vm->_scene->_mapTransitionEffectSceneID = _vm->_scene->getSceneId();
		_vm->_scene->setSceneId(newSceneID);
		_vm->_flickerInitialSceneDirection = flickerDirection;

		_vm->_scene->loadScene(newSceneID, cameraPointID);
	} else {
		_vm->setFlags(ENGINE_FLAG_100000);
	}
}

void ScriptOpcodes::opCodeActorTalk(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);
	ARG_INT16(startSequenceId);
	ARG_INT16(endSequenceId);
	ARG_SKIP(2);
	ARG_UINT32(textIndex);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	//TODO implement actor talk.

	uint16 dialog[2048];
	dialog[0] = 0;

	int sVar2 = -1; //TODO findTextToDtSpeechIndex(textIndex);

	if (!_vm->isUnkFlagSet(1) && (!_vm->isFlagSet(ENGINE_FLAG_1000_SUBTITLES_DISABLED) || sVar2 == -1)) {
		_vm->_talk->loadText(textIndex, dialog, 2048);
	}

	DragonINI *ini = iniId == 0 ? _vm->_dragonINIResource->getFlickerRecord() : _vm->getINI(iniId - 1);

	if ((ini->flags & 1) == 0) {
		Img *img = _vm->_dragonImg->getImg(ini->imgId);

		int y = img->field_e == 0 ? img->y : img->y << 3;

		_vm->_talk->FUN_8003239c(dialog,
				(int)(((uint)img->field_a - (uint)_vm->_scene->_camera.x) * 0x10000) >> 0x13,
				(int)(((y - 8) - (uint)_vm->_scene->_camera.y) * 0x10000) >> 0x13,
				READ_LE_INT16(_vm->_dragonOBD->getFromOpt(iniId) + 6),
				1,
				ini->actor, startSequenceId, endSequenceId, textIndex);
	} else {
		_vm->_talk->FUN_8003239c(dialog,
								 (int)(((uint)ini->actor->_x_pos - (uint)_vm->_scene->_camera.x) * 0x10000) >> 0x13,
								 (int)((((ini->actor->_y_pos - ini->actor->getFrameYOffset()) - (uint)_vm->_scene->_camera.y) * 0x10000) >> 0x13) - 3,
								 READ_LE_INT16(_vm->_dragonOBD->getFromOpt(iniId) + 6),
								 1,
								 ini->actor, startSequenceId, endSequenceId, textIndex);
	}
}

void ScriptOpcodes::setVariable(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(field2);
	ARG_INT16(field4);
	ARG_INT16(field6);
	ARG_INT16(field8);
	ARG_INT8(fieldA);
	ARG_INT8(fieldB);

	enum S2Type {
		notSet,
		var,
		ini
	};

	S2Type s2Type = notSet;

	int16 s1 = 0;
	int16 s2 = 0;

	if (fieldA & 8) {
		// VAR
		s1 = _vm->getVar(field6 / 2);
	} else if (fieldA & 0x10) {
		// INI
		s1 = getINIField(field8 - 1, field6);
	} else {
		s1 = field8;
	}

	if (fieldA & 1) {
		debug(3, "fieldA & 1");
		bool flagValue = s1 != 0 ? true : false;
		_dragonFLG->set(field2 * 8 + field4, flagValue);
	}

	if (fieldA & 2) {
		debug(3, "fieldA & 2");
		s2Type = var;
		s2 = _vm->getVar(field2);
	}

	if (fieldA & 4) {
		debug(3, "fieldA & 4");
		s2Type = ini;
		s2 = getINIField(field4 - 1, field2);
		DragonINI *ini = _vm->getINI(field4 - 1);

		if (field2 == 0x1a && ini->flags & 1 && ini->sceneId == _vm->getCurrentSceneId()) {
			if (s1 & 2) {
				ini->actor->_flags |= ACTOR_FLAG_80;
				ini->actor->_scale = DRAGONS_ENGINE_SPRITE_100_PERCENT_SCALE;
			} else {
				ini->actor->_flags &= ~ACTOR_FLAG_80;
			}

			if (s1 & 4) {
				ini->actor->_flags |= ACTOR_FLAG_8000;
			} else {
				ini->actor->_flags &= ~ACTOR_FLAG_8000;
			}
		}

		if (fieldA & 4 && field2 == 0 && !(ini->flags & 1) && ini->sceneId == _vm->getCurrentSceneId()) {
			if (s1 == -1) {
				if (ini->iptIndex_maybe != -1) {
					_vm->_scene->removeImageOverlay(ini->iptIndex_maybe);
				}
			} else {
				_vm->_scene->loadImageOverlay(s1);
			}
		}
	}

	if (s2Type != notSet) {
		if (fieldB == 1) {
			s2 += s1;
		} else {
			if (fieldB < 2) {
				if (fieldB == 0) {
					s2 = s1;
				}
			} else {
				if (fieldB == 2) {
					s2 -= s1;
				} else {
					if (fieldB == 3) {
						s2 = _vm->getRand(MAX<int16>(1, s1));
					}
				}
			}
		}

		if (s2Type == ini) {
			setINIField(field4 - 1, field2, s2);
		} else  { //var type
			_vm->setVar(field2, s2);
		}
	}
}

uint16 ScriptOpcodes::getINIField(uint32 iniIndex, uint16 fieldOffset) {
	DragonINI *ini = _vm->getINI(iniIndex);

	switch (fieldOffset) {
	case 0 : return ini->iptIndex_maybe;
	case 4 : return ini->actorResourceId;
	case 6 : return ini->sequenceId;
	case 0xC  : return ini->sceneId;
	case 0xE  : return ini->direction;
	case 0x10 : return ini->counter;
	case 0x12  : return ini->objectState;
	case 0x14 : return ini->objectState2;
	case 0x16 : return ini->x;
	case 0x18 : return ini->y;
	case 0x1A : return ini->flags;
	case 0x1C : return ini->baseXOffset;
	case 0x1E : return ini->baseYOffset;
	case 0x20 : return ini->direction2;
	default: error("getINIField() Invalid fieldOffset 0x%X", fieldOffset);
	}
}

void ScriptOpcodes::setINIField(uint32 iniIndex, uint16 fieldOffset, uint16 value) {
	DragonINI *ini = _vm->getINI(iniIndex);

	switch (fieldOffset) {
	case 0 : ini->iptIndex_maybe = value; break;
	case 4 : ini->actorResourceId = value; break;
	case 6 : ini->sequenceId = value; break;
	case 0xc : ini->sceneId = value; break;
	case 0xe : ini->direction = value; break;
	case 0x10 : ini->counter = value; break;
	case 0x12 : ini->objectState = value; break;
	case 0x14 : ini->objectState2 = value; break;
	case 0x16 : ini->x = value; break;
	case 0x18 : ini->y = value; break;
	case 0x1A : ini->flags = value; break;
	case 0x1C : ini->baseXOffset = value; break;
	case 0x1E : ini->baseYOffset = value; break;
	case 0x20 : ini->direction2 = value; break;
	default: error("setINIField() Invalid fieldOffset 0x%X", fieldOffset);
	}

}

void ScriptOpcodes::opUnk16(ScriptOpCall &scriptOpCall) {
	if (scriptOpCall._field8 == 4) {
		scriptOpCall._result |= 1;
	} else {
		scriptOpCall._code += 4;
	}
}

void ScriptOpcodes::opWaitForActorSequenceToFinish(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);

	DragonINI *ini = _vm->getINI(iniId - 1);
	if (ini->flags & 1) {
		while (!(ini->actor->_flags & ACTOR_FLAG_4)) {
			_vm->waitForFrames(1);
		}
	}

}


void ScriptOpcodes::opDialogAtPoint(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_UINT32(field2);
	ARG_INT16(x);
	ARG_INT16(y);
	ARG_INT16(fieldA);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	uint16 dialog[2000];
	dialog[0] = 0;
	_vm->_talk->loadText(field2, dialog, 2000);

//	if (((unkFlags1 & 1) == 0) && (((engine_flags_maybe & 0x1000) == 0 || (sVar1 == -1)))) {
//		dialogText = (uint8 *)load_string_from_dragon_txt(offset, acStack2016);
//	}

	if (fieldA != 0) {
		fieldA = READ_LE_INT16(_vm->_dragonOBD->getFromOpt(fieldA - 1) + 6);
	}
	_vm->_talk->displayDialogAroundPoint(dialog, x, y, fieldA, 1, field2);
}

void ScriptOpcodes::opExecuteObjectSceneScript(ScriptOpCall &scriptOpCall) {
	ARG_INT16(field0);
	ARG_INT16(size);

	if (scriptOpCall._field8 == 3) {
		ScriptOpCall newCall(scriptOpCall._code, size);
		_vm->_scriptOpcodes->runScript(newCall);
	}
	scriptOpCall._code += size;

}

void ScriptOpcodes::opUpdatePaletteCycling(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(index);
	ARG_INT16(field4);
	ARG_INT16(field6);
	ARG_INT16(field8);
	ARG_INT16(fieldA);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	assert(index < 8);

	_vm->_paletteCyclingTbl[index].paletteType = field4;
	_vm->_paletteCyclingTbl[index].startOffset = field6;
	_vm->_paletteCyclingTbl[index].endOffset = field8;
	_vm->_paletteCyclingTbl[index].updateInterval = fieldA;
	_vm->_paletteCyclingTbl[index].updateCounter = 0;
}

void ScriptOpcodes::opWaitForActorToFinishWalking(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	DragonINI *ini = _vm->getINI(iniId - 1);
	if (ini->flags & 1) {
		ini->actor->waitForWalkToFinish();
	}
}

void ScriptOpcodes::opShowActor(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	DragonINI *ini = _vm->getINI(iniId - 1);
	ini->actor->clearFlag(ACTOR_FLAG_400);
}

void ScriptOpcodes::opHideActor(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	DragonINI *ini = _vm->getINI(iniId - 1);
	ini->actor->setFlag(ACTOR_FLAG_400);
}

void ScriptOpcodes::opSetActorFlag0x1000(ScriptOpCall &scriptOpCall) {
	ARG_SKIP(2);
	ARG_INT16(iniId);

	if (scriptOpCall._field8 != 0) {
		return;
	}

	DragonINI *ini = _vm->getINI(iniId - 1);
	ini->actor->setFlag(ACTOR_FLAG_1000);
}

void ScriptOpcodes::opCode_Unk7(ScriptOpCall &scriptOpCall) {

}

void ScriptOpcodes::loadTalkDialogEntries(ScriptOpCall &scriptOpCall) {
	scriptOpCall._field8 = 2;
	scriptOpCall._result = 0;
	_vm->_talk->clearDialogEntries();
	_numDialogStackFramesToPop = 0;
	executeScriptLoop(scriptOpCall);

}

} // End of namespace Dragons
