/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

// TODO: Clean up game variable handling and move it to ToltecsEngine

#include "common/error.h"

#include "graphics/cursorman.h"

#include "toltecs/toltecs.h"
#include "toltecs/animation.h"
#include "toltecs/menu.h"
#include "toltecs/movie.h"
#include "toltecs/music.h"
#include "toltecs/palette.h"
#include "toltecs/resource.h"
#include "toltecs/script.h"
#include "toltecs/segmap.h"
#include "toltecs/sound.h"

namespace Toltecs {

static const VarType varTypes[] = {
	vtByte, vtWord, vtWord, vtByte, vtWord,  //  0 - 4
	vtWord, vtWord, vtWord, vtWord, vtWord,  //  5 - 9
	vtWord, vtWord, vtByte, vtWord, vtWord,  // 10 - 14
	vtWord, vtWord, vtWord, vtWord, vtWord,  // 15 - 19
	vtWord, vtWord                           // 20 - 21
};

static const char *varNames[] = {
	"mouseDisabled", "mouseY",        "mouseX",            "mouseButton",   "verbLineY",        //  0 - 4
	"verbLineX",     "verbLineWidth", "verbLineCount",     "verbLineNum",   "talkTextItemNum",  //  5 - 9
	"talkTextY",     "talkTextX",     "talkTextFontColor", "cameraY",       "cameraX",          // 10 - 14
	"walkSpeedY",    "walkSpeedX",    "flag01",            "sceneResIndex", "guiHeight",        // 15 - 19
	"sceneHeight",   "sceneWidth"                                                               // 20 - 21
};

ScriptInterpreter::ScriptInterpreter(ToltecsEngine *vm) : _vm(vm) {

	_stack = new byte[kScriptStackSize];

	memset(_slots, 0, sizeof(_slots));

	_savedSp = 0;

	_slots[kMaxScriptSlots - 1].size = 1024;
	_slots[kMaxScriptSlots - 1].data = new byte[_slots[kMaxScriptSlots - 1].size];

	setupScriptFunctions();

}

ScriptInterpreter::~ScriptInterpreter() {
	delete[] _stack;
	for (int i = 0; i < kMaxScriptSlots; i++)
		delete[] _slots[i].data;
	for (uint i = 0; i < _scriptFuncs.size(); ++i)
		delete _scriptFuncs[i];
}

typedef Common::Functor0Mem<void, ScriptInterpreter> ScriptFunctionF;
#define RegisterScriptFunction(x) \
	_scriptFuncs.push_back(new ScriptFunctionF(this, &ScriptInterpreter::x));  \
	_scriptFuncNames.push_back(#x);
void ScriptInterpreter::setupScriptFunctions() {

	// 0
	RegisterScriptFunction(sfNop);
	RegisterScriptFunction(sfNop);
	RegisterScriptFunction(sfGetGameVar);
	RegisterScriptFunction(sfSetGameVar);
	RegisterScriptFunction(sfUpdateScreen);
	// 5
	RegisterScriptFunction(sfGetRandomNumber);
	RegisterScriptFunction(sfDrawGuiTextMulti);
	RegisterScriptFunction(sfUpdateVerbLine);
	RegisterScriptFunction(sfSetFontColor);
	RegisterScriptFunction(sfGetTalkTextDuration);
	// 10
	RegisterScriptFunction(sfTalk);
	RegisterScriptFunction(sfFindPaletteFragment);
	RegisterScriptFunction(sfClearPaletteFragments);
	RegisterScriptFunction(sfAddPaletteFragment);
	RegisterScriptFunction(sfSetDeltaAnimPalette);
	// 15
	RegisterScriptFunction(sfSetUnkPaletteEffect);
	RegisterScriptFunction(sfBuildColorTransTable);
	RegisterScriptFunction(sfSetDeltaMainPalette);
	RegisterScriptFunction(sfLoadScript);
	RegisterScriptFunction(sfRegisterFont);
	// 20
	RegisterScriptFunction(sfLoadAddPalette);
	RegisterScriptFunction(sfLoadScene);
	RegisterScriptFunction(sfSetGuiHeight);
	RegisterScriptFunction(sfFindMouseInRectIndex1);
	RegisterScriptFunction(sfFindMouseInRectIndex2);
	// 25
	RegisterScriptFunction(sfDrawGuiImage);
	RegisterScriptFunction(sfAddAnimatedSpriteNoLoop);
	RegisterScriptFunction(sfAddAnimatedSprite);
	RegisterScriptFunction(sfAddStaticSprite);
	RegisterScriptFunction(sfAddAnimatedSpriteScaled);
	// 30
	RegisterScriptFunction(sfFindPath);
	RegisterScriptFunction(sfWalk);
	RegisterScriptFunction(sfScrollCameraUp);
	RegisterScriptFunction(sfScrollCameraDown);
	RegisterScriptFunction(sfScrollCameraLeft);
	// 35
	RegisterScriptFunction(sfScrollCameraRight);
	RegisterScriptFunction(sfScrollCameraUpEx);
	RegisterScriptFunction(sfScrollCameraDownEx);
	RegisterScriptFunction(sfScrollCameraLeftEx);
	RegisterScriptFunction(sfScrollCameraRightEx);
	// 40
	RegisterScriptFunction(sfSetCamera);
	RegisterScriptFunction(sfGetCameraChanged);
	RegisterScriptFunction(sfGetRgbModifiertAtPoint);
	RegisterScriptFunction(sfStartAnim);
	RegisterScriptFunction(sfAnimNextFrame);
	// 45
	RegisterScriptFunction(sfNop);
	RegisterScriptFunction(sfGetAnimFrameNumber);
	RegisterScriptFunction(sfGetAnimStatus);
	RegisterScriptFunction(sfStartShakeScreen);
	RegisterScriptFunction(sfStopShakeScreen);
	// 50
	RegisterScriptFunction(sfStartSequence);
	RegisterScriptFunction(sfEndSequence);
	RegisterScriptFunction(sfSetSequenceVolume);
	RegisterScriptFunction(sfPlayPositionalSound);
	RegisterScriptFunction(sfPlaySound2);
	// 55
	RegisterScriptFunction(sfClearScreen);
	RegisterScriptFunction(sfNop);
	RegisterScriptFunction(sfHandleInput);
	RegisterScriptFunction(sfRunOptionsScreen);
	RegisterScriptFunction(sfPrecacheSprites);
	// 60
	RegisterScriptFunction(sfPrecacheSounds1);
	RegisterScriptFunction(sfDeletePrecachedFiles);
	RegisterScriptFunction(sfPrecacheSounds2);
	RegisterScriptFunction(sfRestoreStackPtr);
	RegisterScriptFunction(sfSaveStackPtr);
	// 65
	RegisterScriptFunction(sfPlayMovie);
	RegisterScriptFunction(sfNop);

}

void ScriptInterpreter::loadScript(uint resIndex, uint slotIndex) {
	if (_slots[slotIndex].resIndex && _slots[slotIndex].resIndex != resIndex && _vm->_screen->isTalkTextActive(slotIndex)) {
		// WORKAROUND: This happens when examining the assembled
		// pickaxe. It could lead to random characters being printed,
		// or possibly even crashes, when subtitles are enabled.
		//
		// According to johndoe and he said there may be some bug or
		// missing feature that causes this situation to happen at all,
		// but he was ok with this workaround for now.
		warning("Possible script bug: Loading script %d into slot %d that has an active talk text, probably for script %d", resIndex, slotIndex, _slots[slotIndex].resIndex);
		_vm->_screen->finishTalkTextItem(slotIndex);
	}

	delete[] _slots[slotIndex].data;

	_slots[slotIndex].resIndex = resIndex;
	Resource *scriptResource = _vm->_res->load(resIndex);
	_slots[slotIndex].size = scriptResource->size;
	_slots[slotIndex].data = new byte[_slots[slotIndex].size];
	memcpy(_slots[slotIndex].data, scriptResource->data, _slots[slotIndex].size);
}

void ScriptInterpreter::setMainScript(uint slotIndex) {
	_switchLocalDataNear = true;
	_switchLocalDataFar = false;
	_switchLocalDataToStack = false;
	_cmpBitTest = false;
	_regs.reg0 = 0;
	_regs.reg1 = 0;
	_regs.reg2 = 0;
	_regs.reg3 = 0;
	_regs.reg4 = slotIndex;
	_regs.reg5 = 0;
	_regs.reg6 = 0;
	_regs.sp = 4096;
	_regs.reg8 = 0;
	_code = getSlotData(_regs.reg4);
}

void ScriptInterpreter::runScript() {
	while (!_vm->shouldQuit()) {
		if (_vm->_movieSceneFlag)
			_vm->_mouseButton = 0;

		if (_vm->_saveLoadRequested != 0) {
			if (_vm->_saveLoadRequested == 1)
				_vm->loadGameState(_vm->_saveLoadSlot);
			else if (_vm->_saveLoadRequested == 2)
				_vm->saveGameState(_vm->_saveLoadSlot, _vm->_saveLoadDescription);
			_vm->_saveLoadRequested = 0;
		}

		if (_switchLocalDataNear) {
			_switchLocalDataNear = false;
			_localData = getSlotData(_regs.reg4);
		}

		if (_switchLocalDataFar) {
			_switchLocalDataFar = false;
			_localData = getSlotData(_regs.reg5);
			_switchLocalDataNear = true;
		}

		if (_switchLocalDataToStack) {
			_switchLocalDataToStack = false;
			_localData = _stack + 2;
			_switchLocalDataNear = true;
		}

		byte opcode = readByte();
		execOpcode(opcode);
	}
}

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

int16 ScriptInterpreter::readInt16() {
	int16 value = READ_LE_UINT16(_code);
	_code += 2;
	return value;
}

void ScriptInterpreter::execOpcode(byte opcode) {
	int16 ofs;

	debug(2, "opcode = %d", opcode);

	switch (opcode) {
	case 0:
	{
		// ok
		_subCode = _code;
		byte length = readByte();
		if (length == 0) {
			warning("Opcode length is 0 when calling script function");
			return;
		}
		debug(2, "length = %d", length);
		uint16 index = readInt16();
		execScriptFunction(index);
		_code += length - 2;
		break;
	}
	case 1:
		_regs.reg0 = readInt16();
		break;
	case 2:
		_regs.reg1 = readInt16();
		break;
	case 3:
		_regs.reg3 = readInt16();
		break;
	case 4:
		_regs.reg5 = _regs.reg0;
		break;
	case 5:
		_regs.reg3 = _regs.reg0;
		break;
	case 6:
		_regs.reg1 = _regs.reg0;
		break;
	case 7:
		_regs.reg1 = localRead16(_regs.reg3);
		break;
	case 8:
		localWrite16(_regs.reg3, _regs.reg0);
		break;
	case 9:
		localWrite16(readInt16(), _regs.reg0);
		break;
	case 10:
		localWrite8(readInt16(), _regs.reg0);
		break;
	case 11:
		localWrite16(readInt16(), _regs.reg5);
		break;
	case 12:
		localWrite16(readInt16(), _regs.reg4);
		break;
	case 13:
		localWrite16(readInt16(), _regs.reg3);
		break;
	case 14:
		_regs.reg3 = localRead16(readInt16());
		break;
	case 15:
		_regs.reg2 = localRead16(_regs.reg1);
		break;
	case 16:
		_regs.reg2 = localRead16(_regs.reg1 + readInt16());
		break;
	case 17:
		_regs.reg2 = _regs.reg0;
		break;
	case 18:
		_regs.reg0 += readInt16();
		break;
	case 19:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) + _regs.reg0);
		break;
	case 20:
		_regs.reg0 += _regs.reg2;
		break;
	case 21:
		_regs.reg3 += _regs.sp;
		break;
	case 22:
		_regs.reg1 += _regs.sp;
		break;
	case 23:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) - _regs.reg0);
		break;
	case 24:
		_regs.reg0 /= readInt16();
		break;
	case 25:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) / _regs.reg0);
		break;
	case 26:
		// NOP
		break;
	case 27:
		_regs.reg0 *= readInt16();
		break;
	case 28:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) * _regs.reg0);
		break;
	case 29:
		_regs.reg0 *= _regs.reg2;
		break;
	case 30:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) + 1);
		break;
	case 31:
		localWrite16(_regs.reg3, localRead16(_regs.reg3) - 1);
		break;
	case 32:
		_switchLocalDataFar = true;
		break;
	case 33:
		_switchLocalDataToStack = true;
		break;
	case 34:
		pushInt16(_regs.reg0);
		break;
	case 35:
		pushInt16(_regs.reg1);
		break;
	case 36:
		_regs.reg1 = popInt16();
		break;
	case 37:
		_regs.reg0 = popInt16();
		break;
	case 38:
		_regs.reg2 = -_regs.reg2;
		break;
	case 39:
		_regs.reg8 = readInt16();
		_cmpBitTest = false;
		break;
	case 40:
		_regs.reg8 = _regs.reg0;
		_cmpBitTest = false;
		break;
	case 41:
		_regs.reg8 = readInt16();
		_cmpBitTest = true;
		break;
	case 42:
		_regs.reg8 = _regs.reg0;
		_cmpBitTest = true;
		break;
	case 43:
		_code = getSlotData(_regs.reg4) + _regs.reg0;
		break;
	case 44:
		_code = getSlotData(_regs.reg5) + _regs.reg0;
		_regs.reg4 = _regs.reg5;
		_switchLocalDataNear = true;
		break;
	case 45:
		pushInt16(_code - getSlotData(_regs.reg4));
		pushInt16(_regs.reg4);
		_code = getSlotData(_regs.reg4) + _regs.reg0;
		break;
	case 46:
		pushInt16(_code - getSlotData(_regs.reg4));
		pushInt16(_regs.reg4);
		_code = getSlotData(_regs.reg5) + _regs.reg0;
		_regs.reg4 = _regs.reg5;
		_switchLocalDataNear = true;
		break;
	case 47:
		_regs.reg4 = popInt16();
		ofs = popInt16();
		_code = getSlotData(_regs.reg4) + ofs;
		_switchLocalDataNear = true;
		break;
	case 48:
		_regs.reg4 = popInt16();
		ofs = popInt16();
		_code = getSlotData(_regs.reg4) + ofs;
		_regs.sp += _regs.reg0;
		_switchLocalDataNear = true;
		break;
	case 49:
		ofs = readByte();
		_code += ofs;
		break;
	case 50:
		if (_cmpBitTest) {
			_regs.reg1 &= _regs.reg8;
			if (_regs.reg1 == 0)
				_code += 4;
		} else {
			if (_regs.reg1 == _regs.reg8)
				_code += 4;
		}
		_code++;
		break;
	case 51:
		if (_cmpBitTest) {
			_regs.reg1 &= _regs.reg8;
			if (_regs.reg1 != 0)
				_code += 4;
		} else {
			if (_regs.reg1 != _regs.reg8)
				_code += 4;
		}
		_code++;
		break;
	case 52:
		if ((uint16)_regs.reg1 >= (uint16)_regs.reg8)
			_code += 4;
		_code++;
		break;
	case 53:
		if ((uint16)_regs.reg1 <= (uint16)_regs.reg8)
			_code += 4;
		_code++;
		break;
	case 54:
		if ((uint16)_regs.reg1 < (uint16)_regs.reg8)
			_code += 4;
		_code++;
		break;
	case 55:
		if ((uint16)_regs.reg1 > (uint16)_regs.reg8)
			_code += 4;
		_code++;
		break;
	default:
		// Most likely a script bug. Throw a warning and ignore it.
		// The original ignores invalid opcodes as well - bug #3604025.
		warning("Invalid opcode %d", opcode);
	}

}

