/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/engines/m4/script.cpp $
 * $Id: script.cpp 48172 2010-03-07 05:06:58Z dreammaster $
 *
 */

#include "common/system.h"

#include "m4/m4.h"
#include "m4/script.h"
#include "m4/resource.h"

namespace M4 {

enum OpcodeType {
	opRet = 0,
	opCall,
	opCallKernel,
	opPush,
	opPush0,
	opPush1,
	opPushNeg1,
	opPop,
	opMov,
	opAdd,
	opSub,
	opInc,
	opDec,
	opCmp,
	opJmp,
	opJmpByTable,
	opJz,
	opJnz,
	opJe,
	opJne,
	opJl,
	opJle,
	opJg,
	opJge,
	opXor,
	opShl,
	opShr,

	opDebug,

	opInvalid
};

const char *opcodeNames[] = {
	"opRet",
	"opCall",
	"opCallKernel",
	"opPush",
	"opPush0",
	"opPush1",
	"opPushNeg1",
	"opPop",
	"opMov",
	"opAdd",
	"opSub",
	"opInc",
	"opDec",
	"opCmp",
	"opJmp",
	"opJmpByTable",
	"opJz",
	"opJnz",
	"opJe",
	"opJne",
	"opJl",
	"opJle",
	"opJg",
	"opJge",
	"opXor",
	"opShl",
	"opShr",
	"opDebug",
	"opInvalid"
};

StringTable::StringTable() : _stringsData(NULL) {
}

StringTable::~StringTable() {
	delete[] _stringsData;
}

void StringTable::load(Common::File *fd) {
	int stringSize = fd->readUint32LE();
	int stringCount = fd->readUint32LE();
	_stringsData = new char[stringSize];
	fd->read(_stringsData, stringSize);
	char *stringPtr = _stringsData;
	for (int i = 0; i < stringCount; i++) {
		_strings.push_back((const char*)stringPtr);
		stringPtr += strlen(stringPtr) + 1;
	}
}

SeriesStreamBreakList::~SeriesStreamBreakList() {
}

void SeriesStreamBreakList::load(Common::File *fd) {
	uint32 count = fd->readUint32LE();
	printf("SeriesStreamBreakList::load() count = %d\n", count);
	for (uint32 i = 0; i < count; i++) {
		SeriesStreamBreakItem *item = new SeriesStreamBreakItem();
		item->frameNum = fd->readUint32LE();
		item->digiName = _inter->loadGlobalString(fd);
		item->digiChannel = fd->readUint32LE();
		item->digiVolume = fd->readUint32LE();
		item->trigger = fd->readUint32LE();
		item->flags = fd->readUint32LE();
		item->variable.type = kGameVar;
		item->variable.value = fd->readUint32LE();
		item->value = fd->readUint32LE();
		_items.push_back(item);

		printf("%02d: frameNum = %d; digiName = %s; digiChannel = %d; digiVolume = %d; trigger = %d; flags = %d; variable = %d; value = %d\n",
			i, item->frameNum, item->digiName,	item->digiChannel, item->digiVolume, item->trigger, item->flags, item->variable.value, item->value);

	}
}

SaidArray::~SaidArray() {
}

void SaidArray::load(Common::File *fd) {
	uint32 count = fd->readUint32LE();
	printf("SaidArray::load() count = %d\n", count);
	for (uint32 i = 0; i < count; i++) {
		SaidArrayItem *item = new SaidArrayItem();
		item->itemName = _inter->loadGlobalString(fd);
		item->digiNameLook = _inter->loadGlobalString(fd);
		item->digiNameTake = _inter->loadGlobalString(fd);
		item->digiNameGear = _inter->loadGlobalString(fd);
		_items.push_back(item);

		printf("itemName = %s; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n",
			item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear);

	}
}

ParserArray::~ParserArray() {
}

void ParserArray::load(Common::File *fd) {
	uint32 count = fd->readUint32LE();
	printf("ParserArray::load() count = %d\n", count);
	for (uint32 i = 0; i < count; i++) {
		ParserArrayItem *item = new ParserArrayItem();
		item->w0 = _inter->loadGlobalString(fd);
		item->w1 = _inter->loadGlobalString(fd);
		item->trigger = fd->readUint32LE();
		item->testVariable.type = kGameVar;
		item->testVariable.value = fd->readUint32LE();
		item->testValue = fd->readUint32LE();
		item->variable.type = kGameVar;
		item->variable.value = fd->readUint32LE();
		item->value = fd->readUint32LE();
		_items.push_back(item);

		printf("w0 = %s; w1 = %s; trigger = %d; testVariable = %d; testValue = %d; variable = %d; value = %d\n",
			item->w0, item->w1, item->trigger, item->testVariable.value, item->testValue, item->variable.value, item->value);

	}
}

ScriptFunction::ScriptFunction(ScriptInterpreter *inter) : _inter(inter) {
}

ScriptFunction::~ScriptFunction() {
	delete _code;
}

void ScriptFunction::load(Common::File *fd) {
	printf("ScriptFunction::load()\n");
	uint32 size = fd->readUint32LE();
	printf("ScriptFunction::load() size = %d\n", size);
	_code = fd->readStream(size);
}

void ScriptFunction::jumpAbsolute(uint32 ofs) {
	_code->seek(ofs);
}

void ScriptFunction::jumpRelative(int32 ofs) {
	_code->seek(ofs, SEEK_CUR);
}

byte ScriptFunction::readByte() {
	return _code->readByte();
}

uint32 ScriptFunction::readUint32() {
	return _code->readUint32LE();
}


ScriptInterpreter::ScriptInterpreter(MadsM4Engine *vm) : _scriptFile(NULL), _vm(vm) {
	initScriptKernel();
	_dataCache = new ScriptDataCache(this);
	_runningFunction = NULL;
}

ScriptInterpreter::~ScriptInterpreter() {
	close();
	delete _dataCache;
}

void ScriptInterpreter::open(const char *filename) {
	if (_scriptFile)
		close();
	_scriptFile = new Common::File();
	_scriptFile->open(filename);
	if (!_scriptFile->isOpen())
		error("ScriptInterpreter::open() Error opening %s", filename);

	_scriptFile->readUint32LE(); // skip magic for now
	uint32 version = _scriptFile->readUint32LE();
	if (version != kScriptFileVersion) {
		error("ScriptInterpreter::open() DAT file version mismatch; requested %li, got %i", kScriptFileVersion, version);
	}

	int functionCount = _scriptFile->readUint32LE();
	printf("functionCount = %d\n", functionCount);
	for (int i = 0; i < functionCount; i++) {
		uint32 offset = _scriptFile->readUint32LE();
		printf("func(%d) offset = %08X\n", i, offset);
		uint32 len = _scriptFile->readUint32LE();
		if (len > 0) {
			char *funcName = new char[len + 1];
			_scriptFile->read(funcName, len);
			funcName[len] = '\0';
			printf("func(%d) name = %s\n", i, funcName);
			_functionNames[Common::String(funcName)] = _functions.size();
			// DEBUG
			_scriptFunctionNames.push_back(Common::String(funcName));
			delete[] funcName;
		}
		_functions.push_back(new ScriptFunctionEntry(offset));
	}

	int dataCount = _scriptFile->readUint32LE();
	printf("dataCount = %d\n", dataCount);
	for (int i = 0; i < dataCount; i++) {
		uint32 offset = _scriptFile->readUint32LE();
		ScriptDataType type = (ScriptDataType)_scriptFile->readUint32LE();
		printf("data(%d) offset = %08X; type = %d\n", i, offset, type);
		_data.push_back(new ScriptDataEntry(offset, type));
	}

	_globalVarCount = _scriptFile->readUint32LE();
	printf("_globalVarCount = %d\n", _globalVarCount);

	uint32 stringOfs = _scriptFile->readUint32LE();
	_scriptFile->seek(stringOfs);
	_constStrings.load(_scriptFile);

	for (int i = 0; i < ARRAYSIZE(_globalVars); i++) {
		_globalVars[i].type = kInteger;
		_globalVars[i].value = 0;
	}

	memset(_logicGlobals, 0, sizeof(_logicGlobals));

	memset(_registers, 0, sizeof(_registers));
	memset(_stack, 0, sizeof(_stack));
	_stackPtr = 0;

}

void ScriptInterpreter::close() {
	delete _scriptFile;
}

void ScriptInterpreter::initScriptKernel() {

#include "m4/scripttab.h"

	_kernelFunctions = kernelFunctions;
	_kernelFunctionsMax = ARRAYSIZE(kernelFunctions) + 1;

	_kernelVars = kernelVars;
	_kernelVarsMax = ARRAYSIZE(kernelVars) + 1;

}


ScriptFunction *ScriptInterpreter::loadFunction(uint32 index) {
	//GONE WHILE DEBUGGING assert(index < _functions.size());
	if (index >= _functions.size()) return NULL;
	ScriptFunction *scriptFunction;
	scriptFunction = _functions[index]->func;
	if (!scriptFunction) {
		scriptFunction = new ScriptFunction(this);
		_scriptFile->seek(_functions[index]->offset);
		scriptFunction->load(_scriptFile);
		_functions[index]->func = scriptFunction;
	}
	return scriptFunction;
}

ScriptFunction *ScriptInterpreter::loadFunction(const Common::String &name) {
	FunctionNameMap::iterator iter = _functionNames.find(name);
	if (iter == _functionNames.end()) {
		printf("ScriptInterpreter::loadFunction() Function '%s' not found!\n", name.c_str());
		return NULL;
	}
	uint32 funcIndex = (*iter)._value;
	printf("ScriptInterpreter::loadFunction() index('%s') = %d\n", name.c_str(), funcIndex);
	return loadFunction(funcIndex);
}

void ScriptInterpreter::unloadFunctions() {
	for (uint32 i = 0; i < _functions.size(); i++) {
		if (_functions[i]->func) {
			delete _functions[i]->func;
			_functions[i]->func = NULL;
		}
	}
}

int ScriptInterpreter::runFunction(ScriptFunction *scriptFunction) {
	bool done = false;

	int oldLocalStackPtr = _localStackPtr;
	ScriptFunction *oldRunningFunction = _runningFunction;

	// TODO: Also initialize _localStackPtr

	_runningFunction = scriptFunction;
	_runningFunction->jumpAbsolute(0);
	while (!done) {
		byte opcode = _runningFunction->readByte();
		done = !execOpcode(opcode);
		fflush(stdout);
	}

	_localStackPtr = oldLocalStackPtr;
	_runningFunction = oldRunningFunction;

	return 0;
}

void ScriptInterpreter::push(const ScriptValue &value) {
	if (_stackPtr == ARRAYSIZE(_stack))
		error("ScriptInterpreter::push() Stack overflow");
	_stack[_stackPtr++] = value;
}

void ScriptInterpreter::pop(ScriptValue &value) {
	if (_stackPtr == 0)
		error("ScriptInterpreter::pop() Stack underflow");
	value = _stack[_stackPtr--];
}

void ScriptInterpreter::dumpStack() {
	printf("ScriptInterpreter::dumpStack()\n");
	for (int i = 0; i < _stackPtr; i++) {
		printf("%03d. type = %02d; value = %d\n", i, _stack[i].type, _stack[i].value);
	}
}

void ScriptInterpreter::dumpRegisters() {
	printf("ScriptInterpreter::dumpRegisters()\n");
	for (int i = 0; i < ARRAYSIZE(_registers); i++) {
		printf("%03d. type = %02d; value = %d\n", i, _registers[i].type, _registers[i].value);
	}
}

void ScriptInterpreter::dumpGlobalVars() {
	printf("ScriptInterpreter::dumpGlobalVars()\n");
	for (int i = 0; i < ARRAYSIZE(_globalVars); i++) {
		if (_globalVars[i].type != -1)
			printf("%03d. type = %02d; value = %d\n", i, _globalVars[i].type, _globalVars[i].value);
	}
}

int ScriptInterpreter::toInteger(const ScriptValue &value) {

	switch (value.type) {

	case kInteger:
		return value.value;

	default:
		printf("ScriptInterpreter::toInteger() Invalid type %d!\n", value.type);
		return 0;

	}

}

const char *ScriptInterpreter::toString(const ScriptValue &value) {

	switch (value.type) {

	case kInteger:
		return NULL;

	case kConstString:
		return _constStrings[value.value];

	default:
		printf("ScriptInterpreter::toString() Invalid type %d!\n", value.type);
		return NULL;

	}

}

const char *ScriptInterpreter::loadGlobalString(Common::File *fd) {
	uint32 index = fd->readUint32LE();
	if (index != 0xFFFFFFFF)
		return getGlobalString(index);
	else
		return NULL;
}

void ScriptInterpreter::test() {
}

void ScriptInterpreter::loadValue(ScriptValue &value) {

	value.type = (ScriptValueType)_runningFunction->readByte();

	switch (value.type) {

	case kGameVar:
	case kInteger:
	case kConstString:
	case kDataRef:
	case kLogicVar:
	case kLogicVarRef:
	case kKernelVar:
		value.value = _runningFunction->readUint32();
		break;

	case kRegister:
		value.value = _runningFunction->readByte();
		break;

	default:
		printf("ScriptInterpreter::loadValue() Invalid value type %d!\n", value.type);

	}

}

void ScriptInterpreter::copyValue(ScriptValue &destValue, ScriptValue &sourceValue) {

	if (sourceValue.type == -1) {
		printf("ScriptInterpreter::copyValue() Trying to read uninitialized value!\n");
	}

	switch (destValue.type) {

	case kGameVar:
		_globalVars[destValue.value] = sourceValue;
		break;

	case kRegister:
		_registers[destValue.value] = sourceValue;
		break;

	case kLogicVar:
		// TODO: Move to own method
		if (sourceValue.type == kInteger) {
			_logicGlobals[destValue.value] = sourceValue.value;
		} else {
			printf("ScriptInterpreter::copyValue() Invalid source value type %d!\n", sourceValue.type);
		}
		break;

	case kKernelVar:
		setKernelVar(destValue.value, sourceValue);
		break;

	default:
		printf("ScriptInterpreter::copyValue() Invalid dest value type %d!\n", destValue.type);

	}

}

void ScriptInterpreter::derefValue(ScriptValue &value) {

	switch (value.type) {

	case kGameVar:
		value = _globalVars[value.value];
		break;

	case kInteger:
	case kConstString:
	case kDataRef:
	case kLogicVarRef:
		// These need no dereferencing
		break;

	case kRegister:
		value = _registers[value.value];
		break;

	case kLogicVar:
		// TODO: Move to own method
		value = _logicGlobals[value.value];
		break;

	case kKernelVar:
		getKernelVar(value.value, value);
		break;

	default:
		printf("ScriptInterpreter::derefValue() Invalid value type %d!\n", value.type);

	}

}

void ScriptInterpreter::callKernelFunction(uint32 index) {

	printf("ScriptInterpreter::callKernelFunction() index = %d\n", index);

	if (index > _kernelFunctionsMax) {
		printf("ScriptInterpreter::callKernelFunction() Invalid kernel functionindex (%d)\n", index);
		return;
	}

	printf("ScriptInterpreter::callKernelFunction() name = %s\n", _kernelFunctions[index].desc);

	int args = (this->*(_kernelFunctions[index].proc))();
	// Now remove values from the stack if the function used any
	if (args > 4)
		_stackPtr -= args - 4;

	printf("-------------\n");

}

ScriptValue ScriptInterpreter::getArg(uint32 index) {
	if (index < 4) {
		return _registers[index];
	} else {
		index -= 4;
		return _stack[_stackPtr - index - 1];
	}
}

void ScriptInterpreter::dumpArgs(uint32 count) {
	printf("ScriptInterpreter::dumpArgs() ");
	for (uint32 i = 0; i < count; i++) {
		ScriptValue argValue = getArg(i);
		if (argValue.type == kConstString) {
			printf("'%s'", toString(argValue));
		} else {
			printf("%d", argValue.value);
		}
		if (i + 1 < count)
			printf(", ");
	}
	printf("\n");
}

void ScriptInterpreter::callFunction(uint32 index) {
	// NOTE: This is a temporary hack for script functions not yet in the m4.dat
	if (index == 0xFFFFFFFF)
		return;
	printf("ScriptInterpreter::callFunction() index = %d [%s]\n", index, _scriptFunctionNames[index].c_str());
	fflush(stdout);
	ScriptFunction *subFunction = loadFunction(index);
	if (!subFunction) {
		// This *should* never happen since the linker checks this
		printf("ScriptInterpreter::callFunction() Function %d could not be loaded!\n", index);
		return;
	}
	runFunction(subFunction);
}

bool ScriptInterpreter::execOpcode(byte opcode) {

	printf("opcode = %d (%s)\n", opcode, opcodeNames[opcode]);

	ScriptValue value1, value2, value3;
	uint32 temp;

	/* TODO: Put all opcodes into separate functions and into an array
			 (but only after all needed opcodes are known and frozen)
	*/

	switch (opcode) {

	case opRet:
		return false;

	case opPush:
		loadValue(value1);
		derefValue(value1);
		push(value1);
		return true;

	case opPush0:
		push(ScriptValue(0));
		return true;

	case opPush1:
		push(ScriptValue(1));
		return true;

	case opPushNeg1:
		push(ScriptValue(-1));
		return true;

	case opPop:
		loadValue(value1);
		pop(value2);
		copyValue(value1, value2);
		return true;

	case opMov:
		loadValue(value1);
		loadValue(value2);
		derefValue(value2);
		copyValue(value1, value2);
		return true;

	// Possibly join all jump variants into one opcode

	case opJmp:
		temp = _runningFunction->readUint32();
		printf("-> ofs = %08X\n", temp);
		_runningFunction->jumpAbsolute(temp);
		return true;

	case opJl:
		temp = _runningFunction->readUint32();
		if (_cmpFlags < 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJle:
		temp = _runningFunction->readUint32();
		if (_cmpFlags <= 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJg:
		temp = _runningFunction->readUint32();
		if (_cmpFlags > 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJge:
		temp = _runningFunction->readUint32();
		if (_cmpFlags >= 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJz:
		temp = _runningFunction->readUint32();
		if (_cmpFlags == 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJnz:
		temp = _runningFunction->readUint32();
		if (_cmpFlags != 0) {
			printf("-> ofs = %08X\n", temp);
			_runningFunction->jumpAbsolute(temp);
		}
		return true;

	case opJmpByTable:
		temp = _runningFunction->readUint32();
		printf("-> index = %d\n", _registers[0].value);
		_runningFunction->jumpRelative(_registers[0].value * 4);
		temp = _runningFunction->readUint32();
		printf("-> ofs = %08X\n", temp);
		_runningFunction->jumpAbsolute(temp);
		return true;

	case opCmp:
		loadValue(value1);
		loadValue(value2);
		derefValue(value1);
		derefValue(value2);
		if (value1.type != kInteger || value2.type != kInteger)
			warning("ScriptInterpreter::execOpcode() Trying to compare non-integer values (%d, %d, line %d)", value1.type, value2.type, _lineNum);
		_cmpFlags = value1.value - value2.value;
		printf("-> cmp %d, %d\n", value1.value, value2.value);
		printf("-> _cmpFlags  = %d\n", _cmpFlags);
		return true;

	case opCall:
		temp = _runningFunction->readUint32();
		callFunction(temp);
		return true;

	case opCallKernel:
		temp = _runningFunction->readUint32();
		callKernelFunction(temp);
		return true;

	case opInc:
		loadValue(value1);
		value2 = value1;
		derefValue(value2);
		value2.value++;
		copyValue(value1, value2);
		return true;

	case opDec:
		loadValue(value1);
		value2 = value1;
		derefValue(value2);
		value2.value--;
		copyValue(value1, value2);
		return true;

	case opAdd:
		loadValue(value1);
		value3 = value1;
		loadValue(value2);
		derefValue(value3);
		derefValue(value2);
		value3.value += value2.value;
		copyValue(value1, value3);
		return true;

	case opSub:
		loadValue(value1);
		value3 = value1;
		loadValue(value2);
		derefValue(value3);
		derefValue(value2);
		value3.value -= value2.value;
		copyValue(value1, value3);
		return true;

	case opDebug:
		_lineNum = (int)_runningFunction->readUint32();
		return true;

	default:
		printf("Invalid opcode %d!\n", opcode);
		return false;

	}

}

// Kernel functions

#define STRING(arg) (toString(getArg(arg)))
#define INTEGER(arg) (toInteger(getArg(arg)))
#define DATA(arg, T) (toData<T>(getArg(arg)))
#define RETURN(value) (_registers[0] = (value))

int ScriptInterpreter::o1_handleStreamBreak() {
	return 0;
}

int ScriptInterpreter::o1_handlePlayBreak() {
	return 0;
}

int ScriptInterpreter::o1_dispatchTriggerOnSoundState() {
	return 0;
}

int ScriptInterpreter::o1_getTicks() {
	return 0;
}

int ScriptInterpreter::o1_setSoundVolume() {
	return 0;
}

int ScriptInterpreter::o1_getSoundStatus() {
	return 0;
}

int ScriptInterpreter::o1_getSoundDuration() {
	return 0;
}

int ScriptInterpreter::o1_setSeriesFrameRate() {
	return 0;
}

int ScriptInterpreter::o1_terminateMachine() {
	return 0;
}

int ScriptInterpreter::o1_sendWoodScriptMessage() {
	return 0;
}

int ScriptInterpreter::o1_runConversation() {
	return 0;
}

int ScriptInterpreter::o1_exportConversationValue() {
	return 0;
}

int ScriptInterpreter::o1_exportConversationPointer() {
	return 0;
}

int ScriptInterpreter::o1_playBreakSeries() {
	return 0;
}

int ScriptInterpreter::o1_hideWalker() {
	return 0;
}

int ScriptInterpreter::o1_showWalker() {
	return 0;
}

int ScriptInterpreter::o1_walk() {
	return 0;
}

int ScriptInterpreter::o1_overrideCrunchTime() {
	return 0;
}

int ScriptInterpreter::o1_addBlockingRect() {
	return 0;
}

int ScriptInterpreter::o1_setPlayerCommandsAllowed() {
	return 0;
}

int ScriptInterpreter::o1_getPlayerCommandsAllowed() {
	return 0;
}

int ScriptInterpreter::o1_setPlayerFacingAngle() {
	return 0;
}

int ScriptInterpreter::o1_disablePlayerFadeToBlack() {
	return 0;
}

int ScriptInterpreter::o1_enablePlayer() {
	return 0;
}

int ScriptInterpreter::o1_disablePlayer() {
	return 0;
}

int ScriptInterpreter::o1_freshenSentence() {
	return 0;
}

int ScriptInterpreter::o1_playerGiveItem() {
	return 0;
}

int ScriptInterpreter::o1_moveObject() {
	return 0;
}

int ScriptInterpreter::o1_setStopSoundsBetweenRooms() {
	return 0;
}

int ScriptInterpreter::o1_backupPalette() {
	return 0;
}

int ScriptInterpreter::o1_unloadWilburWalker() {
	return 0;
}

int ScriptInterpreter::o1_wilburTalk() {
	return 0;
}

int ScriptInterpreter::o1_wilburFinishedTalking() {
	return 0;
}

int ScriptInterpreter::o1_preloadSound() {
	const char *name = STRING(0);
	int room = INTEGER(1);
	printf("name = %s; room = %d\n", name, room);
	return 2;
}

int ScriptInterpreter::o1_unloadSound() {
	const char *name = STRING(0);
	int room = INTEGER(1);
	printf("name = %s; room = %d\n", name, room);
	return 2;
}

int ScriptInterpreter::o1_playSound() {
	const char *name = STRING(0);
	int channel = INTEGER(1);
	int volume = INTEGER(2);
	int trigger = INTEGER(3);
	int room = INTEGER(4);
	printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n",
		name, channel, volume, trigger, room);

	Common::String soundName = Common::String(name) + ".raw";
	_vm->_sound->playVoice(soundName.c_str(), 100);

	// HACK until fixed
	_vm->_kernel->sendTrigger(trigger);

	return 5;
}

int ScriptInterpreter::o1_playLoopingSound() {
	const char *name = STRING(0);
	int channel = INTEGER(1);
	int volume = INTEGER(2);
	int trigger = INTEGER(3);
	int room = INTEGER(4);
	printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n",
		name, channel, volume, trigger, room);

	// HACK until fixed
	_vm->_kernel->sendTrigger(trigger);

	return 5;
}

int ScriptInterpreter::o1_stopSound() {
	int channel = INTEGER(0);
	printf("channel = %d\n", channel);
	return 1;
}

int ScriptInterpreter::o1_fadeSetStart() {
	// skip arg 0: palette ptr
	int percent = INTEGER(1);
	printf("percent = %d\n", percent);
	return 2;
}

int ScriptInterpreter::o1_fadeInit() {
	// skip arg 0: palette ptr
	int first = INTEGER(1);
	int last = INTEGER(2);
	int percent = INTEGER(3);
	int ticks = INTEGER(4);
	int trigger = INTEGER(5);
	printf("first = %d; last = %d; percent = %d; ticks = %d; trigger = %d\n",
		first, last, percent, ticks, trigger);

	// HACK until palette fading is implemented
	_vm->_kernel->sendTrigger(trigger);

	return 6;
}

int ScriptInterpreter::o1_fadeToBlack() {
	return 0;
}

int ScriptInterpreter::o1_initPaletteCycle() {
	int first = INTEGER(0);
	int last = INTEGER(1);
	int delay = INTEGER(2);
	int ticks = INTEGER(3);
	int trigger = INTEGER(4);
	printf("first = %d; last = %d; delay = %d; ticks = %d; trigger = %d\n",
		first, last, delay, ticks, trigger);

	// HACK until palette cycling is implemented
	_vm->_kernel->sendTrigger(trigger);

	return 5;
}

int ScriptInterpreter::o1_stopPaletteCycle() {
	return 0;
}

int ScriptInterpreter::o1_hasPlayerSaid() {
	const char *words[3];
	for (int i = 0; i < 3; i++)
		words[i] = STRING(i);
	printf("'%s', '%s', '%s'\n", words[0], words[1], words[2]);

	int result = _vm->_player->said(words[0], words[1], words[2]);

	printf("   -> '%d'\n", result);
	fflush(stdout);

	RETURN(result);
	return 3;
}

int ScriptInterpreter::o1_hasPlayerSaidAny() {
	const char *words[10];
	for (int i = 0; i < 10; i++)
		words[i] = STRING(i);

	printf("'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'\n",
		words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);

	int result = _vm->_player->saidAny(words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);
	printf("   -> '%d'\n", result);
	fflush(stdout);

	RETURN(result);
	return 10;
}

int ScriptInterpreter::o1_updatePlayerInfo() {
	// skip arg 0: player info struct
	return 1;
}

int ScriptInterpreter::o1_playerHotspotWalkOverride() {
	int x1 = INTEGER(0);
	int y1 = INTEGER(1);
	int x2 = INTEGER(2);
	int y2 = INTEGER(3);
	printf("(%d, %d); (%d, %d)\n", x1, y1, x2, y2);
	return 4;
}

int ScriptInterpreter::o1_playerHasItem() {
	const char *name = STRING(0);
	printf("item = '%s'\n", name);
	// TODO
	RETURN(0);
	return 1;
}

int ScriptInterpreter::o1_setWalkerLocation() {
	// skip arg 0: walker
	int x = INTEGER(1);
	int y = INTEGER(2);
	printf("x = %d; y = %d\n", x, y);
	return 3;
}

int ScriptInterpreter::o1_setWalkerFacing() {
	// skip arg 0: walker
	int facing = INTEGER(1);
	printf("facing = %d\n", facing);
	return 2;
}

int ScriptInterpreter::o1_setHotspot() {
	// skip arg 0: hotspot list
	const char *name = STRING(1);
	int value = INTEGER(2);
	printf("name = '%s' -> %d\n", name, value);

	_vm->_scene->getSceneResources().hotspots->setActive(name, (value != 0));

	return 2;
}

int ScriptInterpreter::o1_loadConversation() {
	const char *name = STRING(0);
	//int trigger = INTEGER(1);
	//int flag = INTEGER(2);

	// TODO; just to show something
	_m4Vm->_converse->startConversation(name);

	return 3;
}

int ScriptInterpreter::o1_playSeries() {
	const char *name = STRING(0);
	int layer = INTEGER(1);
	int flags = INTEGER(2);
	int trigger = INTEGER(3);
	int frameRate = INTEGER(4);
	int loopCount = INTEGER(5);
	int scale = INTEGER(6);
	int x = INTEGER(7);
	int y = INTEGER(8);
	int firstFrame = INTEGER(9);
	int lastFrame = INTEGER(10);

	printf("name = %s; layer = %04X; flags = %08X; trigger = %d; frameRate = %d; loopCount = %d; scale = %d; x = %d; y = %d: firstFrame = %d; lastFrame = %d\n",
		name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame);
		fflush(stdout);

	// TODO: Return the machine to the script
	_vm->_ws->playSeries(name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame);

	return 11;
}

int ScriptInterpreter::o1_showSeries() {
	const char *name = STRING(0);
	int layer = INTEGER(1);
	int flags = INTEGER(2);
	int trigger = INTEGER(3);
	int duration = INTEGER(4);
	int frameIndex = INTEGER(5);
	int scale = INTEGER(6);
	int x = INTEGER(7);
	int y = INTEGER(8);

	printf("name = %s; layer = %04X; flags = %08X; trigger = %d; duration = %d; frameIndex = %d; scale = %d; x = %d; y = %d\n",
		name, layer, flags, trigger, duration, frameIndex, scale, x, y);
		fflush(stdout);

	// TODO: Return the machine to the script
	_vm->_ws->showSeries(name, layer, flags, trigger, duration, frameIndex, scale, x, y);

	return 9;
}

int ScriptInterpreter::o1_loadSeries() {
	const char *name = STRING(0);
	int hash = INTEGER(1);
	// skip arg 3: palette ptr

	printf("name = %s; hash = %d\n", name, hash);
	fflush(stdout);

	int result = _vm->_ws->loadSeries(name, hash, NULL);

	RETURN(result);
	return 3;
}

int ScriptInterpreter::o1_unloadSeries() {
	return 0;
}

int ScriptInterpreter::o1_preloadBreakSeries() {
	//const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
	return 1;
}

int ScriptInterpreter::o1_unloadBreakSeries() {
	//const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
	return 1;
}

int ScriptInterpreter::o1_startBreakSeries() {
	//const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList);
	return 1;
}

int ScriptInterpreter::o1_globalTriggerProc() {
	int value1 = INTEGER(0);
	int value2 = INTEGER(1);
	int value3 = INTEGER(2);
	printf("%d; %d; %d\n", value1, value2, value3);
	return 3;
}

int ScriptInterpreter::o1_triggerTimerProc() {
	int value1 = INTEGER(0);
	int value2 = INTEGER(1);
	int value3 = INTEGER(2);
	printf("%d; %d; %d\n", value1, value2, value3);
	return 3;
}

int ScriptInterpreter::o1_dispatchTrigger() {
	int trigger = INTEGER(0);
	printf("trigger = %d\n", trigger);

	_vm->_kernel->sendTrigger(trigger);
	//g_system->delayMillis(5000);

	return 1;
}

int ScriptInterpreter::o1_getRangedRandomValue() {
	int minValue = INTEGER(0);
	int maxValue = INTEGER(1);
	RETURN(_vm->imath_ranged_rand(minValue, maxValue));
	return 2;
}

int ScriptInterpreter::o1_wilburSaid() {
	const SaidArray& saidArray = DATA(0, SaidArray);

	int result = 0;

	// NOTE: The "Common::String soundName" stuff is just temporary until playVoice is fixed.

	for (int i = 0; i < saidArray.size(); i++) {
		SaidArrayItem *item = saidArray[i];

		if (_vm->_player->said("LOOK AT", item->itemName) && item->digiNameLook) {
			printf("  -> LOOK AT: '%s'\n", item->digiNameLook);
			Common::String soundName = Common::String(item->digiNameLook) + ".raw";
			_vm->_sound->playVoice(soundName.c_str(), 100);
			result = 1;
			break;
		}

		if (_vm->_player->said("TAKE", item->itemName) && item->digiNameTake) {
			printf("  -> TAKE: '%s'\n", item->digiNameTake);
			Common::String soundName = Common::String(item->digiNameTake) + ".raw";
			_vm->_sound->playVoice(soundName.c_str(), 100);
			result = 1;
			break;
		}

		if (_vm->_player->said("GEAR", item->itemName) && item->digiNameGear) {
			printf("  -> GEAR: '%s'\n", item->digiNameGear);
			Common::String soundName = Common::String(item->digiNameGear) + ".raw";
			_vm->_sound->playVoice(soundName.c_str(), 100);
			result = 1;
			break;
		}

		/*
		printf("##### itemName = '%s'; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n",
			item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear);
		*/
	}
	printf("   -> '%d'\n", result);
	fflush(stdout);

	RETURN(result);
	return 1;
}

int ScriptInterpreter::o1_wilburParse() {
	//const ParserArray& parserArray = DATA(0, ParserArray);
	RETURN(0);
	return 1;
}

int ScriptInterpreter::o1_wilburSpeech() {
	const char *name = STRING(0);
	int trigger = INTEGER(1);
	int room = INTEGER(2);
	int flag = INTEGER(3);
	int volume = INTEGER(4);
	int slot = INTEGER(5);

	printf("%s; %d; %d; %d; %d; %d\n", name, trigger, room, flag, volume, slot);
	fflush(stdout);
	//g_system->delayMillis(5000);

	KernelTriggerType oldTriggerMode = _vm->_kernel->triggerMode;

	// TODO
	Common::String soundName = Common::String(name) + ".raw";
	_vm->_sound->playVoice(soundName.c_str(), 100);

	_vm->_kernel->triggerMode = oldTriggerMode;

	return 6;
}

// Kernel vars

void ScriptInterpreter::getKernelVar(int index, ScriptValue &value) {

	printf("ScriptInterpreter::getKernelVar() index = %d\n", index);

	if (index > _kernelVarsMax) {
		printf("ScriptInterpreter::getKernelVar() Invalid kernel var index %d!\n", index);
		return;
	}

	printf("ScriptInterpreter::getKernelVar() name = %s\n", _kernelVars[index].desc);

	ScriptKernelVariable var = _kernelVars[index].var;

	switch (var) {

	case kKernelTrigger:
		value = _vm->_kernel->trigger;
		break;

	case kKernelTriggerMode:
		value = (int)_vm->_kernel->triggerMode;
		break;

	case kKernelContinueHandlingTrigger:
		value = (int)_vm->_kernel->daemonTriggerAvailable;
		break;

	case kGameVersion:
		// TODO
		value = 0;
		break;

	case kGameLanguage:
		// TODO
		value = 0;
		break;

	case kGameNewRoom:
		// TODO
		value = 0;
		break;

	case kPlayerCommandReady:
		value = (int)_vm->_player->commandReady;
		break;

	default:
		printf("ScriptInterpreter::getKernelVar() Invalid kernel var %d!\n", var);
		//g_system->delayMillis(2000);

	}

}

void ScriptInterpreter::setKernelVar(int index, const ScriptValue &value) {

	printf("ScriptInterpreter::setKernelVar() index = %d\n", index);

	if (index > _kernelVarsMax) {
		printf("ScriptInterpreter::setKernelVar() Invalid kernel var index %d!\n", index);
		return;
	}

	printf("ScriptInterpreter::setKernelVar() name = %s\n", _kernelVars[index].desc);

	ScriptKernelVariable var = _kernelVars[index].var;

	switch (var) {

	case kKernelTrigger:
		_vm->_kernel->trigger = toInteger(value);
		printf("kKernelTrigger -> %d\n", toInteger(value));
		break;

	case kKernelTriggerMode:
		_vm->_kernel->triggerMode = (KernelTriggerType)toInteger(value);
		printf("kKernelTrigger -> %d\n", toInteger(value));
		break;

	case kKernelContinueHandlingTrigger:
		_vm->_kernel->daemonTriggerAvailable = (toInteger(value) != 0);
		printf("kKernelContinueHandlingTrigger -> %d\n", toInteger(value));
		break;

	case kGameNewRoom:
		_vm->_kernel->newRoom = toInteger(value);
		printf("kGameNewRoom -> %d\n", toInteger(value));
		break;

	case kPlayerCommandReady:
		// TODO
		printf("kPlayerCommandReady -> %d\n", toInteger(value));
		break;

	default:
		printf("ScriptInterpreter::setKernelVar() Invalid kernel var %d!\n", var);
		//g_system->delayMillis(2000);

	}

}

} // End of namespace M4
