/* 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.
 *
 */

/*
 * This file is based on WME Lite.
 * http://dead-code.org/redir.php?target=wmelite
 * Copyright (c) 2011 Jan Nedoma
 */

#include "engines/wintermute/base/scriptables/script_value.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/scriptables/script_engine.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "common/memstream.h"

namespace Wintermute {

IMPLEMENT_PERSISTENT(ScScript, false)

//////////////////////////////////////////////////////////////////////////
ScScript::ScScript(BaseGame *inGame, ScEngine *engine) : BaseClass(inGame) {
	_buffer = nullptr;
	_bufferSize = _iP = 0;
	_scriptStream = nullptr;
	_filename = nullptr;
	_currentLine = 0;

	_symbols = nullptr;
	_numSymbols = 0;

	_engine = engine;

	_globals = nullptr;

	_scopeStack = nullptr;
	_callStack  = nullptr;
	_thisStack  = nullptr;
	_stack      = nullptr;

	_operand    = nullptr;
	_reg1       = nullptr;

	_functions = nullptr;
	_numFunctions = 0;

	_methods = nullptr;
	_numMethods = 0;

	_events = nullptr;
	_numEvents = 0;

	_externals = nullptr;
	_numExternals = 0;

	_state = SCRIPT_FINISHED;
	_origState = SCRIPT_FINISHED;

	_waitObject = nullptr;
	_waitTime = 0;
	_waitFrozen = false;
	_waitScript = nullptr;

	_timeSlice = 0;

	_thread = false;
	_methodThread = false;
	_threadEvent = nullptr;

	_freezable = true;
	_owner = nullptr;

	_unbreakable = false;
	_parentScript = nullptr;

	_tracingMode = false;
}


//////////////////////////////////////////////////////////////////////////
ScScript::~ScScript() {
	cleanup();
}

void ScScript::readHeader() {
	uint32 oldPos = _scriptStream->pos();
	_scriptStream->seek(0);
	_header.magic = _scriptStream->readUint32LE();
	_header.version = _scriptStream->readUint32LE();
	_header.codeStart = _scriptStream->readUint32LE();
	_header.funcTable = _scriptStream->readUint32LE();
	_header.symbolTable = _scriptStream->readUint32LE();
	_header.eventTable = _scriptStream->readUint32LE();
	_header.externalsTable = _scriptStream->readUint32LE();
	_header.methodTable = _scriptStream->readUint32LE();
	_scriptStream->seek(oldPos);
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::initScript() {
	if (!_scriptStream) {
		_scriptStream = new Common::MemoryReadStream(_buffer, _bufferSize);
	}
	readHeader();

	if (_header.magic != SCRIPT_MAGIC) {
		_gameRef->LOG(0, "File '%s' is not a valid compiled script", _filename);
		cleanup();
		return STATUS_FAILED;
	}

	if (_header.version > SCRIPT_VERSION) {
		_gameRef->LOG(0, "Script '%s' has a wrong version %d.%d (expected %d.%d)", _filename, _header.version / 256, _header.version % 256, SCRIPT_VERSION / 256, SCRIPT_VERSION % 256);
		cleanup();
		return STATUS_FAILED;
	}

	initTables();

	// init stacks
	_scopeStack = new ScStack(_gameRef);
	_callStack  = new ScStack(_gameRef);
	_thisStack  = new ScStack(_gameRef);
	_stack      = new ScStack(_gameRef);

	_operand    = new ScValue(_gameRef);
	_reg1       = new ScValue(_gameRef);


	// skip to the beginning
	_iP = _header.codeStart;
	_scriptStream->seek(_iP);
	_currentLine = 0;

	// ready to rumble...
	_state = SCRIPT_RUNNING;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::initTables() {
	uint32 origIP = _iP;

	readHeader();
	// load symbol table
	_iP = _header.symbolTable;

	_numSymbols = getDWORD();
	_symbols = new char*[_numSymbols];
	for (uint32 i = 0; i < _numSymbols; i++) {
		uint32 index = getDWORD();
		_symbols[index] = getString();
	}

	// load functions table
	_iP = _header.funcTable;

	_numFunctions = getDWORD();
	_functions = new TFunctionPos[_numFunctions];
	for (uint32 i = 0; i < _numFunctions; i++) {
		_functions[i].pos = getDWORD();
		_functions[i].name = getString();
	}


	// load events table
	_iP = _header.eventTable;

	_numEvents = getDWORD();
	_events = new TEventPos[_numEvents];
	for (uint32 i = 0; i < _numEvents; i++) {
		_events[i].pos = getDWORD();
		_events[i].name = getString();
	}


	// load externals
	if (_header.version >= 0x0101) {
		_iP = _header.externalsTable;

		_numExternals = getDWORD();
		_externals = new TExternalFunction[_numExternals];
		for (uint32 i = 0; i < _numExternals; i++) {
			_externals[i].dll_name = getString();
			_externals[i].name = getString();
			_externals[i].call_type = (TCallType)getDWORD();
			_externals[i].returns = (TExternalType)getDWORD();
			_externals[i].nu_params = getDWORD();
			if (_externals[i].nu_params > 0) {
				_externals[i].params = new TExternalType[_externals[i].nu_params];
				for (int j = 0; j < _externals[i].nu_params; j++) {
					_externals[i].params[j] = (TExternalType)getDWORD();
				}
			}
		}
	}

	// load method table
	_iP = _header.methodTable;

	_numMethods = getDWORD();
	_methods = new TMethodPos[_numMethods];
	for (uint32 i = 0; i < _numMethods; i++) {
		_methods[i].pos = getDWORD();
		_methods[i].name = getString();
	}


	_iP = origIP;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::create(const char *filename, byte *buffer, uint32 size, BaseScriptHolder *owner) {
	cleanup();

	_thread = false;
	_methodThread = false;

	delete[] _threadEvent;
	_threadEvent = nullptr;

	_filename = new char[strlen(filename) + 1];
	if (_filename) {
		strcpy(_filename, filename);
	}

	_buffer = new byte [size];
	if (!_buffer) {
		return STATUS_FAILED;
	}

	memcpy(_buffer, buffer, size);

	_bufferSize = size;

	bool res = initScript();
	if (DID_FAIL(res)) {
		return res;
	}

	// establish global variables table
	_globals = new ScValue(_gameRef);

	_owner = owner;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::createThread(ScScript *original, uint32 initIP, const Common::String &eventName) {
	cleanup();

	_thread = true;
	_methodThread = false;
	_threadEvent = new char[eventName.size() + 1];
	if (_threadEvent) {
		strcpy(_threadEvent, eventName.c_str());
	}

	// copy filename
	_filename = new char[strlen(original->_filename) + 1];
	if (_filename) {
		strcpy(_filename, original->_filename);
	}

	// copy buffer
	_buffer = new byte [original->_bufferSize];
	if (!_buffer) {
		return STATUS_FAILED;
	}

	memcpy(_buffer, original->_buffer, original->_bufferSize);
	_bufferSize = original->_bufferSize;

	// initialize
	bool res = initScript();
	if (DID_FAIL(res)) {
		return res;
	}

	// copy globals
	_globals = original->_globals;

	// skip to the beginning of the event
	_iP = initIP;
	_scriptStream->seek(_iP);

	_timeSlice = original->_timeSlice;
	_freezable = original->_freezable;
	_owner = original->_owner;

	_engine = original->_engine;
	_parentScript = original;

	return STATUS_OK;
}




//////////////////////////////////////////////////////////////////////////
bool ScScript::createMethodThread(ScScript *original, const Common::String &methodName) {
	uint32 ip = original->getMethodPos(methodName);
	if (ip == 0) {
		return STATUS_FAILED;
	}

	cleanup();

	_thread = true;
	_methodThread = true;
	_threadEvent = new char[methodName.size() + 1];
	if (_threadEvent) {
		strcpy(_threadEvent, methodName.c_str());
	}

	// copy filename
	_filename = new char[strlen(original->_filename) + 1];
	if (_filename) {
		strcpy(_filename, original->_filename);
	}

	// copy buffer
	_buffer = new byte [original->_bufferSize];
	if (!_buffer) {
		return STATUS_FAILED;
	}

	memcpy(_buffer, original->_buffer, original->_bufferSize);
	_bufferSize = original->_bufferSize;

	// initialize
	bool res = initScript();
	if (DID_FAIL(res)) {
		return res;
	}

	// copy globals
	_globals = original->_globals;

	// skip to the beginning of the event
	_iP = ip;

	_timeSlice = original->_timeSlice;
	_freezable = original->_freezable;
	_owner = original->_owner;

	_engine = original->_engine;
	_parentScript = original;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
void ScScript::cleanup() {
	if (_buffer) {
		delete[] _buffer;
	}
	_buffer = nullptr;

	if (_filename) {
		delete[] _filename;
	}
	_filename = nullptr;

	if (_symbols) {
		delete[] _symbols;
	}
	_symbols = nullptr;
	_numSymbols = 0;

	if (_globals && !_thread) {
		delete _globals;
	}
	_globals = nullptr;

	delete _scopeStack;
	_scopeStack = nullptr;

	delete _callStack;
	_callStack = nullptr;

	delete _thisStack;
	_thisStack = nullptr;

	delete _stack;
	_stack = nullptr;

	if (_functions) {
		delete[] _functions;
	}
	_functions = nullptr;
	_numFunctions = 0;

	if (_methods) {
		delete[] _methods;
	}
	_methods = nullptr;
	_numMethods = 0;

	if (_events) {
		delete[] _events;
	}
	_events = nullptr;
	_numEvents = 0;


	if (_externals) {
		for (uint32 i = 0; i < _numExternals; i++) {
			if (_externals[i].nu_params > 0) {
				delete[] _externals[i].params;
			}
		}
		delete[] _externals;
	}
	_externals = nullptr;
	_numExternals = 0;

	delete _operand;
	delete _reg1;
	_operand = nullptr;
	_reg1 = nullptr;

	delete[] _threadEvent;
	_threadEvent = nullptr;

	_state = SCRIPT_FINISHED;

	_waitObject = nullptr;
	_waitTime = 0;
	_waitFrozen = false;
	_waitScript = nullptr;

	_parentScript = nullptr; // ref only

	delete _scriptStream;
	_scriptStream = nullptr;
}


//////////////////////////////////////////////////////////////////////////
uint32 ScScript::getDWORD() {
	_scriptStream->seek((int32)_iP);
	uint32 ret = _scriptStream->readUint32LE();
	_iP += sizeof(uint32);
//	assert(oldRet == ret);
	return ret;
}

//////////////////////////////////////////////////////////////////////////
double ScScript::getFloat() {
	_scriptStream->seek((int32)_iP);
	byte buffer[8];
	_scriptStream->read(buffer, 8);

#ifdef SCUMM_BIG_ENDIAN
	// TODO: For lack of a READ_LE_UINT64
	SWAP(buffer[0], buffer[7]);
	SWAP(buffer[1], buffer[6]);
	SWAP(buffer[2], buffer[5]);
	SWAP(buffer[3], buffer[4]);
#endif

	double ret;
	memcpy(&ret, buffer, sizeof(double));
	_iP += 8; // Hardcode the double-size used originally.
	return ret;
}


//////////////////////////////////////////////////////////////////////////
char *ScScript::getString() {
	char *ret = (char *)(_buffer + _iP);
	while (*(char *)(_buffer + _iP) != '\0') {
		_iP++;
	}
	_iP++; // string terminator
	_scriptStream->seek(_iP);

	return ret;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::executeInstruction() {
	bool ret = STATUS_OK;

	uint32 dw;
	const char *str = nullptr;

	//ScValue* op = new ScValue(_gameRef);
	_operand->cleanup();

	ScValue *op1;
	ScValue *op2;

	uint32 inst = getDWORD();
	switch (inst) {

	case II_DEF_VAR:
		_operand->setNULL();
		dw = getDWORD();
		if (_scopeStack->_sP < 0) {
			_globals->setProp(_symbols[dw], _operand);
		} else {
			_scopeStack->getTop()->setProp(_symbols[dw], _operand);
		}

		break;

	case II_DEF_GLOB_VAR:
	case II_DEF_CONST_VAR: {
		dw = getDWORD();
		/*      char *temp = _symbols[dw]; // TODO delete */
		// only create global var if it doesn't exist
		if (!_engine->_globals->propExists(_symbols[dw])) {
			_operand->setNULL();
			_engine->_globals->setProp(_symbols[dw], _operand, false, inst == II_DEF_CONST_VAR);
		}
		break;
	}

	case II_RET:
		if (_scopeStack->_sP >= 0 && _callStack->_sP >= 0) {
			_scopeStack->pop();
			_iP = (uint32)_callStack->pop()->getInt();
		} else {
			if (_thread) {
				_state = SCRIPT_THREAD_FINISHED;
			} else {
				if (_numEvents == 0 && _numMethods == 0) {
					_state = SCRIPT_FINISHED;
				} else {
					_state = SCRIPT_PERSISTENT;
				}
			}
		}

		break;

	case II_RET_EVENT:
		_state = SCRIPT_FINISHED;
		break;


	case II_CALL:
		dw = getDWORD();

		_operand->setInt(_iP);
		_callStack->push(_operand);

		_iP = dw;

		break;

	case II_CALL_BY_EXP: {
		// push var
		// push string
		str = _stack->pop()->getString();
		char *methodName = new char[strlen(str) + 1];
		strcpy(methodName, str);

		ScValue *var = _stack->pop();
		if (var->_type == VAL_VARIABLE_REF) {
			var = var->_valRef;
		}

		bool res = STATUS_FAILED;
		bool triedNative = false;

		// we are already calling this method, try native
		if (_thread && _methodThread && strcmp(methodName, _threadEvent) == 0 && var->_type == VAL_NATIVE && _owner == var->getNative()) {
			triedNative = true;
			res = var->_valNative->scCallMethod(this, _stack, _thisStack, methodName);
		}

		if (DID_FAIL(res)) {
			if (var->isNative() && var->getNative()->canHandleMethod(methodName)) {
				if (!_unbreakable) {
					_waitScript = var->getNative()->invokeMethodThread(methodName);
					if (!_waitScript) {
						_stack->correctParams(0);
						runtimeError("Error invoking method '%s'.", methodName);
						_stack->pushNULL();
					} else {
						_state = SCRIPT_WAITING_SCRIPT;
						_waitScript->copyParameters(_stack);
					}
				} else {
					// can call methods in unbreakable mode
					_stack->correctParams(0);
					runtimeError("Cannot call method '%s'. Ignored.", methodName);
					_stack->pushNULL();
				}
				delete[] methodName;
				break;
			}
			/*
			ScValue* val = var->getProp(MethodName);
			if (val) {
			    dw = GetFuncPos(val->getString());
			    if (dw==0) {
			        TExternalFunction* f = GetExternal(val->getString());
			        if (f) {
			            ExternalCall(_stack, _thisStack, f);
			        }
			        else{
			            // not an internal nor external, try for native function
			            _gameRef->ExternalCall(this, _stack, _thisStack, val->getString());
			        }
			    }
			    else{
			        _operand->setInt(_iP);
			        _callStack->Push(_operand);
			        _iP = dw;
			    }
			}
			*/
			else {
				res = STATUS_FAILED;
				if (var->_type == VAL_NATIVE && !triedNative) {
					res = var->_valNative->scCallMethod(this, _stack, _thisStack, methodName);
				}

				if (DID_FAIL(res)) {
					_stack->correctParams(0);
					runtimeError("Call to undefined method '%s'. Ignored.", methodName);
					_stack->pushNULL();
				}
			}
		}
		delete[] methodName;
	}
	break;

	case II_EXTERNAL_CALL: {
		uint32 symbolIndex = getDWORD();

		TExternalFunction *f = getExternal(_symbols[symbolIndex]);
		if (f) {
			externalCall(_stack, _thisStack, f);
		} else {
			_gameRef->externalCall(this, _stack, _thisStack, _symbols[symbolIndex]);
		}

		break;
	}
	case II_SCOPE:
		_operand->setNULL();
		_scopeStack->push(_operand);
		break;

	case II_CORRECT_STACK:
		dw = getDWORD(); // params expected
		_stack->correctParams(dw);
		break;

	case II_CREATE_OBJECT:
		_operand->setObject();
		_stack->push(_operand);
		break;

	case II_POP_EMPTY:
		_stack->pop();
		break;

	case II_PUSH_VAR: {
		ScValue *var = getVar(_symbols[getDWORD()]);
		if (false && /*var->_type==VAL_OBJECT ||*/ var->_type == VAL_NATIVE) {
			_operand->setReference(var);
			_stack->push(_operand);
		} else {
			_stack->push(var);
		}
		break;
	}

	case II_PUSH_VAR_REF: {
		ScValue *var = getVar(_symbols[getDWORD()]);
		_operand->setReference(var);
		_stack->push(_operand);
		break;
	}

	case II_POP_VAR: {
		char *varName = _symbols[getDWORD()];
		ScValue *var = getVar(varName);
		if (var) {
			ScValue *val = _stack->pop();
			if (!val) {
				runtimeError("Script stack corruption detected. Please report this script at WME bug reports forum.");
				var->setNULL();
			} else {
				if (val->getType() == VAL_VARIABLE_REF) {
					val = val->_valRef;
				}
				if (val->_type == VAL_NATIVE) {
					var->setValue(val);
				} else {
					var->copy(val);
				}
			}
		}

		break;
	}

	case II_PUSH_VAR_THIS:
		_stack->push(_thisStack->getTop());
		break;

	case II_PUSH_INT:
		_stack->pushInt((int)getDWORD());
		break;

	case II_PUSH_FLOAT:
		_stack->pushFloat(getFloat());
		break;


	case II_PUSH_BOOL:
		_stack->pushBool(getDWORD() != 0);

		break;

	case II_PUSH_STRING:
		_stack->pushString(getString());
		break;

	case II_PUSH_NULL:
		_stack->pushNULL();
		break;

	case II_PUSH_THIS_FROM_STACK:
		_operand->setReference(_stack->getTop());
		_thisStack->push(_operand);
		break;

	case II_PUSH_THIS:
		_operand->setReference(getVar(_symbols[getDWORD()]));
		_thisStack->push(_operand);
		break;

	case II_POP_THIS:
		_thisStack->pop();
		break;

	case II_PUSH_BY_EXP: {
		str = _stack->pop()->getString();
		ScValue *val = _stack->pop()->getProp(str);
		if (val) {
			_stack->push(val);
		} else {
			_stack->pushNULL();
		}

		break;
	}

	case II_POP_BY_EXP: {
		str = _stack->pop()->getString();
		ScValue *var = _stack->pop();
		ScValue *val = _stack->pop();

		if (val == nullptr) {
			runtimeError("Script stack corruption detected. Please report this script at WME bug reports forum.");
			var->setNULL();
		} else {
			var->setProp(str, val);
		}

		break;
	}

	case II_PUSH_REG1:
		_stack->push(_reg1);
		break;

	case II_POP_REG1:
		_reg1->copy(_stack->pop());
		break;

	case II_JMP:
		_iP = getDWORD();
		break;

	case II_JMP_FALSE: {
		dw = getDWORD();
		//if (!_stack->pop()->getBool()) _iP = dw;
		ScValue *val = _stack->pop();
		if (!val) {
			runtimeError("Script corruption detected. Did you use '=' instead of '==' for comparison?");
		} else {
			if (!val->getBool()) {
				_iP = dw;
			}
		}
		break;
	}

	case II_ADD:
		op2 = _stack->pop();
		op1 = _stack->pop();

		if (op1->isNULL() || op2->isNULL()) {
			_operand->setNULL();
		} else if (op1->getType() == VAL_STRING || op2->getType() == VAL_STRING) {
			char *tempStr = new char [strlen(op1->getString()) + strlen(op2->getString()) + 1];
			strcpy(tempStr, op1->getString());
			strcat(tempStr, op2->getString());
			_operand->setString(tempStr);
			delete[] tempStr;
		} else if (op1->getType() == VAL_INT && op2->getType() == VAL_INT) {
			_operand->setInt(op1->getInt() + op2->getInt());
		} else {
			_operand->setFloat(op1->getFloat() + op2->getFloat());
		}

		_stack->push(_operand);

		break;

	case II_SUB:
		op2 = _stack->pop();
		op1 = _stack->pop();

		if (op1->isNULL() || op2->isNULL()) {
			_operand->setNULL();
		} else if (op1->getType() == VAL_INT && op2->getType() == VAL_INT) {
			_operand->setInt(op1->getInt() - op2->getInt());
		} else {
			_operand->setFloat(op1->getFloat() - op2->getFloat());
		}

		_stack->push(_operand);

		break;

	case II_MUL:
		op2 = _stack->pop();
		op1 = _stack->pop();

		if (op1->isNULL() || op2->isNULL()) {
			_operand->setNULL();
		} else if (op1->getType() == VAL_INT && op2->getType() == VAL_INT) {
			_operand->setInt(op1->getInt() * op2->getInt());
		} else {
			_operand->setFloat(op1->getFloat() * op2->getFloat());
		}

		_stack->push(_operand);

		break;

	case II_DIV:
		op2 = _stack->pop();
		op1 = _stack->pop();

		if (op2->getFloat() == 0.0f) {
			runtimeError("Division by zero.");
		}

		if (op1->isNULL() || op2->isNULL() || op2->getFloat() == 0.0f) {
			_operand->setNULL();
		} else {
			_operand->setFloat(op1->getFloat() / op2->getFloat());
		}

		_stack->push(_operand);

		break;

	case II_MODULO:
		op2 = _stack->pop();
		op1 = _stack->pop();

		if (op2->getInt() == 0) {
			runtimeError("Division by zero.");
		}

		if (op1->isNULL() || op2->isNULL() || op2->getInt() == 0) {
			_operand->setNULL();
		} else {
			_operand->setInt(op1->getInt() % op2->getInt());
		}

		_stack->push(_operand);

		break;

	case II_NOT:
		op1 = _stack->pop();
		//if (op1->isNULL()) _operand->setNULL();
		if (op1->isNULL()) {
			_operand->setBool(true);
		} else {
			_operand->setBool(!op1->getBool());
		}
		_stack->push(_operand);

		break;

	case II_AND:
		op2 = _stack->pop();
		op1 = _stack->pop();
		if (op1 == nullptr || op2 == nullptr) {
			runtimeError("Script corruption detected. Did you use '=' instead of '==' for comparison?");
			_operand->setBool(false);
		} else {
			_operand->setBool(op1->getBool() && op2->getBool());
		}
		_stack->push(_operand);
		break;

	case II_OR:
		op2 = _stack->pop();
		op1 = _stack->pop();
		if (op1 == nullptr || op2 == nullptr) {
			runtimeError("Script corruption detected. Did you use '=' instead of '==' for comparison?");
			_operand->setBool(false);
		} else {
			_operand->setBool(op1->getBool() || op2->getBool());
		}
		_stack->push(_operand);
		break;

	case II_CMP_EQ:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if ((op1->isNULL() && !op2->isNULL()) || (!op1->isNULL() && op2->isNULL())) _operand->setBool(false);
		else if (op1->isNative() && op2->isNative()) {
		    _operand->setBool(op1->getNative() == op2->getNative());
		}
		else if (op1->getType()==VAL_STRING || op2->getType()==VAL_STRING) {
		    _operand->setBool(scumm_stricmp(op1->getString(), op2->getString())==0);
		}
		else if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() == op2->getFloat());
		}
		else{
		    _operand->setBool(op1->getInt() == op2->getInt());
		}
		*/

		_operand->setBool(ScValue::compare(op1, op2) == 0);
		_stack->push(_operand);
		break;

	case II_CMP_NE:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if ((op1->isNULL() && !op2->isNULL()) || (!op1->isNULL() && op2->isNULL())) _operand->setBool(true);
		else if (op1->isNative() && op2->isNative()) {
		    _operand->setBool(op1->getNative() != op2->getNative());
		}
		else if (op1->getType()==VAL_STRING || op2->getType()==VAL_STRING) {
		    _operand->setBool(scumm_stricmp(op1->getString(), op2->getString())!=0);
		}
		else if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() != op2->getFloat());
		}
		else{
		    _operand->setBool(op1->getInt() != op2->getInt());
		}
		*/

		_operand->setBool(ScValue::compare(op1, op2) != 0);
		_stack->push(_operand);
		break;

	case II_CMP_L:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() < op2->getFloat());
		}
		else _operand->setBool(op1->getInt() < op2->getInt());
		*/

		_operand->setBool(ScValue::compare(op1, op2) < 0);
		_stack->push(_operand);
		break;

	case II_CMP_G:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() > op2->getFloat());
		}
		else _operand->setBool(op1->getInt() > op2->getInt());
		*/