void ScriptInterpreter::execScriptFunction(uint16 index) {
	if (index >= _scriptFuncs.size())
		error("ScriptInterpreter::execScriptFunction() Invalid script function index %d", index);
	debug(1, "execScriptFunction %s (%d)", _scriptFuncNames[index], index);
	(*_scriptFuncs[index])();
}

int16 ScriptInterpreter::getGameVar(uint variable) {
	debug(2, "ScriptInterpreter::getGameVar(%d{%s})", variable, varNames[variable]);

	switch (variable) {
	case  0: return _vm->_mouseDisabled;
	case  1: return _vm->_mouseY;
	case  2: return _vm->_mouseX;
	case  3: return _vm->_mouseButton;
	case  4: return _vm->_screen->_verbLineY;
	case  5: return _vm->_screen->_verbLineX;
	case  6: return _vm->_screen->_verbLineWidth;
	case  7: return _vm->_screen->_verbLineCount;
	case  8: return _vm->_screen->_verbLineNum;
	case  9: return _vm->_screen->_talkTextItemNum;
	case 10: return _vm->_screen->_talkTextY;
	case 11: return _vm->_screen->_talkTextX;
	case 12: return _vm->_screen->_talkTextFontColor;
	case 13: return _vm->_cameraY;
	case 14: return _vm->_cameraX;
	case 15: return _vm->_walkSpeedY;
	case 16: return _vm->_walkSpeedX;
	case 17: return _vm->_flag01;
	case 18: return _vm->_sceneResIndex;
	case 19: return _vm->_guiHeight;
	case 20: return _vm->_sceneHeight;
	case 21: return _vm->_sceneWidth;
	default:
		warning("Getting unimplemented game variable %s (%d)", varNames[variable], variable);
		return 0;
	}
}

