/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/config-manager.h"

#include "common/random.h"
#include "common/memstream.h"

#include "mtropolis/coroutines.h"
#include "mtropolis/miniscript.h"

namespace MTropolis {

class MiniscriptInstructionParserFeedback : public IMiniscriptInstructionParserFeedback {
public:
	explicit MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs);

	uint registerGlobalGUIDIndex(uint32 guid) override;

private:
	Common::Array<MiniscriptReferences::GlobalRef> *_globalRefs;
};

MiniscriptInstructionParserFeedback::MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs) : _globalRefs(globalRefs) {
}

uint MiniscriptInstructionParserFeedback::registerGlobalGUIDIndex(uint32 guid) {
	for (uint i = 0; i < _globalRefs->size(); i++) {
		if ((*_globalRefs)[i].guid == guid)
			return i;
	}

	uint newIndex = _globalRefs->size();

	MiniscriptReferences::GlobalRef globalRef;
	globalRef.guid = guid;

	_globalRefs->push_back(globalRef);

	return newIndex;
}

bool miniscriptEvaluateTruth(const DynamicValue &value) {
	// NOTE: Comparing equal to "true" only passes for 1 exactly, but for conditions,
	// any non-zero value is true.
	switch (value.getType()) {
	case DynamicValueTypes::kBoolean:
		return value.getBool();
	case DynamicValueTypes::kInteger:
		return (value.getInt() != 0);
	case DynamicValueTypes::kFloat:
		return !(value.getFloat() == 0.0);
	case DynamicValueTypes::kObject:
		return !value.getObject().object.expired();
	default:
		return false;
	}
}

IMiniscriptInstructionParserFeedback::~IMiniscriptInstructionParserFeedback() {
}

MiniscriptInstruction::~MiniscriptInstruction() {
}

MiniscriptReferences::LocalRef::LocalRef() : guid(0) {
}

MiniscriptReferences::GlobalRef::GlobalRef() : guid(0) {
}

MiniscriptReferences::MiniscriptReferences(const Common::Array<LocalRef> &localRefs, const Common::Array<GlobalRef> &globalRefs) : _localRefs(localRefs), _globalRefs(globalRefs) {
}

void MiniscriptReferences::linkInternalReferences(ObjectLinkingScope *scope) {
	// Resolve using name lookups since there are some known cases where the GUID is broken
	// e.g. "bArriveFromCutScene" in "Set bArriveFromCutScene on PE" in Obsidian
	for (Common::Array<LocalRef>::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it)
		it->resolution = scope->resolve(it->guid, it->name, false);

	for (Common::Array<GlobalRef>::iterator it = _globalRefs.begin(), itEnd = _globalRefs.end(); it != itEnd; ++it)
		it->resolution = scope->resolve(it->guid, "", true);
}

void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
	for (LocalRef &ref : _localRefs) {
		Common::SharedPtr<RuntimeObject> obj = ref.resolution.lock();
		if (obj) {
			if (obj->isModifier()) {
				Common::WeakPtr<Modifier> mod = obj.staticCast<Modifier>();
				visitor->visitWeakModifierRef(mod);
				ref.resolution = mod;
			} else if (obj->isStructural()) {
				Common::WeakPtr<Structural> struc = obj.staticCast<Structural>();
				visitor->visitWeakStructuralRef(struc);
				ref.resolution = struc;
			}
		}
	}

	for (GlobalRef &ref : _globalRefs) {
		Common::SharedPtr<RuntimeObject> obj = ref.resolution.lock();
		if (obj) {
			if (obj->isModifier()) {
				Common::WeakPtr<Modifier> mod = obj.staticCast<Modifier>();
				visitor->visitWeakModifierRef(mod);
				ref.resolution = mod;
			} else if (obj->isStructural()) {
				Common::WeakPtr<Structural> struc = obj.staticCast<Structural>();
				visitor->visitWeakStructuralRef(struc);
				ref.resolution = struc;
			}
		}
	}
}

Common::WeakPtr<RuntimeObject> MiniscriptReferences::getRefByIndex(uint index) const {
	if (index >= _localRefs.size())
		return Common::WeakPtr<RuntimeObject>();
	return _localRefs[index].resolution;
}

Common::WeakPtr<RuntimeObject> MiniscriptReferences::getGlobalRefByIndex(uint index) const {
	if (index >= _globalRefs.size())
		return Common::WeakPtr<RuntimeObject>();
	return _globalRefs[index].resolution;
	
}

MiniscriptProgram::MiniscriptProgram(const Common::SharedPtr<Common::Array<uint8> > &programData, const Common::Array<MiniscriptInstruction *> &instructions, const Common::Array<Attribute> &attributes)
	: _programData(programData), _instructions(instructions), _attributes(attributes) {
}

MiniscriptProgram::~MiniscriptProgram() {
	// Destruct all instructions
	for (Common::Array<MiniscriptInstruction *>::const_iterator it = _instructions.begin(), itEnd = _instructions.end(); it != itEnd; ++it)
		(*it)->~MiniscriptInstruction();
}

const Common::Array<MiniscriptInstruction *> &MiniscriptProgram::getInstructions() const {
	return _instructions;
}

const Common::Array<MiniscriptProgram::Attribute> &MiniscriptProgram::getAttributes() const {
	return _attributes;
}

template<class T>
struct MiniscriptInstructionLoader {
	static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback);
};

template<class T>
bool MiniscriptInstructionLoader<T>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	// Default loader for simple instructions with no private data
	new (dest) T();
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::Send>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	Data::Event dataEvent;
	if (!dataEvent.load(instrDataReader))
		return false;

	Event evt;
	if (!evt.load(dataEvent))
		return false;

	MessageFlags msgFlags;
	msgFlags.immediate = ((instrFlags & 0x04) == 0);
	msgFlags.cascade = ((instrFlags & 0x08) == 0);
	msgFlags.relay = ((instrFlags & 0x10) == 0);

	new (dest) MiniscriptInstructions::Send(evt, msgFlags);
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::BuiltinFunc>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint32 functionID;
	if (!instrDataReader.readU32(functionID))
		return false;

	if (functionID < 1 || functionID > 20)
		return false; // Unknown function

	new (dest) MiniscriptInstructions::BuiltinFunc(static_cast<MiniscriptInstructions::BuiltinFunc::BuiltinFunctionID>(functionID));
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::GetChild>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint32 childAttribute;
	if (!instrDataReader.readU32(childAttribute))
		return false;

	new (dest) MiniscriptInstructions::GetChild(childAttribute, (instrFlags & 1) != 0, (instrFlags & 32) != 0);
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushGlobal>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint32 globalID;
	if (!instrDataReader.readU32(globalID))
		return false;

	new (dest) MiniscriptInstructions::PushGlobal(globalID, (instrFlags & 1) != 0);
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::Jump>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint32 jumpFlags, unknown, instrOffset;
	if (!instrDataReader.readU32(jumpFlags) || !instrDataReader.readU32(unknown) || !instrDataReader.readU32(instrOffset))
		return false;

	bool isConditional = (jumpFlags == 2);
	if (jumpFlags != 1 && jumpFlags != 2)
		return false; // Don't recognize this flag combination

	if (instrOffset == 0)
		return false; // Not valid

	new (dest) MiniscriptInstructions::Jump(instrOffset, isConditional);
	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint16 dataType;
	if (!instrDataReader.readU16(dataType))
		return false;

	if (dataType == 0)
		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeNull, nullptr, false);
	else if (dataType == 0x15) {
		Common::XPFloat f;
		if (!instrDataReader.readPlatformFloat(f))
			return false;

		double d = f.toDouble();
		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeDouble, &d, false);
	} else if (dataType == 0x1a) {
		uint8 boolValue;
		if (!instrDataReader.readU8(boolValue))
			return false;

		bool b = (boolValue != 0);
		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeBool, &b, false);
	} else if (dataType == 0x1f9) {
		uint32 refValue;
		if (!instrDataReader.readU32(refValue))
			return false;

		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeLocalRef, &refValue, (instrFlags & 1) != 0);
	} else if (dataType == 0x1fa) {
		uint32 refValue;
		if (!instrDataReader.readU32(refValue))
			return false;

		uint32 indexedRef = feedback.registerGlobalGUIDIndex(refValue);

		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &indexedRef, (instrFlags & 1) != 0);
	} else if (dataType == 0x1d) {
		MiniscriptInstructions::PushValue::Label label;
		if (!instrDataReader.readU32(label.superGroup) || !instrDataReader.readU32(label.id))
			return false;

		new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeLabel, &label, false);
	} else
		return false;

	return true;
}