		_operand->setBool(ScValue::compare(op1, op2) > 0);
		_stack->push(_operand);
		break;

	case II_CMP_LE:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() <= op2->getFloat());
		}
		else _operand->setBool(op1->getInt() <= op2->getInt());
		*/

		_operand->setBool(ScValue::compare(op1, op2) <= 0);
		_stack->push(_operand);
		break;

	case II_CMP_GE:
		op2 = _stack->pop();
		op1 = _stack->pop();

		/*
		if (op1->getType()==VAL_FLOAT && op2->getType()==VAL_FLOAT) {
		    _operand->setBool(op1->getFloat() >= op2->getFloat());
		}
		else _operand->setBool(op1->getInt() >= op2->getInt());
		*/

		_operand->setBool(ScValue::compare(op1, op2) >= 0);
		_stack->push(_operand);
		break;

	case II_CMP_STRICT_EQ:
		op2 = _stack->pop();
		op1 = _stack->pop();

		//_operand->setBool(op1->getType()==op2->getType() && op1->getFloat()==op2->getFloat());
		_operand->setBool(ScValue::compareStrict(op1, op2) == 0);
		_stack->push(_operand);

		break;

	case II_CMP_STRICT_NE:
		op2 = _stack->pop();
		op1 = _stack->pop();

		//_operand->setBool(op1->getType()!=op2->getType() || op1->getFloat()!=op2->getFloat());
		_operand->setBool(ScValue::compareStrict(op1, op2) != 0);
		_stack->push(_operand);
		break;

	case II_DBG_LINE: {
		int newLine = getDWORD();
		if (newLine != _currentLine) {
			_currentLine = newLine;
		}
		break;

	}
	default:
		_gameRef->LOG(0, "Fatal: Invalid instruction %d ('%s', line %d, IP:0x%x)\n", inst, _filename, _currentLine, _iP - sizeof(uint32));
		_state = SCRIPT_FINISHED;
		ret = STATUS_FAILED;
	} // switch(instruction)

	//delete op;

	return ret;
}