void ScriptInterpreter::setGameVar(uint variable, int16 value) {
	debug(2, "ScriptInterpreter::setGameVar(%d{%s}, %d)", variable, varNames[variable], value);

	switch (variable) {
	case 0:
		_vm->_mouseDisabled = value;
		CursorMan.showMouse(value == 0);
		break;
	case 3:
		_vm->_mouseButton = value;
		break;
	case 4:
		_vm->_screen->_verbLineY = value;
		break;
	case 5:
		_vm->_screen->_verbLineX = value;
		break;
	case 6:
		_vm->_screen->_verbLineWidth = value;
		break;
	case 7:
		_vm->_screen->_verbLineCount = value;
		break;
	case 8:
		_vm->_screen->_verbLineNum = value;
		break;
	case 9:
		_vm->_screen->_talkTextItemNum = value;
		break;
	case 10:
		_vm->_screen->_talkTextY = value;
		break;
	case 11:
		_vm->_screen->_talkTextX = value;
		break;
	case 12:
		_vm->_screen->_talkTextFontColor = value;
		break;
	case 13:
		_vm->_cameraY = value;
		break;
	case 14:
		_vm->_cameraX = value;
		break;
	case 15:
		_vm->_walkSpeedY = value;
		break;
	case 16:
		_vm->_walkSpeedX = value;
		break;
	case 17:
		_vm->_flag01 = value != 0;
		break;
	case 18:
		_vm->_sceneResIndex = value;
		break;
	case 19:
		_vm->_guiHeight = value;
		break;
	case 20:
		_vm->_sceneHeight = value;
		break;
	case 21:
		_vm->_sceneWidth = value;
		break;
	case 1:
	case 2:
	default:
		warning("Setting unimplemented game variable %s (%d) to %d", varNames[variable], variable, value);
		break;
	}
}