template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushString>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
	uint16 strLength;
	if (!instrDataReader.readU16(strLength))
		return false;

	// Unlike most cases, in this case the string is null-terminated but the str length doesn't include the terminator
	Common::String str;
	if (!instrDataReader.readTerminatedStr(str, strLength + 1))
		return false;

	new (dest) MiniscriptInstructions::PushString(str);

	return true;
}

struct SIMiniscriptInstructionFactory {
	bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
	void (*getSizeAndAlignment)(size_t &outSize, size_t &outAlignment);
};

template<class T>
class MiniscriptInstructionFactory {
public:
	static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
	static void getSizeAndAlignment(size_t &outSize, size_t &outAlignment);

	static SIMiniscriptInstructionFactory *getInstance();

private:
	static SIMiniscriptInstructionFactory _instance;
};

template<class T>
bool MiniscriptInstructionFactory<T>::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback) {
	if (!MiniscriptInstructionLoader<T>::loadInstruction(dest, instrFlags, instrDataReader, feedback))
		return false;

	outMiniscriptInstructionPtr = static_cast<MiniscriptInstruction *>(static_cast<T *>(dest));
	return true;
}

template<class T>
void MiniscriptInstructionFactory<T>::getSizeAndAlignment(size_t &outSize, size_t &outAlignment) {
	outSize = sizeof(T);
	outAlignment = alignof(T);
}

template<class T>
inline SIMiniscriptInstructionFactory *MiniscriptInstructionFactory<T>::getInstance() {
	return &_instance;
}

template<class T>
SIMiniscriptInstructionFactory MiniscriptInstructionFactory<T>::_instance = {
	MiniscriptInstructionFactory<T>::create,
	MiniscriptInstructionFactory<T>::getSizeAndAlignment
};

MiniscriptParser::InstructionData::InstructionData()
	: opcode(0), flags(0), pdPosition(0), instrFactory(nullptr) {
}

bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::SharedPtr<MiniscriptProgram> &outProgram, Common::SharedPtr<MiniscriptReferences> &outReferences) {
	Common::Array<MiniscriptReferences::LocalRef> localRefs;
	Common::Array<MiniscriptReferences::GlobalRef> globalRefs;
	Common::Array<MiniscriptProgram::Attribute> attributes;
	Common::SharedPtr<Common::Array<uint8> > programDataPtr;
	Common::Array<MiniscriptInstruction *> miniscriptInstructions;

	// If the program is empty then just return an empty program
	if (program.bytecode.size() == 0 || program.numOfInstructions == 0) {
		outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
		outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));
		return true;
	}

	localRefs.resize(program.localRefs.size());
	for (size_t i = 0; i < program.localRefs.size(); i++) {
		localRefs[i].guid = program.localRefs[i].guid;
		localRefs[i].name = program.localRefs[i].name;
	}

	attributes.resize(program.attributes.size());
	for (size_t i = 0; i < program.attributes.size(); i++) {
		attributes[i].name = program.attributes[i].name;
	}

	Common::MemoryReadStream stream(&program.bytecode[0], program.bytecode.size());
	Data::DataReader reader(0, stream, program.dataFormat, kRuntimeVersion100, false);

	Common::Array<InstructionData> rawInstructions;
	rawInstructions.resize(program.numOfInstructions);

	for (size_t i = 0; i < program.numOfInstructions; i++) {
		InstructionData &rawInstruction = rawInstructions[i];
		uint16 instrSize;
		if (!reader.readU16(rawInstruction.opcode) || !reader.readU16(rawInstruction.flags) || !reader.readU16(instrSize))
			return false;

		if (instrSize < 6)
			return false;

		if (instrSize > 6) {
			rawInstruction.contents.resize(instrSize - 6);
			if (!reader.read(&rawInstruction.contents[0], instrSize - 6))
				return false;
		}
	}

	programDataPtr.reset(new Common::Array<uint8>());
	Common::Array<uint8> &programData = *programDataPtr.get();

	// Find out how much space we need and place instructions
	size_t maxAlignment = 1;
	size_t programDataSize = 0;
	for (size_t i = 0; i < program.numOfInstructions; i++) {
		InstructionData &rawInstruction = rawInstructions[i];

		SIMiniscriptInstructionFactory *factory = resolveOpcode(rawInstruction.opcode);
		rawInstruction.instrFactory = factory;

		if (!factory)
			return false;

		size_t compiledSize = 0;
		size_t compiledAlignment = 0;
		factory->getSizeAndAlignment(compiledSize, compiledAlignment);

		if (programDataSize % compiledAlignment != 0)
			programDataSize += (compiledAlignment - (programDataSize % compiledAlignment));

		rawInstruction.pdPosition = programDataSize;
		programDataSize += compiledSize;

		if (maxAlignment < compiledAlignment)
			maxAlignment = compiledAlignment;
	}

	programData.resize(programDataSize + maxAlignment - 1);
	uintptr programDataAddress = reinterpret_cast<uintptr>(&programData[0]);

	size_t baseOffset = 0;
	if (programDataAddress % maxAlignment != 0)
		baseOffset = (maxAlignment - (programDataSize % maxAlignment));

	miniscriptInstructions.resize(program.numOfInstructions);

	MiniscriptInstructionParserFeedback parserFeedback(&globalRefs);

	// Create instructions
	for (size_t i = 0; i < program.numOfInstructions; i++) {
		const InstructionData &rawInstruction = rawInstructions[i];

		const void *dataLoc = nullptr;
		if (rawInstruction.contents.size() != 0)
			dataLoc = &rawInstruction.contents[0];

		Common::MemoryReadStream instrContentsStream(static_cast<const byte *>(dataLoc), rawInstruction.contents.size());
		Data::DataReader instrContentsReader(0, instrContentsStream, reader.getDataFormat(), reader.getRuntimeVersion(), reader.isVersionAutoDetect());

		if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i], parserFeedback)) {
			// Destroy any already-created instructions
			for (size_t di = 0; di < i; di++) {
				miniscriptInstructions[i - 1 - di]->~MiniscriptInstruction();
			}

			return false;
		}
	}

	// Done
	outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
	outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));

	return true;
}