//////////////////////////////////////////////////////////////////////////
uint32 ScScript::getFuncPos(const Common::String &name) {
	for (uint32 i = 0; i < _numFunctions; i++) {
		if (name == _functions[i].name) {
			return _functions[i].pos;
		}
	}
	return 0;
}


//////////////////////////////////////////////////////////////////////////
uint32 ScScript::getMethodPos(const Common::String &name) const {
	for (uint32 i = 0; i < _numMethods; i++) {
		if (name == _methods[i].name) {
			return _methods[i].pos;
		}
	}
	return 0;
}


//////////////////////////////////////////////////////////////////////////
ScValue *ScScript::getVar(char *name) {
	ScValue *ret = nullptr;

	// scope locals
	if (_scopeStack->_sP >= 0) {
		if (_scopeStack->getTop()->propExists(name)) {
			ret = _scopeStack->getTop()->getProp(name);
		}
	}

	// script globals
	if (ret == nullptr) {
		if (_globals->propExists(name)) {
			ret = _globals->getProp(name);
		}
	}

	// engine globals
	if (ret == nullptr) {
		if (_engine->_globals->propExists(name)) {
			ret = _engine->_globals->getProp(name);
		}
	}

	if (ret == nullptr) {
		//RuntimeError("Variable '%s' is inaccessible in the current block. Consider changing the script.", name);
		_gameRef->LOG(0, "Warning: variable '%s' is inaccessible in the current block. Consider changing the script (script:%s, line:%d)", name, _filename, _currentLine);
		ScValue *val = new ScValue(_gameRef);
		ScValue *scope = _scopeStack->getTop();
		if (scope) {
			scope->setProp(name, val);
			ret = _scopeStack->getTop()->getProp(name);
		} else {
			_globals->setProp(name, val);
			ret = _globals->getProp(name);
		}
		delete val;
	}

	return ret;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::waitFor(BaseObject *object) {
	if (_unbreakable) {
		runtimeError("Script cannot be interrupted.");
		return STATUS_OK;
	}

	_state = SCRIPT_WAITING;
	_waitObject = object;
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::waitForExclusive(BaseObject *object) {
	_engine->resetObject(object);
	return waitFor(object);
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::sleep(uint32 duration) {
	if (_unbreakable) {
		runtimeError("Script cannot be interrupted.");
		return STATUS_OK;
	}

	_state = SCRIPT_SLEEPING;
	if (_gameRef->_state == GAME_FROZEN) {
		_waitTime = g_system->getMillis() + duration;
		_waitFrozen = true;
	} else {
		_waitTime = _gameRef->getTimer()->getTime() + duration;
		_waitFrozen = false;
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::finish(bool includingThreads) {
	if (_state != SCRIPT_FINISHED && includingThreads) {
		_state = SCRIPT_FINISHED;
		finishThreads();
	} else {
		_state = SCRIPT_FINISHED;
	}


	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::run() {
	_state = SCRIPT_RUNNING;
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////
void ScScript::runtimeError(const char *fmt, ...) {
	char buff[256];
	va_list va;

	va_start(va, fmt);
	vsprintf(buff, fmt, va);
	va_end(va);

	_gameRef->LOG(0, "Runtime error. Script '%s', line %d", _filename, _currentLine);
	_gameRef->LOG(0, "  %s", buff);

	if (!_gameRef->_suppressScriptErrors) {
		_gameRef->quickMessage("Script runtime error. View log for details.");
	}
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::persist(BasePersistenceManager *persistMgr) {

	persistMgr->transferPtr(TMEMBER_PTR(_gameRef));

	// buffer
	if (persistMgr->getIsSaving()) {
		if (_state != SCRIPT_PERSISTENT && _state != SCRIPT_FINISHED && _state != SCRIPT_THREAD_FINISHED) {
			persistMgr->transferUint32(TMEMBER(_bufferSize));
			persistMgr->putBytes(_buffer, _bufferSize);
		} else {
			// don't save idle/finished scripts
			int32 bufferSize = 0;
			persistMgr->transferSint32(TMEMBER(bufferSize));
		}
	} else {
		persistMgr->transferUint32(TMEMBER(_bufferSize));
		if (_bufferSize > 0) {
			_buffer = new byte[_bufferSize];
			persistMgr->getBytes(_buffer, _bufferSize);
			_scriptStream = new Common::MemoryReadStream(_buffer, _bufferSize);
			initTables();
		} else {
			_buffer = nullptr;
			_scriptStream = nullptr;
		}
	}

	persistMgr->transferPtr(TMEMBER_PTR(_callStack));
	persistMgr->transferSint32(TMEMBER(_currentLine));
	persistMgr->transferPtr(TMEMBER_PTR(_engine));
	persistMgr->transferCharPtr(TMEMBER(_filename));
	persistMgr->transferBool(TMEMBER(_freezable));
	persistMgr->transferPtr(TMEMBER_PTR(_globals));
	persistMgr->transferUint32(TMEMBER(_iP));
	persistMgr->transferPtr(TMEMBER_PTR(_scopeStack));
	persistMgr->transferPtr(TMEMBER_PTR(_stack));
	persistMgr->transferSint32(TMEMBER_INT(_state));
	persistMgr->transferPtr(TMEMBER_PTR(_operand));
	persistMgr->transferSint32(TMEMBER_INT(_origState));
	persistMgr->transferPtr(TMEMBER_PTR(_owner));
	persistMgr->transferPtr(TMEMBER_PTR(_reg1));
	persistMgr->transferBool(TMEMBER(_thread));
	persistMgr->transferCharPtr(TMEMBER(_threadEvent));
	persistMgr->transferPtr(TMEMBER_PTR(_thisStack));
	persistMgr->transferUint32(TMEMBER(_timeSlice));
	persistMgr->transferPtr(TMEMBER_PTR(_waitObject));
	persistMgr->transferPtr(TMEMBER_PTR(_waitScript));
	persistMgr->transferUint32(TMEMBER(_waitTime));
	persistMgr->transferBool(TMEMBER(_waitFrozen));

	persistMgr->transferBool(TMEMBER(_methodThread));
	persistMgr->transferBool(TMEMBER(_methodThread)); // TODO-SAVE: Deduplicate.
	persistMgr->transferBool(TMEMBER(_unbreakable));
	persistMgr->transferPtr(TMEMBER_PTR(_parentScript));

	if (!persistMgr->getIsSaving()) {
		_tracingMode = false;
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
ScScript *ScScript::invokeEventHandler(const Common::String &eventName, bool unbreakable) {
	//if (_state!=SCRIPT_PERSISTENT) return nullptr;

	uint32 pos = getEventPos(eventName);
	if (!pos) {
		return nullptr;
	}

	ScScript *thread = new ScScript(_gameRef,  _engine);
	if (thread) {
		bool ret = thread->createThread(this, pos, eventName);
		if (DID_SUCCEED(ret)) {
			thread->_unbreakable = unbreakable;
			_engine->_scripts.add(thread);
			return thread;
		} else {
			delete thread;
			return nullptr;
		}
	} else {
		return nullptr;
	}

}


//////////////////////////////////////////////////////////////////////////
uint32 ScScript::getEventPos(const Common::String &name) const {
	for (int i = _numEvents - 1; i >= 0; i--) {
		if (scumm_stricmp(name.c_str(), _events[i].name) == 0) {
			return _events[i].pos;
		}
	}
	return 0;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::canHandleEvent(const Common::String &eventName) const {
	return getEventPos(eventName) != 0;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::canHandleMethod(const Common::String &methodName) const {
	return getMethodPos(methodName) != 0;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::pause() {
	if (_state == SCRIPT_PAUSED) {
		_gameRef->LOG(0, "Attempting to pause a paused script ('%s', line %d)", _filename, _currentLine);
		return STATUS_FAILED;
	}

	if (!_freezable || _state == SCRIPT_PERSISTENT) {
		return STATUS_OK;
	}

	_origState = _state;
	_state = SCRIPT_PAUSED;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::resume() {
	if (_state != SCRIPT_PAUSED) {
		return STATUS_OK;
	}

	_state = _origState;
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
ScScript::TExternalFunction *ScScript::getExternal(char *name) {
	for (uint32 i = 0; i < _numExternals; i++) {
		if (strcmp(name, _externals[i].name) == 0) {
			return &_externals[i];
		}
	}
	return nullptr;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::externalCall(ScStack *stack, ScStack *thisStack, ScScript::TExternalFunction *function) {

	_gameRef->LOG(0, "External functions are not supported on this platform.");
	stack->correctParams(0);
	stack->pushNULL();
	return STATUS_FAILED;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::copyParameters(ScStack *stack) {
	int i;
	int numParams = stack->pop()->getInt();
	for (i = numParams - 1; i >= 0; i--) {
		_stack->push(stack->getAt(i));
	}
	_stack->pushInt(numParams);

	for (i = 0; i < numParams; i++) {
		stack->pop();
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool ScScript::finishThreads() {
	for (uint32 i = 0; i < _engine->_scripts.size(); i++) {
		ScScript *scr = _engine->_scripts[i];
		if (scr->_thread && scr->_state != SCRIPT_FINISHED && scr->_owner == _owner && scumm_stricmp(scr->_filename, _filename) == 0) {
			scr->finish(true);
		}
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
// IWmeDebugScript interface implementation
int ScScript::dbgGetLine() {
	return _currentLine;
}

//////////////////////////////////////////////////////////////////////////
const char *ScScript::dbgGetFilename() {
	return _filename;
}

//////////////////////////////////////////////////////////////////////////
void ScScript::afterLoad() {
	if (_buffer == nullptr) {
		byte *buffer = _engine->getCompiledScript(_filename, &_bufferSize);
		if (!buffer) {
			_gameRef->LOG(0, "Error reinitializing script '%s' after load. Script will be terminated.", _filename);
			_state = SCRIPT_ERROR;
			return;
		}

		_buffer = new byte [_bufferSize];
		memcpy(_buffer, buffer, _bufferSize);

		delete _scriptStream;
		_scriptStream = new Common::MemoryReadStream(_buffer, _bufferSize);

		initTables();
	}
}

} // End of namespace Wintermute