byte ScriptInterpreter::arg8(int16 offset) {
	return _subCode[offset];
}

int16 ScriptInterpreter::arg16(int16 offset) {
	return READ_LE_UINT16(&_subCode[offset]);
}

void ScriptInterpreter::pushInt16(int16 value) {
	WRITE_LE_UINT16(_stack + _regs.sp, value);
	_regs.sp -= 2;
}

int16 ScriptInterpreter::popInt16() {
	_regs.sp += 2;
	return READ_LE_UINT16(_stack + _regs.sp);
}

void ScriptInterpreter::localWrite8(int16 offset, byte value) {
	//debug(2, "localWrite8(%d, %d)", offset, value);
	_localData[offset] = value;
}

byte ScriptInterpreter::localRead8(int16 offset) {
	//debug(2, "localRead8(%d) -> %d", offset, _localData[offset]);
	return _localData[offset];
}

void ScriptInterpreter::localWrite16(int16 offset, int16 value) {
	//debug(2, "localWrite16(%d, %d)", offset, value);
	WRITE_LE_UINT16(&_localData[offset], value);
}

int16 ScriptInterpreter::localRead16(int16 offset) {
	//debug(2, "localRead16(%d) -> %d", offset, (int16)READ_LE_UINT16(&_localData[offset]));
	return (int16)READ_LE_UINT16(&_localData[offset]);
}