SIMiniscriptInstructionFactory *MiniscriptParser::resolveOpcode(uint16 opcode) {
	switch (opcode) {
	case 0x834:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Set>::getInstance();
	case 0x898:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Send>::getInstance();
	case 0xc9:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Add>::getInstance();
	case 0xca:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Sub>::getInstance();
	case 0xcb:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Mul>::getInstance();
	case 0xcc:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Div>::getInstance();
	case 0xcd:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Pow>::getInstance();
	case 0xce:
		return MiniscriptInstructionFactory<MiniscriptInstructions::And>::getInstance();
	case 0xcf:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Or>::getInstance();
	case 0xd0:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Neg>::getInstance();
	case 0xd1:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Not>::getInstance();
	case 0xd2:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpEqual>::getInstance();
	case 0xd3:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpNotEqual>::getInstance();
	case 0xd4:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpLessOrEqual>::getInstance();
	case 0xd5:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpLess>::getInstance();
	case 0xd6:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpGreaterOrEqual>::getInstance();
	case 0xd7:
		return MiniscriptInstructionFactory<MiniscriptInstructions::CmpGreater>::getInstance();
	case 0xd8:
		return MiniscriptInstructionFactory<MiniscriptInstructions::BuiltinFunc>::getInstance();
	case 0xd9:
		return MiniscriptInstructionFactory<MiniscriptInstructions::DivInt>::getInstance();
	case 0xda:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Modulo>::getInstance();
	case 0xdb:
		return MiniscriptInstructionFactory<MiniscriptInstructions::StrConcat>::getInstance();
	case 0x12f:
		return MiniscriptInstructionFactory<MiniscriptInstructions::PointCreate>::getInstance();
	case 0x130:
		return MiniscriptInstructionFactory<MiniscriptInstructions::RangeCreate>::getInstance();
	case 0x131:
		return MiniscriptInstructionFactory<MiniscriptInstructions::VectorCreate>::getInstance();
	case 0x135:
		return MiniscriptInstructionFactory<MiniscriptInstructions::GetChild>::getInstance();
	case 0x136:
		return MiniscriptInstructionFactory<MiniscriptInstructions::ListAppend>::getInstance();
	case 0x137:
		return MiniscriptInstructionFactory<MiniscriptInstructions::ListCreate>::getInstance();
	case 0x191:
		return MiniscriptInstructionFactory<MiniscriptInstructions::PushValue>::getInstance();
	case 0x192:
		return MiniscriptInstructionFactory<MiniscriptInstructions::PushGlobal>::getInstance();
	case 0x193:
		return MiniscriptInstructionFactory<MiniscriptInstructions::PushString>::getInstance();
	case 0x7d3:
		return MiniscriptInstructionFactory<MiniscriptInstructions::Jump>::getInstance();
	default:
		return nullptr;
	}
}

namespace MiniscriptInstructions {

MiniscriptInstructionOutcome UnimplementedInstruction::execute(MiniscriptThread *thread) const {
	thread->error("Unimplemented instruction");
	return kMiniscriptInstructionOutcomeFailed;
}

MiniscriptInstructionOutcome Set::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() != 2) {
		// Sets are only allowed when they would empty the stack
		thread->error("Invalid stack state for set instruction");
		return kMiniscriptInstructionOutcomeFailed;
	}

	// Convert value
	MiniscriptInstructionOutcome outcome = kMiniscriptInstructionOutcomeContinue;

	const MiniscriptStackValue &srcValue = thread->getStackValueFromTop(0);
	MiniscriptStackValue &target = thread->getStackValueFromTop(1);

	if (target.value.getType() == DynamicValueTypes::kWriteProxy) {
		const DynamicValueWriteProxy &proxy = target.value.getWriteProxy();
		outcome = proxy.pod.ifc->write(thread, srcValue.value, proxy.pod.objectRef, proxy.pod.ptrOrOffset);
		if (outcome == kMiniscriptInstructionOutcomeFailed) {
			thread->error("Failed to assign value to proxy");
			return outcome;
		}
	} else {
		VariableModifier *var = nullptr;
		if (target.value.getType() == DynamicValueTypes::kObject) {
			Common::SharedPtr<RuntimeObject> obj = target.value.getObject().object.lock();
			if (obj && obj->isModifier() && static_cast<const Modifier *>(obj.get())->isVariable())
				var = static_cast<VariableModifier *>(obj.get());
		}

		if (var != nullptr) {
			(void)var->varSetValue(thread, srcValue.value);
		} else {
			thread->error("Can't assign to rvalue");
			return kMiniscriptInstructionOutcomeFailed;
		}
	}
	thread->popValues(2);

	return outcome;
}

Send::Send(const Event &evt, const MessageFlags &messageFlags) : _evt(evt), _messageFlags(messageFlags) {
}

MiniscriptInstructionOutcome Send::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() != 2) {
		// Send instructions are only allowed if they empty the stack
		thread->error("Invalid stack state for send instruction");
		return kMiniscriptInstructionOutcomeFailed;
	}

	DynamicValue &targetValue = thread->getStackValueFromTop(0).value;
	DynamicValue &payloadValue = thread->getStackValueFromTop(1).value;

	if (targetValue.getType() != DynamicValueTypes::kObject) {
		// Failed sends are non-fatal (Obsidian requires this in the aircraft propulsion room to enter the propulsion puzzle)
		warning("Invalid message destination (target isn't an object reference)");
		thread->popValues(2);
		return kMiniscriptInstructionOutcomeContinue;
	}

	Common::SharedPtr<RuntimeObject> obj = targetValue.getObject().object.lock();

	if (!obj) {
		// Obsidian also triggers NAV_Restart on Project Started, which triggers "<init globals> on NAV_Restart"
		// which sends PRG_Toggle_Status_Display to sharedScene, even though at that point there is no shared scene.
		warning("Invalid message destination (target object is invalid)");
		thread->popValues(2);
		return kMiniscriptInstructionOutcomeContinue;
	}

	Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(_evt, payloadValue, thread->getModifier()->getSelfReference()));
	Common::SharedPtr<MessageDispatch> dispatch;
	if (obj->isModifier())
		dispatch.reset(new MessageDispatch(msgProps, static_cast<Modifier *>(obj.get()), _messageFlags.cascade, _messageFlags.relay, true));
	else if (obj->isStructural())
		dispatch.reset(new MessageDispatch(msgProps, static_cast<Structural *>(obj.get()), _messageFlags.cascade, _messageFlags.relay, true));
	else {
		warning("Invalid message destination (target object is not a modifier or structural object)");
		return kMiniscriptInstructionOutcomeContinue;
	}

	thread->popValues(2);

	if (_messageFlags.immediate) {
		thread->getRuntime()->sendMessageOnVThread(dispatch);
		return kMiniscriptInstructionOutcomeYieldToVThread;
	} else {
		thread->getRuntime()->queueMessage(dispatch);
		return kMiniscriptInstructionOutcomeContinue;
	}
}