byte *ScriptInterpreter::localPtr(int16 offset) {
	//debug(2, "localPtr(%d)", offset);
	return &_localData[offset];
}

void ScriptInterpreter::saveState(Common::WriteStream *out) {
	// Save registers
	out->writeUint16LE(_regs.reg0);
	out->writeUint16LE(_regs.reg1);
	out->writeUint16LE(_regs.reg2);
	out->writeUint16LE(_regs.reg3);
	out->writeUint16LE(_regs.reg4);
	out->writeUint16LE(_regs.reg5);
	out->writeUint16LE(_regs.reg6);
	out->writeUint16LE(_regs.sp);
	out->writeUint16LE(_regs.reg8);

	// Save slots
	for (int slot = 0; slot < kMaxScriptSlots; slot++) {
		out->writeUint32LE(_slots[slot].size);
		out->writeUint16LE(_slots[slot].resIndex);
		if (_slots[slot].size > 0)
			out->write(_slots[slot].data, _slots[slot].size);
	}

	// Save stack
	out->write(_stack, kScriptStackSize);
	out->writeUint16LE(_savedSp);

	// Save IP
	out->writeUint16LE((int16)(_code - getSlotData(_regs.reg4)));
}

void ScriptInterpreter::loadState(Common::ReadStream *in) {
	// Load registers
	_regs.reg0 = in->readUint16LE();
	_regs.reg1 = in->readUint16LE();
	_regs.reg2 = in->readUint16LE();
	_regs.reg3 = in->readUint16LE();
	_regs.reg4 = in->readUint16LE();
	_regs.reg5 = in->readUint16LE();
	_regs.reg6 = in->readUint16LE();
	_regs.sp = in->readUint16LE();
	_regs.reg8 = in->readUint16LE();

	// Load slots
	for (int slot = 0; slot < kMaxScriptSlots; slot++) {
		_slots[slot].size = in->readUint32LE();
		_slots[slot].resIndex = in->readUint16LE();
		_slots[slot].data = NULL;
		if (_slots[slot].size > 0) {
			_slots[slot].data = new byte[_slots[slot].size];
			in->read(_slots[slot].data, _slots[slot].size);
		}
	}

	// Load stack
	in->read(_stack, kScriptStackSize);
	_savedSp = in->readUint16LE();

	// Load IP
	_code = getSlotData(_regs.reg4) + in->readUint16LE();
}

void ScriptInterpreter::sfNop() {
	// NOP
}

void ScriptInterpreter::sfGetGameVar() {
	int16 value = getGameVar(arg16(3));
	localWrite16(arg16(5), value);
}

void ScriptInterpreter::sfSetGameVar() {
	int16 varIndex = arg16(3);
	assert(varIndex <= 21);

	VarType varType = varTypes[varIndex];
	int16 value = 0;
	if (varType == vtByte)
		value = arg8(5);
	else if (varType == vtWord)
		value = arg16(5);
	setGameVar(varIndex, value);
}

void ScriptInterpreter::sfUpdateScreen() {
	_vm->updateScreen();
}

void ScriptInterpreter::sfGetRandomNumber() {
	localWrite16(arg16(5), _vm->_rnd->getRandomNumber(arg16(3) - 1));
}

void ScriptInterpreter::sfDrawGuiTextMulti() {
	_vm->_screen->drawGuiTextMulti((byte *)localPtr(arg16(3)));
}

void ScriptInterpreter::sfUpdateVerbLine() {
	_vm->_screen->updateVerbLine(arg16(5), arg16(3));
}

void ScriptInterpreter::sfSetFontColor() {
	_vm->_screen->_fontColor1 = 0;
	_vm->_screen->_fontColor2 = arg8(3);
}

void ScriptInterpreter::sfGetTalkTextDuration() {
	localWrite16(arg16(3), _vm->_screen->getTalkTextDuration());
}

void ScriptInterpreter::sfTalk() {
	_vm->talk(arg16(5), arg16(3));
}

void ScriptInterpreter::sfFindPaletteFragment() {
	localWrite16(arg16(5), _vm->_palette->findFragment(arg16(3)));
}

void ScriptInterpreter::sfClearPaletteFragments() {
	_vm->_palette->clearFragments();
}

void ScriptInterpreter::sfAddPaletteFragment() {
	_vm->_palette->addFragment(arg16(3), arg16(5));
}

void ScriptInterpreter::sfSetDeltaAnimPalette() {
	_vm->_palette->setDeltaPalette(_vm->_palette->getAnimPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3));
}

void ScriptInterpreter::sfSetUnkPaletteEffect() {
	error("ScriptInterpreter::sfSetUnkPaletteEffect called");	// unused
}

void ScriptInterpreter::sfBuildColorTransTable() {
	_vm->_palette->buildColorTransTable(arg8(4), (char)arg8(3), arg8(5));
}

void ScriptInterpreter::sfSetDeltaMainPalette() {
	_vm->_palette->setDeltaPalette(_vm->_palette->getMainPalette(), arg8(6), (char)arg8(5), arg8(4), arg8(3));
}

void ScriptInterpreter::sfLoadScript() {
	int16 codeOfs = _code - getSlotData(_regs.reg4);
	loadScript(arg16(4), arg8(3));
	_code = getSlotData(_regs.reg4) + codeOfs;
	_switchLocalDataNear = true;
}

void ScriptInterpreter::sfRegisterFont() {
	_vm->_screen->registerFont(arg8(3), arg16(4));
}