MiniscriptInstructionOutcome BinaryArithInstruction::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rs = thread->getStackValueFromTop(0).value;
	DynamicValue &lsDest = thread->getStackValueFromTop(1).value;

	if (lsDest.getType() == DynamicValueTypes::kPoint && rs.getType() == DynamicValueTypes::kPoint) {
		Common::Point lsPoint = lsDest.getPoint();
		Common::Point rsPoint = rs.getPoint();

		double resultX = 0.0;
		double resultY = 0.0;
		outcome = arithExecute(thread, resultX, lsPoint.x, rsPoint.x);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;

		outcome = arithExecute(thread, resultY, lsPoint.y, rsPoint.y);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;

		lsDest.setPoint(Common::Point(static_cast<int16>(round(resultX)), static_cast<int16>(round(resultY))));
	} else {
		double leftVal = 0.0;
		switch (lsDest.getType()) {
		case DynamicValueTypes::kInteger:
			leftVal = lsDest.getInt();
			break;
		case DynamicValueTypes::kFloat:
			leftVal = lsDest.getFloat();
			break;
		case DynamicValueTypes::kBoolean:
			leftVal = lsDest.getBool() ? 1.0 : 0.0;
			break;
		default:
			thread->error("Invalid left-side type for binary arithmetic operator");
			return kMiniscriptInstructionOutcomeFailed;
		}

		double rightVal = 0.0;
		switch (rs.getType()) {
		case DynamicValueTypes::kInteger:
			rightVal = rs.getInt();
			break;
		case DynamicValueTypes::kFloat:
			rightVal = rs.getFloat();
			break;
		case DynamicValueTypes::kBoolean:
			rightVal = rs.getBool() ? 1.0 : 0.0;
			break;
		default:
			thread->error("Invalid right-side type for binary arithmetic operator");
			return kMiniscriptInstructionOutcomeFailed;
		}

		double result = 0.0;
		outcome = arithExecute(thread, result, leftVal, rightVal);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;

		lsDest.setFloat(result);
	}

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Add::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	result = left + right;
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Sub::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	result = left - right;
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Mul::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	result = left * right;
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Div::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	if (right == 0.0) {
		thread->error("Arithmetic error: Division by zero");
		return kMiniscriptInstructionOutcomeFailed;
	}
	result = left / right;
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Pow::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	if (left < 0.0 && right != floor(right)) {
		thread->error("Arithmetic error: Left side is negative but right side is not an integer");
		return kMiniscriptInstructionOutcomeFailed;
	}
	result = pow(left, right);
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome DivInt::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	if (right == 0.0) {
		thread->error("Arithmetic error: Integer division by zero");
		return kMiniscriptInstructionOutcomeFailed;
	}
	result = floor(left / right);
	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Modulo::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
	if (right == 0.0) {
		thread->error("Arithmetic error: Modulo division by zero");
		return kMiniscriptInstructionOutcomeFailed;
	}

	// fmod keeps the sign from the left operand, but mTropolis modulo keeps the
	// sign of the right operand.
	double r = fmod(left, right);
	if (signbit(left) != signbit(right)) {
		if (r == 0.0)
			r = copysign(0.0, right);
		else
			r += right;
	}

	result = r;

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome UnorderedCompareInstruction::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rs = thread->getStackValueFromTop(0).value;
	DynamicValue &lsDest = thread->getStackValueFromTop(1).value;

	bool isEqual = false;
	bool isUndefined = false;
	switch (lsDest.getType()) {
	case DynamicValueTypes::kString:
		if (rs.getType() == DynamicValueTypes::kString)
			isEqual = caseInsensitiveEqual(lsDest.getString(), rs.getString());
		break;
	case DynamicValueTypes::kBoolean: {
			switch (rs.getType()) {
			case DynamicValueTypes::kInteger:
				isEqual = (rs.getInt() == (lsDest.getBool() ? 1 : 0));
				break;
			case DynamicValueTypes::kFloat:
				isEqual = (rs.getFloat() == (lsDest.getBool() ? 1.0 : 0.0));
				break;
			case DynamicValueTypes::kBoolean:
				isEqual = (rs.getBool() == lsDest.getBool());
				break;
			default:
				isEqual = (lsDest.getBool() == false);
				break;
			}
		} break;
	case DynamicValueTypes::kFloat: {
			if (isnan(lsDest.getFloat()))
				isUndefined = true;
			else {
				switch (rs.getType()) {
				case DynamicValueTypes::kInteger:
					isEqual = (rs.getInt() == lsDest.getFloat());
					break;
				case DynamicValueTypes::kFloat:
					if (isnan(rs.getFloat()))
						isUndefined = true;
					else
						isEqual = (rs.getFloat() == lsDest.getFloat());
					break;
				case DynamicValueTypes::kBoolean:
					isEqual = ((rs.getBool() ? 1.0 : 0.0) == lsDest.getFloat());
					break;
				default:
					isEqual = false;
					break;
				}
			}
		} break;
	case DynamicValueTypes::kInteger: {
			switch (rs.getType()) {
			case DynamicValueTypes::kInteger:
				isEqual = (rs.getInt() == lsDest.getInt());
				break;
			case DynamicValueTypes::kFloat:
				if (isnan(rs.getFloat()))
					isUndefined = true;
				else
					isEqual = (rs.getFloat() == lsDest.getInt());
				break;
			case DynamicValueTypes::kBoolean:
				isEqual = ((rs.getBool() ? 1 : 0) == lsDest.getInt());
				break;
			default:
				isEqual = false;
				break;
			}
		} break;
	case DynamicValueTypes::kLabel: {
			// Really not sure how this works but there are buggy scripts in Obsidian which
			// were probably written as "if loop = false then ..." except Miniscript resolved
			// "loop" as a sound marker (!) because a sound marker with that name exists instead
			// of resolving it as equivalent to element.loop as it usually would.
			// Strict equality checks prevent the "GEN_Streaming_Update on ALC" script from
			// working, which prevents the bqtstreaming and bstreaming flags from being cleared,
			// causing, among other things, the player to get stuck after the Forest->Bureau
			// transition because the stuck streaming flags are blocking the VO.
			if (rs.getType() == DynamicValueTypes::kBoolean)
				isEqual = !rs.getBool();
			else
				isEqual = (lsDest == rs);
		} break;
	default:
		isEqual = (lsDest == rs);
		break;
	}

	lsDest.setBool(isUndefined == false && this->resolve(isEqual));
	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}


MiniscriptInstructionOutcome And::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rs = thread->getStackValueFromTop(0).value;
	DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
	lsDest.setBool(miniscriptEvaluateTruth(lsDest) && miniscriptEvaluateTruth(rs));

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Or::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rs = thread->getStackValueFromTop(0).value;
	DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
	lsDest.setBool(miniscriptEvaluateTruth(lsDest) || miniscriptEvaluateTruth(rs));

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Neg::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 1) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &value = thread->getStackValueFromTop(0).value;
	switch (value.getType()) {
	case DynamicValueTypes::kFloat:
		value.setFloat(-value.getFloat());
		break;
	case DynamicValueTypes::kInteger: {
		int32 i = value.getInt();
		if (i == (0 - 1 -0x7fffffff))
			value.setFloat(-static_cast<double>(i));
		else
			value.setInt(-i);
		} break;
	default:
		thread->error("Couldn't negate a value of a non-numeric type");
		return kMiniscriptInstructionOutcomeFailed;
	}

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome Not::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 1) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &value = thread->getStackValueFromTop(0).value;
	value.setBool(!miniscriptEvaluateTruth(value));

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome OrderedCompareInstruction::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rs = thread->getStackValueFromTop(0).value;
	DynamicValue &lsDest = thread->getStackValueFromTop(1).value;

	double leftValue = 0.0;
	double rightValue = 0.0;
	if (lsDest.getType() == DynamicValueTypes::kFloat)
		leftValue = lsDest.getFloat();
	else if (lsDest.getType() == DynamicValueTypes::kInteger)
		leftValue = lsDest.getInt();
	else {
		thread->error("Left-side value is invalid for comparison");
		return kMiniscriptInstructionOutcomeFailed;
	}

	if (rs.getType() == DynamicValueTypes::kFloat)
		rightValue = rs.getFloat();
	else if (rs.getType() == DynamicValueTypes::kInteger)
		rightValue = rs.getInt();
	else {
		thread->error("Right-side value is invalid for comparison");
		return kMiniscriptInstructionOutcomeFailed;
	}

	lsDest.setBool(this->compareFloat(leftValue, rightValue));
	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

BuiltinFunc::BuiltinFunc(BuiltinFunctionID bfid) : _funcID(bfid) {
}