void ScriptInterpreter::sfLoadAddPalette() {
	_vm->_palette->loadAddPalette(arg16(4), arg8(3));
}

void ScriptInterpreter::sfLoadScene() {
	if (arg8(3) == 0) {
		// FIXME: Originally, this was stopSpeech(). However, we need to stop
		// ALL sounds here (including sound effects and background sounds)
		// before purgeCache() is called, otherwise the sound buffers will be
		// invalidated. This is apparent when moving from a scene that has
		// background sounds (such as the canyon at the beginning), to another
		// one that doesn't (such as the map), and does not stop the sounds
		// already playing. In this case, the engine will either crash or
		// garbage will be heard through the speakers.
		// TODO: We should either move purgeCache() elsewhere, or monitor
		// which resources are still used before purging the cache.
		_vm->_sound->stopAll();
		_vm->_res->purgeCache();
		_vm->loadScene(arg16(4));
	} else {
		_vm->_screen->loadMouseCursor(arg16(4));
	}
}

void ScriptInterpreter::sfSetGuiHeight() {
	_vm->setGuiHeight(arg8(3));
}

void ScriptInterpreter::sfFindMouseInRectIndex1() {
	int16 index = -1;
	if (_vm->_mouseY < _vm->_cameraHeight) {
		int16 slotIndex = arg16(5);
		index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3),
			_vm->_mouseX + _vm->_cameraX,
			_vm->_mouseY + _vm->_cameraY,
			arg16(11) + 1, arg16(7),
			getSlotData(slotIndex) + _slots[slotIndex].size);
	}
	localWrite16(arg16(9), index);
}

void ScriptInterpreter::sfFindMouseInRectIndex2() {
	int16 index = -1;
	if (_vm->_sceneResIndex != 0) {
		if (_vm->_mouseY < _vm->_cameraHeight) {
			int16 slotIndex = arg16(5);
			index = _vm->findRectAtPoint(getSlotData(slotIndex) + arg16(3),
				_vm->_mouseX + _vm->_cameraX,
				_vm->_mouseY + _vm->_cameraY,
				0, arg16(7),
				getSlotData(slotIndex) + _slots[slotIndex].size);
		}
	}
	localWrite16(arg16(9), index);
}

void ScriptInterpreter::sfDrawGuiImage() {
	_vm->_screen->drawGuiImage(arg16(5), arg16(3), arg16(7));
}

void ScriptInterpreter::sfAddAnimatedSpriteNoLoop() {
	_vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), false, 2);
}

void ScriptInterpreter::sfAddAnimatedSprite() {
	_vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), true, 2);
}

void ScriptInterpreter::sfAddStaticSprite() {
	_vm->_screen->addStaticSprite(_subCode + 3);
}

void ScriptInterpreter::sfAddAnimatedSpriteScaled() {
	_vm->_screen->addAnimatedSprite(arg16(5), arg16(3), arg16(7), (byte *)localPtr(0), (int16 *)localPtr(arg16(9)), true, 1);
}

void ScriptInterpreter::sfFindPath() {
	_vm->_segmap->findPath((int16 *)(getSlotData(arg16(13)) + arg16(11)), arg16(9), arg16(7), arg16(5), arg16(3));
}

void ScriptInterpreter::sfWalk() {
	_vm->walk(getSlotData(arg16(5)) + arg16(3));
}

void ScriptInterpreter::sfScrollCameraUp() {
	_vm->scrollCameraUp(4);
}

void ScriptInterpreter::sfScrollCameraDown() {
	_vm->scrollCameraDown(4);
}

void ScriptInterpreter::sfScrollCameraLeft() {
	_vm->scrollCameraLeft(4);
}

void ScriptInterpreter::sfScrollCameraRight() {
	_vm->scrollCameraRight(4);
}

void ScriptInterpreter::sfScrollCameraUpEx() {
	_vm->scrollCameraUp(arg16(3));
}

void ScriptInterpreter::sfScrollCameraDownEx() {
	_vm->scrollCameraDown(arg16(3));
}

void ScriptInterpreter::sfScrollCameraLeftEx() {
	_vm->scrollCameraLeft(arg16(3));
}

void ScriptInterpreter::sfScrollCameraRightEx() {
	_vm->scrollCameraRight(arg16(3));
}

void ScriptInterpreter::sfSetCamera() {
	_vm->setCamera(arg16(5), arg16(3));
}

void ScriptInterpreter::sfGetCameraChanged() {
	localWrite16(arg16(3), _vm->getCameraChanged() ? 1 : 0);
}

void ScriptInterpreter::sfGetRgbModifiertAtPoint() {
	byte *rgb = getSlotData(arg16(11)) + arg16(9);
	_vm->_segmap->getRgbModifiertAtPoint(arg16(5), arg16(3), arg16(7), rgb[0], rgb[1], rgb[2]);
}

void ScriptInterpreter::sfStartAnim() {
	_vm->_anim->start(arg16(3));
}

void ScriptInterpreter::sfAnimNextFrame() {
	_vm->_anim->nextFrame();
}

void ScriptInterpreter::sfGetAnimFrameNumber() {
	localWrite16(arg16(3), _vm->_anim->getFrameNumber());
}

void ScriptInterpreter::sfGetAnimStatus() {
	int16 status = _vm->_anim->getStatus();
	if (status == 0 || status == 1) {
		// TODO mov screenFlag01, 0
	}
	localWrite16(arg16(3), status);
}

void ScriptInterpreter::sfStartShakeScreen() {
	_vm->_screen->startShakeScreen(arg16(3));
}

void ScriptInterpreter::sfStopShakeScreen() {
	_vm->_screen->stopShakeScreen();
}

void ScriptInterpreter::sfStartSequence() {
	int16 sequenceResIndex = arg16(3);
	debug(1, "ScriptInterpreter::sfStartSequence(%d)", sequenceResIndex);

	if (sequenceResIndex >= 0) {
		//_vm->_arc->dump(sequenceResIndex, "music");	// DEBUG: Dump music so we know what's in there

		_vm->_music->playSequence(sequenceResIndex);
	}
}

void ScriptInterpreter::sfEndSequence() {
	_vm->_music->stopSequence();
}

void ScriptInterpreter::sfSetSequenceVolume() {
	// TODO
	//debug("ScriptInterpreter::sfSetSequenceVolume");
}

void ScriptInterpreter::sfPlayPositionalSound() {
	_vm->_sound->playSoundAtPos(arg16(3), arg16(9), arg16(7));
}

void ScriptInterpreter::sfPlaySound2() {
	_vm->_sound->playSound(arg16(3), arg16(5), arg16(7));
}

void ScriptInterpreter::sfClearScreen() {
	// TODO: Occurs on every scene change, but seems unneeded
	//debug("ScriptInterpreter::sfClearScreen");
}

void ScriptInterpreter::sfHandleInput() {
	int16 varOfs = arg16(3);
	int16 keyCode = 0;
	if (_vm->_rightButtonDown) {
		keyCode = 1;
	} else {
		// Convert keyboard scancode to IBM PC scancode.
		// Only scancodes known to be used (so far) are converted.
		switch (_vm->_keyState.keycode) {
		case Common::KEYCODE_ESCAPE:
			keyCode = 1;
			break;
		case Common::KEYCODE_F10:
			keyCode = 68;
			break;
		default:
			break;
		}
	}
	localWrite16(varOfs, keyCode);
}

void ScriptInterpreter::sfRunOptionsScreen() {
	_vm->showMenu(kMenuIdMain);
}

/**
 * NOTE: The opcodes sfPrecacheSprites, sfPrecacheSounds1, sfPrecacheSounds2 and
 * sfDeletePrecachedFiles were used by the original engine to handle precaching
 * of data so the game doesn't stall while playing (due to the slow speed of
 * CD-Drives back then). This is not needed in ScummVM since all supported
 * systems are fast enough to load data in-game.
 */

void ScriptInterpreter::sfPrecacheSprites() {
	// See note above
}

void ScriptInterpreter::sfPrecacheSounds1() {
	// See note above
}

void ScriptInterpreter::sfDeletePrecachedFiles() {
	// See note above
}

void ScriptInterpreter::sfPrecacheSounds2() {
	// See note above
}

void ScriptInterpreter::sfRestoreStackPtr() {
	_regs.sp = _savedSp;
}

void ScriptInterpreter::sfSaveStackPtr() {
	_savedSp = _regs.sp;
}

void ScriptInterpreter::sfPlayMovie() {
	CursorMan.showMouse(false);
	_vm->_moviePlayer->playMovie(arg16(3));
	CursorMan.showMouse(true);
}

} // End of namespace Toltecs