MiniscriptInstructionOutcome BuiltinFunc::execute(MiniscriptThread *thread) const {
	size_t stackArgsNeeded = 1;
	bool returnsValue = true;

	if (thread->getStackSize() < stackArgsNeeded) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	for (size_t i = 0; i < stackArgsNeeded; i++) {
		MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(i);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;
	}

	DynamicValue staticDest;
	DynamicValue *dest = nullptr;

	if (returnsValue) {
		if (stackArgsNeeded > 0)
			dest = &thread->getStackValueFromTop(stackArgsNeeded - 1).value;
		else
			dest = &staticDest;
	}

	MiniscriptInstructionOutcome outcome = executeFunction(thread, dest);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	if (stackArgsNeeded > 0) {
		size_t valuesToPop = stackArgsNeeded;
		if (returnsValue)
			valuesToPop--;

		if (valuesToPop > 0) {
			// coverity[dead_error_line]
			thread->popValues(valuesToPop);
		}
	} else {
		// coverity[dead_error_line]
		if (returnsValue)
			thread->pushValue(staticDest);
	}

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome BuiltinFunc::executeFunction(MiniscriptThread *thread, DynamicValue *returnValue) const {
	switch (_funcID) {
	case kSin:
	case kCos:
	case kRandom:
	case kSqrt:
	case kTan:
	case kAbs:
	case kSign:
	case kArctangent:
	case kExp:
	case kLn:
	case kLog:
	case kCosH:
	case kSinH:
	case kTanH:
	case kTrunc:
	case kRound:
		return executeSimpleNumericInstruction(thread, returnValue);
	case kRect2Polar:
		return executeRectToPolar(thread, returnValue);
	case kPolar2Rect:
		return executePolarToRect(thread, returnValue);
	case kNum2Str:
		return executeNum2Str(thread, returnValue);
	case kStr2Num:
		return executeStr2Num(thread, returnValue);
	default:
		thread->error("Unimplemented built-in function");
		return kMiniscriptInstructionOutcomeFailed;
	}
}

MiniscriptInstructionOutcome BuiltinFunc::executeSimpleNumericInstruction(MiniscriptThread *thread, DynamicValue *returnValue) const {
	double result = 0.0;

	double input = 0.0;
	const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;

	switch (inputDynamicValue.getType()) {
	case DynamicValueTypes::kInteger:
		input = inputDynamicValue.getInt();
		break;
	case DynamicValueTypes::kFloat:
		input = inputDynamicValue.getFloat();
		break;
	default:
		thread->error("Invalid numeric function input type");
		return kMiniscriptInstructionOutcomeFailed;
	}

	switch (_funcID) {
	case kSin:
		result = sin(input * (M_PI / 180.0));
		break;
	case kCos:
		result = cos(input * (M_PI / 180.0));
		break;
	case kRandom:
		if (input < 1.5)
			result = 0.0;
		else {
			uint rngMax = static_cast<uint>(floor(input + 0.5)) - 1;
			result = thread->getRuntime()->getRandom()->getRandomNumber(rngMax);
		}
		break;
	case kSqrt:
		result = sqrt(input);
		break;
	case kTan:
		result = tan(input * (M_PI / 180.0));
		break;
	case kAbs:
		result = fabs(input);
		break;
	case kSign:
		if (input < 0.0)
			result = -1;
		else if (input > 0.0)
			result = 1;
		else
			result = 0;
		break;
	case kArctangent:
		result = atan(input) * (180.0 / M_PI);
		break;
	case kExp:
		result = exp(input);
		break;
	case kLn:
		result = log(input);
		break;
	case kLog:
		result = log10(input);
		break;
	case kCosH:
		result = cosh(input * (M_PI / 180.0));
		break;
	case kSinH:
		result = sinh(input * (M_PI / 180.0));
		break;
	case kTanH:
		result = tanh(input * (M_PI / 180.0));
		break;
	case kTrunc:
		result = trunc(input);
		break;
	case kRound:
		result = round(input);
		break;
	default:
		thread->error("Unimplemented numeric function");
		return kMiniscriptInstructionOutcomeFailed;
	}

	returnValue->setFloat(result);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome BuiltinFunc::executeRectToPolar(MiniscriptThread *thread, DynamicValue *returnValue) const {
	const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;

	if (inputDynamicValue.getType() != DynamicValueTypes::kPoint) {
		thread->error("Polar to rect input must be a vector");
		return kMiniscriptInstructionOutcomeFailed;
	}

	Common::Point pt = inputDynamicValue.getPoint();

	double angle = atan2(pt.y, pt.x);
	double magnitude = sqrt(pt.x * pt.x + pt.y * pt.y);

	returnValue->setVector(AngleMagVector::createRadians(angle, magnitude));

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome BuiltinFunc::executePolarToRect(MiniscriptThread *thread, DynamicValue *returnValue) const {
	const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;

	if (inputDynamicValue.getType() != DynamicValueTypes::kVector) {
		thread->error("Polar to rect input must be a vector");
		return kMiniscriptInstructionOutcomeFailed;
	}

	const AngleMagVector &vec = inputDynamicValue.getVector();

	double x = cos(vec.angleDegrees * (M_PI / 180.0)) * vec.magnitude;
	double y = sin(vec.angleDegrees * (M_PI / 180.0)) * vec.magnitude;

	returnValue->setPoint(Common::Point(static_cast<int16>(round(x)), static_cast<int16>(round(y))));

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome BuiltinFunc::executeNum2Str(MiniscriptThread *thread, DynamicValue *returnValue) const {
	Common::String result;

	const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
	switch (inputDynamicValue.getType()) {
	case DynamicValueTypes::kInteger:
		result = Common::String::format("%i", static_cast<int>(inputDynamicValue.getInt()));
		break;
	case DynamicValueTypes::kFloat:
		result = Common::String::format("%g", static_cast<double>(inputDynamicValue.getFloat()));
		break;
	default:
		thread->error("Invalid input value to num2str");
		return kMiniscriptInstructionOutcomeFailed;
	}

	returnValue->setString(result);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome BuiltinFunc::executeStr2Num(MiniscriptThread *thread, DynamicValue *returnValue) const {
	double result = 0.0;

	const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
	if (inputDynamicValue.getType() != DynamicValueTypes::kString) {
		thread->error("Invalid input value to str2num");
		return kMiniscriptInstructionOutcomeFailed;
	}

	const Common::String &str = inputDynamicValue.getString();
	if (str.empty())
		result = 0.0;
	else if (!sscanf(str.c_str(), "%lf", &result)) {
		// NOTE: sscanf will properly handle cases where a number is followed by a non-numeric value,
		// which is consistent with mTropolis' behavior.
		// 
		// If it fails, the result is 0.0 and no script error.
		//
		// This includes a case in Obsidian where it tries to parse in invalid room number "L100"
		// upon entering the sky face for the first time in Bureau.

#ifdef MTROPOLIS_DEBUG_ENABLE
		if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
			debugger->notify(kDebugSeverityWarning, Common::String::format("Failed to parse '%s' as a number", str.c_str()));
#endif

		result = 0.0;
	}

	returnValue->setFloat(result);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome StrConcat::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &rVal = thread->getStackValueFromTop(0).value;
	DynamicValue &lValDest = thread->getStackValueFromTop(1).value;

	if (rVal.getType() != DynamicValueTypes::kString) {
		thread->error("String concat right side was not a string");
		return kMiniscriptInstructionOutcomeFailed;
	}

	if (lValDest.getType() != DynamicValueTypes::kString) {
		thread->error("String concat left side was not a string");
		return kMiniscriptInstructionOutcomeFailed;
	}

	lValDest.setString(lValDest.getString() + rVal.getString());
	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome PointCreate::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &yVal = thread->getStackValueFromTop(0).value;
	DynamicValue &xValDest = thread->getStackValueFromTop(1).value;

	int16 coords[2];
	DynamicValue *coordInputs[2] = {&xValDest, &yVal};

	for (int i = 0; i < 2; i++) {
		DynamicValue *v = coordInputs[i];
		DynamicValue listContents;

		if (v->getType() == DynamicValueTypes::kList) {
			// Yes this is actually allowed
			const Common::SharedPtr<DynamicList> &list = v->getList();
			if (list->getSize() != 1 || !list->getAtIndex(0, listContents)) {
				thread->error("Can't convert list to integer");
				return kMiniscriptInstructionOutcomeFailed;
			}

			v = &listContents;
		}

		switch (v->getType()) {
		case DynamicValueTypes::kFloat:
			coords[i] = static_cast<int16>(floor(v->getFloat() + 0.5)) & 0xffff;
			break;
		case DynamicValueTypes::kInteger:
			coords[i] = static_cast<int16>(v->getInt());
			break;
		case DynamicValueTypes::kBoolean:
			coords[i] = (v->getBool()) ? 1 : 0;
			break;
		default:
			thread->error("Invalid input for point creation");
			return kMiniscriptInstructionOutcomeFailed;
		}
	}

	xValDest.setPoint(Common::Point(coords[0], coords[1]));

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome RangeCreate::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	outcome = thread->dereferenceRValue(1);
	if (outcome != kMiniscriptInstructionOutcomeContinue)
		return outcome;

	DynamicValue &yVal = thread->getStackValueFromTop(0).value;
	DynamicValue &xValDest = thread->getStackValueFromTop(1).value;

	int32 coords[2];
	DynamicValue *coordInputs[2] = {&xValDest, &yVal};

	for (int i = 0; i < 2; i++) {
		DynamicValue *v = coordInputs[i];
		DynamicValue listContents;

		if (v->getType() == DynamicValueTypes::kList) {
			// Yes this is actually allowed
			const Common::SharedPtr<DynamicList> &list = v->getList();
			if (list->getSize() != 1 || !list->getAtIndex(0, listContents)) {
				thread->error("Can't convert list to integer");
				return kMiniscriptInstructionOutcomeFailed;
			}

			v = &listContents;
		}

		switch (v->getType()) {
		case DynamicValueTypes::kFloat:
			coords[i] = static_cast<int32>(floor(v->getFloat() + 0.5));
			break;
		case DynamicValueTypes::kInteger:
			coords[i] = v->getInt();
			break;
		case DynamicValueTypes::kBoolean:
			coords[i] = (v->getBool()) ? 1 : 0;
			break;
		default:
			thread->error("Invalid input for point creation");
			return kMiniscriptInstructionOutcomeFailed;
		}
	}

	xValDest.setIntRange(IntRange(coords[0], coords[1]));

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

GetChild::GetChild(uint32 attribute, bool isLValue, bool isIndexed)
	: _attribute(attribute), _isLValue(isLValue), _isIndexed(isIndexed) {
}

MiniscriptInstructionOutcome GetChild::execute(MiniscriptThread *thread) const {
	const Common::Array<MiniscriptProgram::Attribute> &attribs = thread->getProgram()->getAttributes();
	if (_attribute >= attribs.size()) {
		thread->error("Invalid attribute index");
		return kMiniscriptInstructionOutcomeFailed;
	}

	const Common::String &attrib = attribs[_attribute].name;

	MiniscriptInstructionOutcome outcome = kMiniscriptInstructionOutcomeFailed;

	if (_isIndexed) {
		if (thread->getStackSize() < 2) {
			thread->error("Stack underflow");
			return kMiniscriptInstructionOutcomeFailed;
		}

		// Convert index
		outcome = thread->dereferenceRValue(0);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;

		const MiniscriptStackValue &indexSlot = thread->getStackValueFromTop(0);
		MiniscriptStackValue &indexableValueSlot = thread->getStackValueFromTop(1);

		if (_isLValue) {
			if (indexableValueSlot.value.getType() == DynamicValueTypes::kObject) {
				Common::SharedPtr<RuntimeObject> obj = indexableValueSlot.value.getObject().object.lock();
				if (!obj) {
					thread->error("Tried to write '" + attrib + "' to an invalid object reference");
					return kMiniscriptInstructionOutcomeFailed;
				}

				DynamicValueWriteProxy proxy;
				outcome = obj->writeRefAttributeIndexed(thread, proxy, attrib, indexSlot.value);
				if (outcome == kMiniscriptInstructionOutcomeFailed) {
					thread->error("Failed to get a writeable reference to indexed attribute '" + attrib + "'");
					return outcome;
				}

				indexableValueSlot.value.setWriteProxy(proxy);
			} else if (indexableValueSlot.value.getType() == DynamicValueTypes::kWriteProxy) {
				DynamicValueWriteProxy proxy = indexableValueSlot.value.getWriteProxy();

				outcome = proxy.pod.ifc->refAttribIndexed(thread, proxy, proxy.pod.objectRef, proxy.pod.ptrOrOffset, attrib, indexSlot.value);
				if (outcome == kMiniscriptInstructionOutcomeFailed) {
					thread->error("Can't write to indexed attribute '" + attrib + "'");
					return outcome;
				}

				indexableValueSlot.value.setWriteProxy(proxy);
			} else {
				thread->error("Tried to l-value index something that was not writeable");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} else {
			outcome = readRValueAttribIndexed(thread, indexableValueSlot.value, attrib, indexSlot.value);
			if (outcome != kMiniscriptInstructionOutcomeContinue)
				return outcome;
		}

		thread->popValues(1);
	} else {
		if (thread->getStackSize() < 1) {
			thread->error("Stack underflow");
			return kMiniscriptInstructionOutcomeFailed;
		}

		MiniscriptStackValue &indexableValueSlot = thread->getStackValueFromTop(0);

		if (_isLValue) {
			if (indexableValueSlot.value.getType() == DynamicValueTypes::kObject) {
				Common::SharedPtr<RuntimeObject> obj = indexableValueSlot.value.getObject().object.lock();
				if (!obj) {
					thread->error("Tried to indirect '" + attrib + "' using an invalid object reference");
					return kMiniscriptInstructionOutcomeFailed;
				}

				DynamicValueWriteProxy writeProxy;
				outcome = obj->writeRefAttribute(thread, writeProxy, attrib);
				if (outcome == kMiniscriptInstructionOutcomeFailed) {
					thread->error("Failed to get a writeable reference to attribute '" + attrib + "'");
					return outcome;
				}

				indexableValueSlot.value.setWriteProxy(writeProxy);
			} else if (indexableValueSlot.value.getType() == DynamicValueTypes::kWriteProxy) {
				DynamicValueWriteProxy proxy = indexableValueSlot.value.getWriteProxy();
				outcome = proxy.pod.ifc->refAttrib(thread, proxy, proxy.pod.objectRef, proxy.pod.ptrOrOffset, attrib);
				if (outcome == kMiniscriptInstructionOutcomeFailed) {
					thread->error("Can't write to attribute '" + attrib + "'");
					return outcome;
				}
				indexableValueSlot.value.setWriteProxy(proxy);
			} else {
				thread->error("Tried to l-value index something that was not writeable");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} else {
			outcome = readRValueAttrib(thread, indexableValueSlot.value, attrib);
		}
	}

	return outcome;
}

MiniscriptInstructionOutcome GetChild::readRValueAttrib(MiniscriptThread *thread, DynamicValue &valueSrcDest, const Common::String &attrib) const {
	switch (valueSrcDest.getType()) {
	case DynamicValueTypes::kPoint:
		if (attrib == "x")
			valueSrcDest.setInt(valueSrcDest.getPoint().x);
		else if (attrib == "y")
			valueSrcDest.setInt(valueSrcDest.getPoint().y);
		else {
			thread->error("Point has no attribute '" + attrib + "'");
			return kMiniscriptInstructionOutcomeFailed;
		}
		break;
	case DynamicValueTypes::kIntegerRange:
		if (attrib == "start")
			valueSrcDest.setInt(valueSrcDest.getIntRange().min);
		else if (attrib == "end")
			valueSrcDest.setInt(valueSrcDest.getIntRange().max);
		else {
			thread->error("Integer range has no attribute '" + attrib + "'");
			return kMiniscriptInstructionOutcomeFailed;
		}
		break;

	case DynamicValueTypes::kVector:
		if (attrib == "angle")
			valueSrcDest.setFloat(valueSrcDest.getVector().angleDegrees);
		else if (attrib == "magnitude")
			valueSrcDest.setFloat(valueSrcDest.getVector().magnitude);
		else {
			thread->error("Vector has no attribute '" + attrib + "'");
			return kMiniscriptInstructionOutcomeFailed;
		}
		break;
	case DynamicValueTypes::kObject: {
			Common::SharedPtr<RuntimeObject> obj = valueSrcDest.getObject().object.lock();
			if (!obj) {
				thread->error("Unable to read object attribute '" + attrib + "' from invalid object");
				return kMiniscriptInstructionOutcomeFailed;
			} else if (!obj->readAttribute(thread, valueSrcDest, attrib)) {
				thread->error("Unable to read object attribute '" + attrib + "'");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} break;
	case DynamicValueTypes::kList: {
			Common::SharedPtr<DynamicList> list = valueSrcDest.getList();
			if (attrib == "count") {
				valueSrcDest.setInt(list->getSize());
			} else {
				thread->error("Unable to read list attribute '" + attrib + "'");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} break;
	default:
		thread->error("Unable to read attribute '" + attrib + "' from rvalue");
		return kMiniscriptInstructionOutcomeFailed;
	}

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome GetChild::readRValueAttribIndexed(MiniscriptThread *thread, DynamicValue &valueSrcDest, const Common::String &attrib, const DynamicValue &index) const {
	switch (valueSrcDest.getType()) {
	case DynamicValueTypes::kList:
		if (attrib == "value") {
			// Hold list ref since it may get released by the read operation
			Common::SharedPtr<DynamicList> list = valueSrcDest.getList();
			size_t realIndex = 0;
			if (!DynamicList::dynamicValueToIndex(realIndex, index)) {
				thread->error("Unable to list value at specified index");
				return kMiniscriptInstructionOutcomeFailed;
			}

			if (!list->getAtIndex(realIndex, valueSrcDest)) {
				thread->error("List read index out of bounds");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} else {
			thread->error("Unable to read list attribute '" + attrib + "'");
			return kMiniscriptInstructionOutcomeFailed;
		}
		break;
	case DynamicValueTypes::kObject: {
			Common::SharedPtr<RuntimeObject> obj = valueSrcDest.getObject().object.lock();
			if (!obj) {
				thread->error("Unable to read object indexed attribute '" + attrib + "' from invalid object");
				return kMiniscriptInstructionOutcomeFailed;
			} else if (!obj->readAttributeIndexed(thread, valueSrcDest, attrib, index)) {
				thread->error("Unable to read object indexed attribute '" + attrib + "'");
				return kMiniscriptInstructionOutcomeFailed;
			}
		} break;
	default:
		thread->error("Unable to read indexed rvalue attribute '" + attrib + "'");
		return kMiniscriptInstructionOutcomeFailed;
	}

	return kMiniscriptInstructionOutcomeContinue;
}

PushValue::PushValue(DataType dataType, const void *value, bool isLValue)
	: _dataType(dataType)/*, _isLValue(isLValue) */ {

	switch (dataType) {
	case DataType::kDataTypeBool:
		new (static_cast<bool *>(&_value.b)) bool(*static_cast<const bool *>(value));
		break;
	case DataType::kDataTypeDouble:
		new (static_cast<double *>(&_value.f)) double(*static_cast<const double *>(value));
		break;
	case DataType::kDataTypeLocalRef:
	case DataType::kDataTypeGlobalRef:
		new (static_cast<uint32 *>(&_value.ref)) uint32(*static_cast<const uint32 *>(value));
		break;
	case DataType::kDataTypeLabel:
		new (static_cast<Label *>(&_value.lbl)) Label(*static_cast<const Label *>(value));
		break;
	case DataType::kDataTypeNull:
		break;
	default:
		warning("PushValue instruction has an unknown type of value, this will probably malfunction!");
		break;
	}
}

PushValue::ValueUnion::ValueUnion() {
}

PushValue::~PushValue() {
	switch (_dataType) {
	case DataType::kDataTypeLabel:
		_value.lbl.~Label();
		break;
	default:
		break;
	}
}

MiniscriptInstructionOutcome ListCreate::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptStackValue &rs = thread->getStackValueFromTop(0);
	MiniscriptStackValue &lsDest = thread->getStackValueFromTop(1);

	Common::SharedPtr<DynamicList> list(new DynamicList());
	if (!list->setAtIndex(0, lsDest.value)) {
		thread->error("Failed to set value 1 of list");
		return kMiniscriptInstructionOutcomeFailed;
	}
	if (!list->setAtIndex(1, rs.value)) {
		thread->error("Failed to set value 2 of list");
		return kMiniscriptInstructionOutcomeFailed;
	}

	lsDest.value.setList(list);
	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome ListAppend::execute(MiniscriptThread *thread) const {
	if (thread->getStackSize() < 2) {
		thread->error("Stack underflow");
		return kMiniscriptInstructionOutcomeFailed;
	}

	MiniscriptStackValue &rs = thread->getStackValueFromTop(0);
	MiniscriptStackValue &lsDest = thread->getStackValueFromTop(1);

	if (lsDest.value.getType() != DynamicValueTypes::kList) {
		thread->error("Expected list on left side of list_append");
		return kMiniscriptInstructionOutcomeFailed;
	}

	Common::SharedPtr<DynamicList> listRef = lsDest.value.getList();
	if (listRef.refCount() != 2) {
		listRef = listRef->clone();
		lsDest.value.setList(listRef);
	}

	if (!listRef->setAtIndex(listRef->getSize(), rs.value)) {
		thread->error("Failed to expand list");
		return kMiniscriptInstructionOutcomeFailed;
	}

	thread->popValues(1);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome PushValue::execute(MiniscriptThread *thread) const {
	DynamicValue value;

	switch (_dataType) {
	case DataType::kDataTypeNull:
		value.clear();
		break;
	case DataType::kDataTypeDouble:
		value.setFloat(_value.f);
		break;
	case DataType::kDataTypeBool:
		value.setBool(_value.b);
		break;
	case DataType::kDataTypeLocalRef:
		value.setObject(ObjectReference(thread->getRefs()->getRefByIndex(_value.ref)));
		break;
	case DataType::kDataTypeGlobalRef:
		value.setObject(ObjectReference(thread->getRefs()->getGlobalRefByIndex(_value.ref)));
		break;
	case DataType::kDataTypeLabel: {
		MTropolis::Label label;
		label.id = _value.lbl.id;
		label.superGroupID = _value.lbl.superGroup;
		value.setLabel(label);
	} break;
	default:
		assert(false);
		break;
	}

	thread->pushValue(value);

	return kMiniscriptInstructionOutcomeContinue;
}

PushGlobal::PushGlobal(uint32 globalID, bool isLValue) : _globalID(globalID), _isLValue(isLValue) {
}

MiniscriptInstructionOutcome PushGlobal::execute(MiniscriptThread *thread) const {
	thread->pushValue(DynamicValue());

	DynamicValue &value = thread->getStackValueFromTop(0).value;

	switch (_globalID) {
	case kGlobalRefElement:
	case kGlobalRefSection:
	case kGlobalRefScene:
	case kGlobalRefProject:
		return executeFindFilteredParent(thread, value);
	case kGlobalRefModifier:
		value.setObject(thread->getModifier()->getSelfReference());
		break;
	case kGlobalRefIncomingData:
		if (_isLValue) {
			DynamicValueWriteProxy proxy;
			thread->createWriteIncomingDataProxy(proxy);
			value.setWriteProxy(proxy);
		} else
			value = thread->getMessageProperties()->getValue();
		break;
	case kGlobalRefSource:
		value.setObject(ObjectReference(thread->getMessageProperties()->getSource()));
		break;
	case kGlobalRefMouse:
		value.setPoint(thread->getRuntime()->getCachedMousePosition());
		break;
	case kGlobalRefTicks:
		value.setInt(thread->getRuntime()->getPlayTime() * 60 / 1000);
		break;
	case kGlobalRefSharedScene:
		value.setObject(ObjectReference(thread->getRuntime()->getActiveSharedScene()));
		break;
	case kGlobalRefActiveScene:
		value.setObject(ObjectReference(thread->getRuntime()->getActiveMainScene()));
		break;
	default:
		assert(false);
		thread->error("Unknown global ref type");
		return kMiniscriptInstructionOutcomeFailed;
	}

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome PushGlobal::executeFindFilteredParent(MiniscriptThread *thread, DynamicValue &result) const {
	Common::WeakPtr<RuntimeObject> ref = thread->getModifier()->getSelfReference();
	for (;;) {
		Common::SharedPtr<RuntimeObject> obj = ref.lock();
		if (!obj)
			break;

		bool isMatch = false;
		switch (_globalID) {
		case kGlobalRefElement:
			isMatch = obj->isStructural();	// We don't classify the project, sections, and subsections as elements, but mTropolis does
			break;
		case kGlobalRefSection:
			isMatch = obj->isSection();
			break;
		case kGlobalRefScene:
			// FIXME: Need better detection of scenes
			isMatch = obj->isElement() && static_cast<Element *>(obj.get())->getParent()->isSubsection();
			break;
		case kGlobalRefProject:
			isMatch = obj->isProject();
			break;
		default:
			break;
		};

		if (isMatch)
			break;
		else if (obj->isStructural()) {
			Structural *parent = static_cast<Structural *>(obj.get())->getParent();
			if (parent)
				ref = parent->getSelfReference();
			else {
				ref.reset();
				break;
			}
		} else if (obj->isModifier()) {
			ref = static_cast<Modifier *>(obj.get())->getParent();
		} else {
			ref.reset();
			break;
		}
	}

	result.setObject(ref);

	return kMiniscriptInstructionOutcomeContinue;
}

PushString::PushString(const Common::String &str) : _str(str) {
}

MiniscriptInstructionOutcome PushString::execute(MiniscriptThread *thread) const {
	DynamicValue str;
	str.setString(_str);
	thread->pushValue(str);

	return kMiniscriptInstructionOutcomeContinue;
}

Jump::Jump(uint32 instrOffset, bool isConditional) : _instrOffset(instrOffset), _isConditional(isConditional) {
}

MiniscriptInstructionOutcome Jump::execute(MiniscriptThread *thread) const {
	if (_isConditional) {
		if (thread->getStackSize() < 1) {
			thread->error("Stack underflow");
			return kMiniscriptInstructionOutcomeFailed;
		}

		MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
		if (outcome != kMiniscriptInstructionOutcomeContinue)
			return outcome;

		bool isTrue = miniscriptEvaluateTruth(thread->getStackValueFromTop(0).value);

		thread->popValues(1);

		if (!isTrue)
			thread->jumpOffset(this->_instrOffset);
	} else {
		thread->jumpOffset(this->_instrOffset);
	}

	return kMiniscriptInstructionOutcomeContinue;
}

} // End of namespace MiniscriptInstructions

MiniscriptThread::MiniscriptThread(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msgProps, const Common::SharedPtr<MiniscriptProgram> &program, const Common::SharedPtr<MiniscriptReferences> &refs, Modifier *modifier)
	: _runtime(runtime), _msgProps(msgProps), _program(program), _refs(refs), _modifier(modifier), _currentInstruction(0), _failed(false) {
}

void MiniscriptThread::error(const Common::String &message) {
#ifdef MTROPOLIS_DEBUG_ENABLE
	if (_runtime->debugGetDebugger())
		_runtime->debugGetDebugger()->notify(kDebugSeverityError, Common::String("Miniscript error: " + message));
#endif
	warning("Miniscript error in (%x '%s'): %s", _modifier->getStaticGUID(), _modifier->getName().c_str(), message.c_str());

	// This should be redundant
	_failed = true;
}

const Common::SharedPtr<MiniscriptProgram> &MiniscriptThread::getProgram() const {
	return _program;
}

const Common::SharedPtr<MiniscriptReferences> &MiniscriptThread::getRefs() const {
	return _refs;
}

Modifier *MiniscriptThread::getModifier() const {
	return _modifier;
}

const Common::SharedPtr<MessageProperties> &MiniscriptThread::getMessageProperties() const {
	return _msgProps;
}

Runtime *MiniscriptThread::getRuntime() const {
	return _runtime;
}

void MiniscriptThread::pushValue(const DynamicValue &value) {
	_stack.push_back(MiniscriptStackValue());

	MiniscriptStackValue &stackValue = _stack.back();
	stackValue.value = value;
}

void MiniscriptThread::popValues(size_t count) {
	while (count--)
		_stack.pop_back();
}

size_t MiniscriptThread::getStackSize() const {
	return _stack.size();
}

MiniscriptStackValue &MiniscriptThread::getStackValueFromTop(size_t offset) {
	assert(offset < _stack.size());
	return _stack[_stack.size() - 1 - offset];
}

MiniscriptInstructionOutcome MiniscriptThread::dereferenceRValue(size_t offset) {
	assert(offset < _stack.size());
	MiniscriptStackValue &stackValue = _stack[_stack.size() - 1 - offset];

	switch (stackValue.value.getType()) {
	case DynamicValueTypes::kObject: {
			Common::SharedPtr<RuntimeObject> obj = stackValue.value.getObject().object.lock();
			if (obj && obj->isModifier()) {
				const Modifier *modifier = static_cast<const Modifier *>(obj.get());
				if (modifier->isVariable()) {
					static_cast<const VariableModifier *>(modifier)->varGetValue(stackValue.value);
				}
			}
		} break;
	case DynamicValueTypes::kWriteProxy:
		this->error("Attempted to dereference an lvalue proxy");
		return kMiniscriptInstructionOutcomeFailed;
	case DynamicValueTypes::kList:
		stackValue.value.setList(stackValue.value.getList()->clone());
		break;
	default:
		break;
	}

	return kMiniscriptInstructionOutcomeContinue;
}

void MiniscriptThread::jumpOffset(size_t offset) {
	if (offset == 0) {
		this->error("Invalid jump offset");
		_failed = true;
		return;
	}

	_currentInstruction += offset - 1;
}

bool MiniscriptThread::evaluateTruthOfResult(bool &isTrue) {
	if (_stack.size() != 1) {
		this->error("Miniscript program didn't return a result");
		return false;
	}

	MiniscriptInstructionOutcome outcome = dereferenceRValue(0);
	if (outcome != kMiniscriptInstructionOutcomeContinue) {
		this->error("Miniscript program result couldn't be dereferenced");
		return false;
	}

	isTrue = miniscriptEvaluateTruth(_stack[0].value);
	return true;
}

void MiniscriptThread::createWriteIncomingDataProxy(DynamicValueWriteProxy &proxy) {
	proxy.pod.ifc = DynamicValueWriteInterfaceGlue<IncomingDataWriteInterface>::getInstance();
	proxy.pod.objectRef = this;
	proxy.pod.ptrOrOffset = 0;
}

void MiniscriptThread::retryInstruction() {
	_currentInstruction--;
}

MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
	thread->_msgProps->setValue(value);

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
	// TODO: Generic refAttrib for dynamic values
	return kMiniscriptInstructionOutcomeFailed;
}

MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
	// TODO: Generic refAttribIndexed for dynamic values
	return kMiniscriptInstructionOutcomeFailed;
}

CORO_BEGIN_DEFINITION(MiniscriptThread::ResumeThreadCoroutine)
	struct Locals {
		MiniscriptThread *self = nullptr;
		size_t numInstrs = 0;
		size_t instrNum = 0;
	};

	CORO_BEGIN_FUNCTION
		locals->self = params->thread.get();
		locals->numInstrs = locals->self->_program->getInstructions().size();

		CORO_IF (locals->numInstrs == 0)
			CORO_RETURN;
		CORO_END_IF

		CORO_WHILE (locals->self->_currentInstruction < locals->numInstrs && !locals->self->_failed)
			CORO_AWAIT_MINISCRIPT(locals->self->runNextInstruction());
		CORO_END_WHILE
	CORO_END_FUNCTION
CORO_END_DEFINITION

MiniscriptInstructionOutcome MiniscriptThread::runNextInstruction() {
	const MiniscriptInstruction *instr = _program->getInstructions()[_currentInstruction++];

	MiniscriptInstructionOutcome outcome = instr->execute(this);

	if (outcome == kMiniscriptInstructionOutcomeFailed) {
		// Treat this as non-fatal but bail out of the execution loop
		_failed = true;
		return kMiniscriptInstructionOutcomeContinue;
	}

	// Otherwise continue
	return outcome;
}

MiniscriptInstructionOutcome MiniscriptThread::tryLoadVariable(MiniscriptStackValue &stackValue) {
	if (stackValue.value.getType() == DynamicValueTypes::kObject) {
		Common::SharedPtr<RuntimeObject> obj = stackValue.value.getObject().object.lock();
		if (obj && obj->isModifier() && static_cast<Modifier *>(obj.get())->isVariable()) {
			VariableModifier *varMod = static_cast<VariableModifier *>(obj.get());
			varMod->varGetValue(stackValue.value);
		}
	}

	return kMiniscriptInstructionOutcomeContinue;
}

MiniscriptInstructionOutcome miniscriptIgnoreFailure(MiniscriptInstructionOutcome outcome) {
	if (outcome == kMiniscriptInstructionOutcomeFailed)
		return kMiniscriptInstructionOutcomeContinue;

	return outcome;
}

} // End of namespace MTropolis
