/* 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 "m4/wscript/ws_cruncher.h"
#include "m4/wscript/wscript.h"
#include "m4/wscript/wst_regs.h"
#include "m4/core/errors.h"
#include "m4/core/imath.h"
#include "m4/dbg/debug.h"
#include "m4/mem/mem.h"
#include "m4/vars.h"

namespace M4 {

#define VERIFY_INTIALIZED(s) if (!_GWS(cruncherInitialized)) error_show(FL, 'WSCI', "%s failed.", s);

static int32 dataFormats[] = { 0, 5, 8, 12, 16 };

static const frac16 sinCosTable[320] = {
	(frac16)0,		(frac16)1608,	(frac16)3215,	(frac16)4821,
	(frac16)6423,	(frac16)8022,	(frac16)9616,	(frac16)11204,
	(frac16)12785,	(frac16)14359,	(frac16)15923,	(frac16)17479,
	(frac16)19024,	(frac16)20557,	(frac16)22078,	(frac16)23586,
	(frac16)25079,	(frac16)26557,	(frac16)28020,	(frac16)29465,
	(frac16)30893,	(frac16)32302,	(frac16)33692,	(frac16)35061,
	(frac16)36409,	(frac16)37736,	(frac16)39039,	(frac16)40319,
	(frac16)41575,	(frac16)42806,	(frac16)44011,	(frac16)45189,
	(frac16)46340,	(frac16)47464,	(frac16)48558,	(frac16)49624,
	(frac16)50660,	(frac16)51665,	(frac16)52639,	(frac16)53581,
	(frac16)54491,	(frac16)55368,	(frac16)56212,	(frac16)57022,
	(frac16)57797,	(frac16)58538,	(frac16)59243,	(frac16)59913,
	(frac16)60547,	(frac16)61144,	(frac16)61705,	(frac16)62228,
	(frac16)62714,	(frac16)63162,	(frac16)63571,	(frac16)63943,
	(frac16)64276,	(frac16)64571,	(frac16)64826,	(frac16)65043,
	(frac16)65220,	(frac16)65358,	(frac16)65457,	(frac16)65516,
	(frac16)65536,	(frac16)65516,	(frac16)65457,	(frac16)65358,
	(frac16)65220,	(frac16)65043,	(frac16)64826,	(frac16)64571,
	(frac16)64276,	(frac16)63943,	(frac16)63571,	(frac16)63162,
	(frac16)62714,	(frac16)62228,	(frac16)61705,	(frac16)61144,
	(frac16)60547,	(frac16)59913,	(frac16)59243,	(frac16)58538,
	(frac16)57797,	(frac16)57022,	(frac16)56212,	(frac16)55368,
	(frac16)54491,	(frac16)53581,	(frac16)52639,	(frac16)51665,
	(frac16)50660,	(frac16)49624,	(frac16)48558,	(frac16)47464,
	(frac16)46340,	(frac16)45189,	(frac16)44011,	(frac16)42806,
	(frac16)41575,	(frac16)40319,	(frac16)39039,	(frac16)37736,
	(frac16)36409,	(frac16)35061,	(frac16)33692,	(frac16)32302,
	(frac16)30893,	(frac16)29465,	(frac16)28020,	(frac16)26557,
	(frac16)25079,	(frac16)23586,	(frac16)22078,	(frac16)20557,
	(frac16)19024,	(frac16)17479,	(frac16)15923,	(frac16)14359,
	(frac16)12785,	(frac16)11204,	(frac16)9616,	(frac16)8022,
	(frac16)6423,	(frac16)4821,	(frac16)3215,	(frac16)1608,
	(frac16)0,		(frac16)-1608,	(frac16)-3215,	(frac16)-4821,
	(frac16)-6423,	(frac16)-8022,	(frac16)-9616,	(frac16)-11204,
	(frac16)-12785,	(frac16)-14359,	(frac16)-15923,	(frac16)-17479,
	(frac16)-19024,	(frac16)-20557,	(frac16)-22078,	(frac16)-23586,
	(frac16)-25079,	(frac16)-26557,	(frac16)-28020,	(frac16)-29465,
	(frac16)-30893,	(frac16)-32302,	(frac16)-33692,	(frac16)-35061,
	(frac16)-36409,	(frac16)-37736,	(frac16)-39039,	(frac16)-40319,
	(frac16)-41575,	(frac16)-42806,	(frac16)-44011,	(frac16)-45189,
	(frac16)-46340,	(frac16)-47464,	(frac16)-48558,	(frac16)-49624,
	(frac16)-50660,	(frac16)-51665,	(frac16)-52639,	(frac16)-53581,
	(frac16)-54491,	(frac16)-55368,	(frac16)-56212,	(frac16)-57022,
	(frac16)-57797,	(frac16)-58538,	(frac16)-59243,	(frac16)-59913,
	(frac16)-60547,	(frac16)-61144,	(frac16)-61705,	(frac16)-62228,
	(frac16)-62714,	(frac16)-63162,	(frac16)-63571,	(frac16)-63943,
	(frac16)-64276,	(frac16)-64571,	(frac16)-64826,	(frac16)-65043,
	(frac16)-65220,	(frac16)-65358,	(frac16)-65457,	(frac16)-65516,
	(frac16)-65536,	(frac16)-65516,	(frac16)-65457,	(frac16)-65358,
	(frac16)-65220,	(frac16)-65043,	(frac16)-64826,	(frac16)-64571,
	(frac16)-64276,	(frac16)-63943,	(frac16)-63571,	(frac16)-63162,
	(frac16)-62714,	(frac16)-62228,	(frac16)-61705,	(frac16)-61144,
	(frac16)-60547,	(frac16)-59913,	(frac16)-59243,	(frac16)-58538,
	(frac16)-57797,	(frac16)-57022,	(frac16)-56212,	(frac16)-55368,
	(frac16)-54491,	(frac16)-53581,	(frac16)-52639,	(frac16)-51665,
	(frac16)-50660,	(frac16)-49624,	(frac16)-48558,	(frac16)-47464,
	(frac16)-46340,	(frac16)-45189,	(frac16)-44011,	(frac16)-42806,
	(frac16)-41575,	(frac16)-40319,	(frac16)-39039,	(frac16)-37736,
	(frac16)-36409,	(frac16)-35061,	(frac16)-33692,	(frac16)-32302,
	(frac16)-30893,	(frac16)-29465,	(frac16)-28020,	(frac16)-26557,
	(frac16)-25079,	(frac16)-23586,	(frac16)-22078,	(frac16)-20557,
	(frac16)-19024,	(frac16)-17479,	(frac16)-15923,	(frac16)-14359,
	(frac16)-12785,	(frac16)-11204,	(frac16)-9616,	(frac16)-8022,
	(frac16)-6423,	(frac16)-4821,	(frac16)-3215,	(frac16)-1608,
	(frac16)0,		(frac16)1608,	(frac16)3215,	(frac16)4821,
	(frac16)6423,	(frac16)8022,	(frac16)9616,	(frac16)11204,
	(frac16)12785,	(frac16)14359,	(frac16)15923,	(frac16)17479,
	(frac16)19024,	(frac16)20557,	(frac16)22078,	(frac16)23586,
	(frac16)25079,	(frac16)26557,	(frac16)28020,	(frac16)29465,
	(frac16)30893,	(frac16)32302,	(frac16)33692,	(frac16)35061,
	(frac16)36409,	(frac16)37736,	(frac16)39039,	(frac16)40319,
	(frac16)41575,	(frac16)42806,	(frac16)44011,	(frac16)45189,
	(frac16)46340,	(frac16)47464,	(frac16)48558,	(frac16)49624,
	(frac16)50660,	(frac16)51665,	(frac16)52639,	(frac16)53581,
	(frac16)54491,	(frac16)55368,	(frac16)56212,	(frac16)57022,
	(frac16)57797,	(frac16)58538,	(frac16)59243,	(frac16)59913,
	(frac16)60547,	(frac16)61144,	(frac16)61705,	(frac16)62228,
	(frac16)62714,	(frac16)63162,	(frac16)63571,	(frac16)63943,
	(frac16)64276,	(frac16)64571,	(frac16)64826,	(frac16)65043,
	(frac16)65220,	(frac16)65358,	(frac16)65457,	(frac16)65516
};

static const frac16 *sinTable = &(sinCosTable[0]);
static const frac16 *cosTable = &(sinCosTable[64]);

int32 *ws_GetDataFormats() {
	return &dataFormats[0];
}

bool ws_InitCruncher(void) {
	// Make sure the cruncher has not been initialized
	if (_GWS(cruncherInitialized))
		error_show(FL, 'WSCR');

	// Register the end of sequence struct with the stash manager
	mem_register_stash_type(&_GWS(memtypeEOS), sizeof(EOSreq), 32, "+EOS");
	if (_GWS(memtypeEOS) < 0)
		error_show(FL, 'WSCE');

	if ((_GWS(myCruncher) = (cruncher *)mem_alloc(sizeof(cruncher), "cruncher")) == nullptr)
		error_show(FL, 'OOM!', "%d bytes.", sizeof(cruncher));

	_GWS(myCruncher)->backLayerAnim8 = nullptr;
	_GWS(myCruncher)->frontLayerAnim8 = nullptr;
	_GWS(myCruncher)->firstAnim8ToCrunch = nullptr;
	_GWS(myCruncher)->lastAnim8ToCrunch = nullptr;

	// Set up stack
	_GWS(stackSize) = 2048;
	if ((_GWS(stackBase) = (uint32 *)mem_alloc(_GWS(stackSize), "crunchstack")) == nullptr) {
		error_show(FL, 'OOM!', "%d bytes.", _GWS(stackSize));
	}
	_GWS(stackTop) = _GWS(stackBase);
	_GWS(stackLimit) = (uint32 *)((byte *)_GWS(stackBase) + (uint32)_GWS(stackSize));

	_GWS(cruncherInitialized) = true;

	return true;
}

void ws_KillCruncher() {
	if (_GWS(cruncherInitialized)) {
		Anim8 *myAnim8 = _GWS(myCruncher)->firstAnim8ToCrunch;
		while (myAnim8) {
			_GWS(myCruncher)->firstAnim8ToCrunch = myAnim8->next;
			if (myAnim8->myCCB) {
				KillCCB(myAnim8->myCCB, false);
			}
			mem_free(myAnim8->myRegs);
			myAnim8 = _GWS(myCruncher)->firstAnim8ToCrunch;
		}
		mem_free(_GWS(myCruncher));
		if (_GWS(stackBase)) {
			mem_free(_GWS(stackBase));
		}

		_GWS(cruncherInitialized) = false;
	}
}

Anim8 *ws_AddAnim8ToCruncher(machine *m, int32 sequHash) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_AddAnim8ToCruncher()");

	// Allocate an anim8 structure
	Anim8 *myAnim8 = (Anim8 *)mem_alloc(sizeof(Anim8), "Anim8");
	if (myAnim8 == nullptr) {
		ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(Anim8));
		return nullptr;
	}

	// Find the sequence
	int32 numLocalVars;
	myAnim8->sequHandle = ws_GetSEQU((uint32)sequHash, &numLocalVars, &myAnim8->pcOffset);
	if (myAnim8->sequHandle == nullptr) {
		mem_free(myAnim8);
		return nullptr;
	}

	// Allocate an array of registers
	frac16 *my_regs = (frac16 *)mem_alloc(sizeof(frac16) * (IDX_COUNT + numLocalVars), "Anim8 regs");
	if (my_regs == nullptr) {
		ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(frac16) * (IDX_COUNT + numLocalVars));
		return nullptr;
	}

	// Initialize the Anim8 structure
	myAnim8->active = true;
	myAnim8->sequHash = sequHash;
	myAnim8->myMachine = m;								// Pointer back to myMachine
	myAnim8->eosReqOffset = -1;
	myAnim8->myParent = m->parentAnim8;					// The parent anim8
	myAnim8->myCCB = nullptr;
	myAnim8->dataHash = m->dataHash;					// The array of data
	myAnim8->dataHandle = m->dataHandle;
	myAnim8->dataOffset = m->dataOffset;
	myAnim8->startTime = 0;
	myAnim8->switchTime = 0;
	myAnim8->flags = 0;
	myAnim8->numLocalVars = numLocalVars;
	myAnim8->myRegs = my_regs;
	myAnim8->returnStackIndex = 0;

	// Link it into the execution list
	myAnim8->next = nullptr;
	myAnim8->prev = _GWS(myCruncher)->lastAnim8ToCrunch;
	if (_GWS(myCruncher)->lastAnim8ToCrunch) {
		_GWS(myCruncher)->lastAnim8ToCrunch->next = myAnim8;
	} else {
		_GWS(myCruncher)->firstAnim8ToCrunch = myAnim8;
	}
	_GWS(myCruncher)->lastAnim8ToCrunch = myAnim8;

	// Now link it into the layering list
	myAnim8->myLayer = 0;
	myAnim8->infront = nullptr;
	myAnim8->behind = _GWS(myCruncher)->frontLayerAnim8;
	if (_GWS(myCruncher)->frontLayerAnim8) {
		_GWS(myCruncher)->frontLayerAnim8->infront = myAnim8;
	} else {
		_GWS(myCruncher)->backLayerAnim8 = myAnim8;
	}
	_GWS(myCruncher)->frontLayerAnim8 = myAnim8;

	// Now clear the registers, but set the scale = 100%
	for (int32 i = 0; i < IDX_COUNT + numLocalVars; i++) {
		myAnim8->myRegs[i] = 0;
	}
	myAnim8->myRegs[IDX_S] = 0x10000;
	myAnim8->myRegs[IDX_MACH_ID] = m->machID;

	return myAnim8;
}

bool ws_ChangeAnim8Program(machine *m, int32 newSequHash) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_ChangeAnim8Program()");

	// Parameter verification
	if ((!m) || (!m->myAnim8)) {
		error_show(FL, 'WSMI');
	}

	Anim8 *myAnim8 = m->myAnim8;

	// Find the sequence
	int32 numLocalVars;
	myAnim8->sequHandle = ws_GetSEQU((uint32)newSequHash, &numLocalVars, &myAnim8->pcOffset);
	if (myAnim8->sequHandle == nullptr) {
		return false;
	}

	// Now see if we've started a sequence requiring more vars than the prev
	if (myAnim8->numLocalVars < numLocalVars) {
		ws_LogErrorMsg(FL, "Can't launch a sequence with more local vars than the previous sequence.");
		return false;
	}

	// Intialize the Anim8
	myAnim8->switchTime = 0;
	myAnim8->active = true;
	myAnim8->eosReqOffset = -1;
	myAnim8->sequHash = newSequHash;
	myAnim8->returnStackIndex = 0;

	return true;
}

void ws_RemoveAnim8FromCruncher(Anim8 *myAnim8) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_RemoveAnim8FromCruncher()");

	if (!myAnim8)
		error_show(FL, 'WSAI');

	// In case we are crunching the current list of EOS requests, remove any for this machine
	EOSreq *tempEOSreq = _GWS(EOSreqList);
	while (tempEOSreq && (tempEOSreq->myAnim8 != myAnim8)) {
		tempEOSreq = tempEOSreq->next;
	}

	if (tempEOSreq) {
		if (tempEOSreq->next) {
			tempEOSreq->next->prev = tempEOSreq->prev;
		}
		if (tempEOSreq->prev) {
			tempEOSreq->prev->next = tempEOSreq->next;
		} else {
			_GWS(EOSreqList) = tempEOSreq->next;
		}

		mem_free_to_stash((void *)tempEOSreq, _GWS(memtypeEOS));
	}

	// Incase we are in the middle of crunching
	if (myAnim8 == _GWS(crunchNext)) {
		_GWS(crunchNext) = myAnim8->next;
	}

	// Remove myAnim8 from the crunch list
	if (myAnim8->prev) {
		myAnim8->prev->next = myAnim8->next;
	} else {
		_GWS(myCruncher)->firstAnim8ToCrunch = myAnim8->next;
	}

	if (myAnim8->next) {
		myAnim8->next->prev = myAnim8->prev;
	} else {
		_GWS(myCruncher)->lastAnim8ToCrunch = myAnim8->prev;
	}

	// Now remove it from the anim8 layer
	if (myAnim8->infront) {
		myAnim8->infront->behind = myAnim8->behind;
	} else {
		_GWS(myCruncher)->frontLayerAnim8 = myAnim8->behind;
	}
	if (myAnim8->behind) {
		myAnim8->behind->infront = myAnim8->infront;
	} else {
		_GWS(myCruncher)->backLayerAnim8 = myAnim8->infront;
	}

	// Clean up and free the CCB for this anim8
	if (myAnim8->myCCB) {
		KillCCB(myAnim8->myCCB, true);
	}

	// Deallocate the register list
	mem_free(myAnim8->myRegs);

	// Finally, turf myAnim8
	mem_free(myAnim8);
}


// This procedure flags the anim8 slot as inactive, but still owned by the machine
bool ws_PauseAnim8(Anim8 *myAnim8) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_PauseAnim8()");

	if (!myAnim8)
		error_show(FL, 'WSAI');

	myAnim8->active = false;
	HideCCB(myAnim8->myCCB);
	return true;
}

// This procedure reactivates the anim8 slot owned by the machine
bool ws_ResumeAnim8(Anim8 *myAnim8) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_PauseAnim8()");

	if (!myAnim8)
		error_show(FL, 'WSAI');

	myAnim8->active = true;
	if (myAnim8->myCCB) {
		ShowCCB(myAnim8->myCCB);
		myAnim8->myCCB->flags |= CCB_SKIP;
	}

	return true;
}

static bool ExtractArg(Anim8 *myAnim8, int32 myFormat, int32 myData, frac16 **argPtr, frac16 *argValue) {
	int32 myIndex;
	Anim8 *parentAnim8;
	uint32 *dataArray;
	Common::String prefix;

	// If the format indicates the argument is a local source (parent, register, or data)
	if (myFormat == FMT_LOCAL_SRC) {
		if (!myAnim8) {
			ws_LogErrorMsg(FL, "INTERNAL ERROR - ExtractArg() failed - An invalid Anim8* was passed.");
			return false;
		}

		// Find out if the index has been previously stored in a special index register
		if (myData & REG_SET_IDX_REG) {
			myIndex = _GWS(indexReg);
			prefix = "S";
		} else {
			// Else the index is part of the data segment for this arg
			myIndex = myData & REG_SET_IDX;
		}

		// Find the right register set
		switch (myData & LOCAL_FMT) {
		case LOCAL_FMT_PARENT:
			parentAnim8 = myAnim8->myParent;

			// Range check to make sure we don't index off into hyperspace
			if ((!parentAnim8) || (myIndex >= IDX_COUNT + parentAnim8->numLocalVars)) {
				if (!parentAnim8) {
					ws_LogErrorMsg(FL, "Trying to access a parent register - no parent exists");
				} else {
					ws_LogErrorMsg(FL, "Parent Reg Index out of range - max: %d, requested %d.",
						IDX_COUNT + parentAnim8->numLocalVars, myIndex);
				}
				return false;
			}
			*argPtr = &parentAnim8->myRegs[myIndex];
			prefix += "P";
			dbg_AddRegParamToCurrMachInstr(myIndex, prefix.c_str());
			break;

		case LOCAL_FMT_REG:
			// Range check to make sure we don't index off into hyperspace
			if (myIndex >= IDX_COUNT + myAnim8->numLocalVars) {
				ws_LogErrorMsg(FL, "Register Index out of range - max: %d, requested %d.",
					IDX_COUNT + myAnim8->numLocalVars, myIndex);
				return false;
			}
			*argPtr = &myAnim8->myRegs[myIndex];
			dbg_AddRegParamToCurrMachInstr(myIndex, prefix.c_str());
			break;

		case LOCAL_FMT_DATA:
			// Ensure we have a dataHandle
			if (!myAnim8->dataHandle || !*(myAnim8->dataHandle)) {
				ws_LogErrorMsg(FL, "Trying to access a DATA field when no DATA has been set");
				return false;
			}

			// Dereferrence the dataHandle, add the offset to find the array of data for this anim8
			dataArray = (uint32 *)((intptr)*(myAnim8->dataHandle) + myAnim8->dataOffset);

			// Copy the data field into dataArg1, and set myArg1 to point to this location
			*argValue = (int32)FROM_LE_32(dataArray[myIndex]);
			*argPtr = argValue;
			prefix += Common::String::format("DATA %d", myIndex);
			dbg_AddParamToCurrMachInstr(prefix.c_str());
			break;

		default:
			break;
		}
	} else if (myFormat == FMT_GLOBAL_SRC) {
		// Else if the format indicates the argument is from the ws_globals register set
		// Find out if the index has been previously stored in a special index register
		if (myData & REG_SET_IDX_REG) {
			myIndex = _GWS(indexReg);
			prefix = "S";
		} else {
			// Else the index is part of the data segment for this arg
			myIndex = myData & REG_SET_IDX;
		}

		// Finally, set myArg1 to point to the location in the ws_globals array, whichever index
		*argPtr = &(_GWS(ws_globals)[myIndex]);
		dbg_AddGlobalParamToCurrMachInstr(myIndex, prefix.c_str());
	} else {
		// Else the argument is not a variable, but an actual value

		// The top bit of the data segment is a negative flag, the format determines how far the other
		// 15 bits of the data segment are shifted left, so the value requested is in frac16 format.
		// The value is stored in the frac16 (dataArg1), and...
		if (myData & OP_DATA_SIGN) {
			*argValue = -(myData & OP_DATA_VALUE) << dataFormats[myFormat - 3];
		} else {
			*argValue = (myData & OP_DATA_VALUE) << dataFormats[myFormat - 3];
		}

		// myArg1 will point to this location
		*argPtr = argValue;
		prefix += Common::String::format("%" PRIdPTR, *argValue);
		dbg_AddParamToCurrMachInstr(prefix.c_str());
	}

	return true;
}

int32 ws_PreProcessPcode(uint32 **PC, Anim8 *myAnim8) {
	uint32 word2;

	if (!PC) {
		ws_LogErrorMsg(FL, "INTERNAL ERROR - ws_PreProcessPcode() failed - An invalid PC was passed.");
		return -1;
	}

	uint32 *myPC = *PC;

	// Get the opCode
	const uint32 opCode = FROM_LE_32(*myPC++);

	// Get the instruction number
	const int32 myInstruction = (opCode & OP_INSTR) >> 25;
	dbg_AddOpcodeToMachineInstr(myInstruction);

	// Get the format for the first arg 
	int32 myFormat = (opCode & OP_FORMAT1) >> 22;

	// Get the data for the first arg
	int32 myData = opCode & OP_LOW_DATA;

	// Verify we have an argument
	if (myFormat) {
		if (!ExtractArg(myAnim8, myFormat, myData, &_GWS(myArg1), &_GWS(dataArg1))) {
			return -1;
		}
	} else {
		// Otherwise this argument is called with no args
		_GWS(myArg1) = nullptr;
		_GWS(myArg2) = nullptr;
		_GWS(myArg3) = nullptr;
		*PC = myPC;
		return myInstruction;
	}

	// Check for arg2
	myFormat = (opCode & OP_FORMAT2) >> 19;
	if (myFormat) {
		word2 = FROM_LE_32(*myPC++);
		myData = word2 >> 16;
		if (!ExtractArg(myAnim8, myFormat, myData, &_GWS(myArg2), &_GWS(dataArg2))) {
			return -1;
		}
	} else {
		_GWS(myArg2) = nullptr;
		_GWS(myArg3) = nullptr;
		*PC = myPC;
		return myInstruction;
	}

	// Finally check for arg3
	myFormat = (opCode & OP_FORMAT3) >> 16;
	if (myFormat) {
		myData = word2 & OP_LOW_DATA;
		if (!ExtractArg(myAnim8, myFormat, myData, &_GWS(myArg3), &_GWS(dataArg3))) {
			return -1;
		}
	} else {
		_GWS(myArg3) = nullptr;
		*PC = myPC;
		return myInstruction;
	}

	*PC = myPC;
	return myInstruction;
}


static void op_END(Anim8 *myAnim8) {
	_GWS(terminated) = true;
	_GWS(keepProcessing) = false;
}

static void op_CLEAR(Anim8 *myAnim8) {
	// Now clear the registers, but set the scale = 100%
	for (int32 i = 0; i <= IDX_COUNT + myAnim8->numLocalVars; i++) {
		myAnim8->myRegs[i] = 0;
	}
	myAnim8->myRegs[IDX_S] = 0x10000;
	myAnim8->myRegs[IDX_MACH_ID] = myAnim8->myMachine->machID;
}

static void op_SET(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = arg2  or  arg1 = rand(arg2, arg3)");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		*_GWS(myArg1) = *_GWS(myArg2);
	}
}

static void op_COMPARE(Anim8 *myAnim8) {
	frac16 myArg;
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: cmp arg1, arg2  or  cmp arg1, rand(arg2, arg3) **sets CCR");
	}
	if (_GWS(myArg3)) {
		myArg = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		myArg = *_GWS(myArg2);
	}
	if (*_GWS(myArg1) < myArg) {
		_GWS(compareCCR) = -1;
	} else if (*_GWS(myArg1) > myArg) {
		_GWS(compareCCR) = 1;
	} else {
		_GWS(compareCCR) = 0;
	}
}

static void op_ADD(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 += arg2  or  arg1 += rand(arg2, arg3)");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) += imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		*_GWS(myArg1) += *_GWS(myArg2);
	}
}

static void op_SUB(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 -= arg2  or  arg1 -= rand(arg2, arg3)");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) -= imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		*_GWS(myArg1) -= *_GWS(myArg2);
	}
}

static void op_MULT(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 *= arg2  or  arg1 *= rand(arg2, arg3)");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) = MulSF16(*_GWS(myArg1), imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)));
	} else {
		*_GWS(myArg1) = MulSF16(*_GWS(myArg1), *_GWS(myArg2));
	}
}

static void op_DIV(Anim8 *myAnim8) {
	frac16	divisor;
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 /= arg2  or  arg1 /= rand(arg2, arg3)");
	}
	if (_GWS(myArg3)) {
		divisor = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		divisor = *_GWS(myArg2);
	}
	if (divisor == 0) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0253, nullptr);
	} else {
		*_GWS(myArg1) = DivSF16(*_GWS(myArg1), divisor);
	}
}

static void op_SIN(Anim8 *myAnim8) {
	int32 tempAngle;

	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = sin(arg2)  or  arg1 = sin(rand(arg2, arg3))");
	}
	if (_GWS(myArg3)) {
		tempAngle = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)) >> 16;
	} else {
		tempAngle = *_GWS(myArg2) >> 16;
	}
	if (tempAngle < 0) {
		tempAngle = 0x0100 - ((-tempAngle) & 0xff);
	} else {
		tempAngle &= 0xff;
	}

	*_GWS(myArg1) = -(int)cosTable[tempAngle];
}

static void op_COS(Anim8 *myAnim8) {
	int32 tempAngle;

	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = cos(arg2)  or  arg1 = cos(rand(arg2, arg3))");
	}
	if (_GWS(myArg3)) {
		tempAngle = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)) >> 16;
	} else {
		tempAngle = *_GWS(myArg2) >> 16;
	}
	if (tempAngle < 0) {
		tempAngle = 0x0100 - ((-tempAngle) & 0xff);
	} else {
		tempAngle &= 0xff;
	}

	*_GWS(myArg1) = sinTable[tempAngle];
}

static void op_AND(Anim8 *myAnim8) {
	frac16 myArg;

	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 &= arg2  or  arg1 &= rand(arg2, arg3) **also sets CCR");
	}
	if (_GWS(myArg3)) {
		myArg = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		myArg = *_GWS(myArg2);
	}
	if ((*_GWS(myArg1)) & myArg) {
		_GWS(compareCCR) = 0;
	} else {
		_GWS(compareCCR) = 1;
	}

	*_GWS(myArg1) &= myArg;
}

static void op_OR(Anim8 *myAnim8) {
	frac16	myArg;

	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 |= arg2  or  arg1 |= rand(arg2, arg3) **also sets CCR");
	}
	if (_GWS(myArg3)) {
		myArg = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		myArg = *_GWS(myArg2);
	}
	if ((*_GWS(myArg1)) | myArg) {
		_GWS(compareCCR) = 0;
	} else {
		_GWS(compareCCR) = 1;
	}

	*_GWS(myArg1) |= myArg;
}

static void op_NOT(Anim8 *myAnim8) {
	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: arg1 = (arg1 ? 0 : 1) **also sets CCR");
	}
	if (*_GWS(myArg1) == 0) {
		*_GWS(myArg1) = 0x10000;
		_GWS(compareCCR) = 1;
	} else {
		*_GWS(myArg1) = 0;
		_GWS(compareCCR) = 0;
	}
}

static void op_ABS(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = abs(arg2)");
	}
	if (*_GWS(myArg2) < 0) {
		*_GWS(myArg1) = -(int)(*_GWS(myArg2));
	} else {
		*_GWS(myArg1) = *_GWS(myArg2);
	}
}

static void op_MIN(Anim8 *myAnim8) {
	if (!_GWS(myArg3)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0252, "functionality: arg1 = min(arg2, arg3)");
	}
	if (*_GWS(myArg2) < *_GWS(myArg3)) {
		*_GWS(myArg1) = *_GWS(myArg2);
	} else {
		*_GWS(myArg1) = *_GWS(myArg3);
	}
}

static void op_MAX(Anim8 *myAnim8) {
	if (!_GWS(myArg3)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0252, "functionality: arg1 = max(arg2, arg3)");
	}
	if (*_GWS(myArg2) < *_GWS(myArg3)) {
		*_GWS(myArg1) = *_GWS(myArg3);
	} else {
		*_GWS(myArg1) = *_GWS(myArg2);
	}
}

static void op_MOD(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 %= arg2  or  arg1 = arg2%arg3");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) = (*_GWS(myArg1)) % (imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)));
	} else {
		*_GWS(myArg1) = (*_GWS(myArg1)) % (*_GWS(myArg2));
	}
}

static void op_FLOOR(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = floor(arg2)  or  arg1 = floor(rand(arg2,arg3))");
	}
	if (_GWS(myArg3)) {
		*_GWS(myArg1) = (imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3)) >> 16) << 16;
	} else {
		*_GWS(myArg1) = (*_GWS(myArg2) >> 16) << 16;
	}
}

static void op_ROUND(Anim8 *myAnim8) {
	frac16 myArg;
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = round(arg2)  or  arg1 = round(rand(arg2,arg3))");
	}
	if (_GWS(myArg3)) {
		myArg = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		myArg = *_GWS(myArg2);
	}
	if ((myArg & 0xffff) >= 0x8000) {
		*_GWS(myArg1) = ((myArg + 0x10000) >> 16) << 16;
	} else {
		*_GWS(myArg1) = (myArg >> 16) << 16;
	}
}

static void op_CEIL(Anim8 *myAnim8) {
	frac16 myArg;
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = ceil(arg2)  or  arg1 = ceil(rand(arg2,arg3))");
	}
	if (_GWS(myArg3)) {
		myArg = imath_ranged_rand16(*_GWS(myArg2), *_GWS(myArg3));
	} else {
		myArg = *_GWS(myArg2);
	}
	if ((myArg & 0xffff) > 0) {
		*_GWS(myArg1) = ((myArg >> 16) << 16) + 0x10000;
	} else {
		*_GWS(myArg1) = (myArg >> 16) << 16;
	}
}

static void op_POINT(Anim8 *myAnim8) {
	if (!_GWS(myArg3)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0252, "functionality: arg1 = angle of line segment (x, y) , (arg2, arg3)");
	}
	*_GWS(myArg1) = Atan2F16(-(int)(*_GWS(myArg3)) + myAnim8->myRegs[IDX_Y], *_GWS(myArg2) - myAnim8->myRegs[IDX_X]);
}

static void op_DIST2D(Anim8 *myAnim8) {
	if (!_GWS(myArg3)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0252, "functionality: arg1 = distance from (x, y) to (arg2, arg3)");
	}
	int32 temp1 = (int32)(imath_abs(*_GWS(myArg2) - myAnim8->myRegs[IDX_X]));
	int32 temp2 = (int32)(imath_abs(*_GWS(myArg3) - myAnim8->myRegs[IDX_Y]));
	if ((temp1 >= 0x800000) || (temp2 >= 0x800000)) {
		temp1 >>= 16;
		temp2 >>= 16;
		*_GWS(myArg1) = (frac16)(SqrtF16(temp1 * temp1 + temp2 * temp2) << 16);
	} else {
		*_GWS(myArg1) = SqrtF16(SquareSF16(temp1) + SquareSF16(temp2)) << 8;
	}
}

static void op_CRUNCH(Anim8 *myAnim8) {
	frac16	myArg;
	if (_GWS(myArg2)) {
		myArg = imath_ranged_rand16(*_GWS(myArg1), *_GWS(myArg2));
	} else if (_GWS(myArg1)) {
		myArg = *_GWS(myArg1);
	} else {
		myArg = 0;
	}
	myAnim8->startTime = _GWS(ws_globals)[GLB_TIME];
	if (myArg >= 0) {
		myAnim8->switchTime = _GWS(ws_globals)[GLB_TIME] + (myArg >> 16);
	} else {
		myAnim8->switchTime = -1;
	}
	if (myAnim8->transTime <= 0x10000) {
		myAnim8->flags &= ~((TAG_BEZ | TAG_TARGS) << 16);
	}
	_GWS(keepProcessing) = false;
}

static void op_BRANCH(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "check the CCR, arg1 is the branch type, arg2 is the PC offset");
	}
	const int32 myOffset = *_GWS(myArg2) >> 14;
	switch (*_GWS(myArg1) >> 16) {
	case BRANCH_BR:
		myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BLT:
		if (_GWS(compareCCR) < 0) myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BLE:
		if (_GWS(compareCCR) <= 0) myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BE:
		if (_GWS(compareCCR) == 0) myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BNE:
		if (_GWS(compareCCR) != 0) myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BGE:
		if (_GWS(compareCCR) >= 0) myAnim8->pcOffset += myOffset;
		break;
	case BRANCH_BGT:
		if (_GWS(compareCCR) > 0) myAnim8->pcOffset += myOffset;
		break;
	default:
		break;
	}
}

static void op_SETCEL(Anim8 *myAnim8) {
	int32 myIndex;

	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "setcel(arg1, arg2)  or  setcel(arg1, rand(arg2, arg3))");
	}
	if (_GWS(myArg3)) {
		myIndex = imath_ranged_rand(*_GWS(myArg2) >> 16, *_GWS(myArg3) >> 16);
	} else if (_GWS(myArg2)) {
		myIndex = *_GWS(myArg2) >> 16;
	} else {
		myIndex = (*_GWS(myArg1) & 0xff0000) >> 16;
	}

	if (!myAnim8->myCCB) {
		// Allocate and initialize a CCB structure
		if ((myAnim8->myCCB = (CCB *)mem_alloc(sizeof(CCB), "CCB")) == nullptr) {
			ws_LogErrorMsg(FL, "Out of memory - mem requested: %d bytes.", sizeof(CCB));
			ws_Error(myAnim8->myMachine, ERR_SEQU, 0x02fe, "setcel() failed.");
		}
		if (!InitCCB(myAnim8->myCCB)) {
			ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025d, "setcel() failed.");
		}
	}

	CCB *myCCB = myAnim8->myCCB;
	if (myCCB->flags & CCB_DISC_STREAM) {
		ws_CloseSSstream(myCCB);
	}
	ShowCCB(myCCB);
	myCCB->flags |= CCB_SKIP;
	if ((myAnim8->myCCB = GetWSAssetCEL((uint32)(*_GWS(myArg1)) >> 24, (uint32)myIndex, myCCB)) == nullptr) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025b, "setcel() failed.");
	}
	frac16 *myRegs = myAnim8->myRegs;
	if (myRegs[IDX_W] < 0) {
		myRegs[IDX_W] = -myCCB->source->w << 16;
	} else {
		myRegs[IDX_W] = myCCB->source->w << 16;
	}

	myRegs[IDX_H] = myCCB->source->h << 16;
	_GWS(mapTheCel) = true;
}


static void op_SEQ_SEND_MSG(Anim8 *myAnim8) {
	frac16 msgValue;

	//_GWS(myArg1) is the recipient machine hash, _GWS(myArg2) is the msg hash, _GWS(myArg3) (if exists) is the msg value
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: send to machine arg1, the message arg2 or the message arg2, arg3");
	}

	if (_GWS(myArg3)) {
		msgValue = *_GWS(myArg3);
	} else {
		msgValue = 0;
	}

	sendWSMessage(*_GWS(myArg2), msgValue, nullptr, (*_GWS(myArg1)) >> 16, nullptr, 1);
	return;
}


static void op_PUSH(Anim8 *myAnim8) {
	int32 numOfArgs;

	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: push arg1  or start with arg1, and push a total of arg2 values");
	}
	int32 direction = 1;
	if (_GWS(myArg2)) {
		if (*_GWS(myArg2) > 0) numOfArgs = (*_GWS(myArg2)) >> 16;
		else {
			numOfArgs = -(int)(*_GWS(myArg2)) >> 16;
			direction = -1;
		}
	} else {
		numOfArgs = 1;
	}

	if (((byte *)_GWS(stackLimit) - (byte *)_GWS(stackTop)) < (numOfArgs << 2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0254, "overflow during push instruction");
		return;
	}
	if (_GWS(myArg2)) {
		uint32 *data = (uint32 *)_GWS(myArg1);
		for (int32 i = 0; i < numOfArgs; i++) {
			*_GWS(stackTop)++ = *data;
			data += direction;
		}
	} else {
		*_GWS(stackTop)++ = (uint32)(*_GWS(myArg1));
	}
}

static void op_POP(Anim8 *myAnim8) {
	int32 numOfArgs;

	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: pop into arg1  or start with arg1, and pop a total of arg2 values");
	}
	int32 direction = 1;
	if (_GWS(myArg2)) {
		if (*_GWS(myArg2) > 0) numOfArgs = (*_GWS(myArg2)) >> 16;
		else {
			numOfArgs = -(int)(*_GWS(myArg2)) >> 16;
			direction = -1;
		}
	} else
		numOfArgs = 1;
	if (((byte *)_GWS(stackTop) - (byte *)_GWS(stackBase)) < (numOfArgs << 2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0255, "underflow during pop instruction");
	}
	if (_GWS(myArg2)) {
		uint32 *data = (uint32 *)_GWS(myArg1);
		for (int32 i = 0; i < numOfArgs; i++) {
			*data = *(--_GWS(stackTop));
			data += direction;
		}
	} else {
		*_GWS(myArg1) = (frac16)(*(--_GWS(stackTop)));
	}
}

static void op_JSR(Anim8 *myAnim8) {
	if (myAnim8->returnStackIndex >= JSR_STACK_MAX) {
		ws_LogErrorMsg(FL, "Max number of nested jsr instructions is: %d", JSR_STACK_MAX);
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0256, "jsr() failed");
	}
	myAnim8->returnHashes[myAnim8->returnStackIndex] = myAnim8->sequHash;
	myAnim8->returnOffsets[myAnim8->returnStackIndex] = myAnim8->pcOffset;
	myAnim8->returnStackIndex++;

	// Find the sequence
	int32 dummy;
	myAnim8->sequHandle = ws_GetSEQU((uint32)*_GWS(myArg1) >> 16, &dummy, &myAnim8->pcOffset);
	if (myAnim8->sequHandle == nullptr) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025f, "jsr() failed");
	}
	myAnim8->sequHash = (uint32)*_GWS(myArg1) >> 16;

	dbg_LaunchSequence(myAnim8);
}

static void op_RETURN(Anim8 *myAnim8) {
	if (myAnim8->returnStackIndex <= 0) {
		op_END(myAnim8);
		return;
	}

	myAnim8->returnStackIndex--;
	const uint32 returnSequHash = myAnim8->returnHashes[myAnim8->returnStackIndex];
	const uint32 returnOffset = myAnim8->returnOffsets[myAnim8->returnStackIndex];

	// Find the sequence
	int32 dummy, dummy2;
	myAnim8->sequHandle = ws_GetSEQU((uint32)returnSequHash, &dummy, &dummy2);
	if (myAnim8->sequHandle == nullptr) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025f, "return() failed");
	}
	myAnim8->sequHash = returnSequHash;
	myAnim8->pcOffset = returnOffset;

	dbg_LaunchSequence(myAnim8);
}

static void op_GET_CELS_COUNT(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = series_count(arg2)");
	}
	*_GWS(myArg1) = GetWSAssetCELCount((uint32)(*_GWS(myArg2)) >> 24) << 16;
}

static void op_GET_CELS_FRAME_RATE(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = series_frame_rate(arg2)");
	}
	*_GWS(myArg1) = GetWSAssetCELFrameRate((uint32)(*_GWS(myArg2)) >> 24);
}

static void op_GET_CELS_PIX_SPEED(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: arg1 = series_pix_speed(arg2)");
	}
	*_GWS(myArg1) = GetWSAssetCELPixSpeed((uint32)(*_GWS(myArg2)) >> 24);
}

static void op_SET_INDEX(Anim8 *myAnim8) {
	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: index_reg = arg1");
	}
	_GWS(indexReg) = *_GWS(myArg1) >> 16;
}

static void op_SET_LAYER(Anim8 *myAnim8) {
	Anim8 *tempAnim8;
	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: set_layer(arg1)");
	}

	const int32 newLayer = *_GWS(myArg1) >> 16;
	const int32 myLayer = myAnim8->myLayer;

	if (myLayer == newLayer) {
		return;
	}

	// If we are moving myAnim8 closer to the front (screen)	
	if ((newLayer < myLayer) && myAnim8->infront) {

		//search "upward" to find the new position for myAnim8
		tempAnim8 = myAnim8->infront;
		while (tempAnim8 && (tempAnim8->myLayer > newLayer)) {
			tempAnim8 = tempAnim8->infront;
		}

		// If myAnim8 is to be moved
		if (tempAnim8 != myAnim8->infront) {

			// Remove myAnim8 from the list
			myAnim8->infront->behind = myAnim8->behind;
			if (myAnim8->behind) {
				myAnim8->behind->infront = myAnim8->infront;
			} else {
				_GWS(myCruncher)->backLayerAnim8 = myAnim8->infront;
			}

			// If it belongs at the top layer of the list
			if (!tempAnim8) {
				myAnim8->infront = nullptr;
				myAnim8->behind = _GWS(myCruncher)->frontLayerAnim8;
				_GWS(myCruncher)->frontLayerAnim8->infront = myAnim8;
				_GWS(myCruncher)->frontLayerAnim8 = myAnim8;
			}

			// Else it belongs after tempAnim8
			else {
				myAnim8->infront = tempAnim8;
				myAnim8->behind = tempAnim8->behind;
				tempAnim8->behind->infront = myAnim8;
				tempAnim8->behind = myAnim8;
			}
		}
	}

	// Else we are moving myAnim8 close to the back (further from the screen)
	else if ((newLayer > myLayer) && myAnim8->behind) {

		//search "downward" to find the new position for myAnim8
		tempAnim8 = myAnim8->behind;
		while (tempAnim8 && (tempAnim8->myLayer < newLayer)) {
			tempAnim8 = tempAnim8->behind;
		}

		// If myAnim8 is to be moved
		if (tempAnim8 != myAnim8->behind) {

			// Remove it from the list
			if (myAnim8->infront) {
				myAnim8->infront->behind = myAnim8->behind;
			} else {
				_GWS(myCruncher)->frontLayerAnim8 = myAnim8->behind;
			}
			myAnim8->behind->infront = myAnim8->infront;

			// If it belongs at the bottom of the list
			if (!tempAnim8) {
				myAnim8->infront = _GWS(myCruncher)->backLayerAnim8;
				myAnim8->behind = nullptr;
				_GWS(myCruncher)->backLayerAnim8->behind = myAnim8;
				_GWS(myCruncher)->backLayerAnim8 = myAnim8;
			}

			// Else it belongs right before tempAnim8
			else {
				myAnim8->infront = tempAnim8->infront;
				myAnim8->behind = tempAnim8;
				tempAnim8->infront->behind = myAnim8;
				tempAnim8->infront = myAnim8;
			}
		}
	}

	// Now, make sure the layer is set in both myAnim8, and the register
	myAnim8->myLayer = newLayer;
	myAnim8->myRegs[IDX_LAYER] = newLayer << 16;
}

static void op_SET_DEPTH(Anim8 *myAnim8) {
	int32 myDepth;
	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: set_depth(arg1)");
	}
	if (!_GWS(myDepthTable)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x02ff, "op_SET_DEPTH() failed - no depth table.");
	}
	for (myDepth = 0; myDepth < 15; myDepth++) {
		if (_GWS(myDepthTable)[myDepth + 1] < (int)(*_GWS(myArg1) >> 16)) break;
	}
	_GWS(dataArg1) = (myAnim8->myRegs[IDX_LAYER] & 0xffffff) + (myDepth << 24);
	_GWS(myArg1) = &_GWS(dataArg1);
	op_SET_LAYER(myAnim8);
}

static void op_SET_DATA(Anim8 *myAnim8) {
	if (!_GWS(myArg2)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0251, "functionality: set_data(arg1, arg2)");
	}
	if ((myAnim8->dataHandle = ws_GetDATA(*_GWS(myArg1) >> 16, *_GWS(myArg2) >> 16, &myAnim8->dataOffset)) == nullptr) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025f, "set_data() failed.");
	}
}

static void op_OPEN_STREAM_SS(Anim8 *myAnim8) {
	if (!_GWS(myArg1)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0250, "functionality: stream_series(arg1)");
	}

	if (!myAnim8->myCCB) {
		// Allocate and initialize a CCB structure
		if ((myAnim8->myCCB = (CCB *)mem_alloc(sizeof(CCB), "CCB")) == nullptr) {
			ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(CCB));
			ws_Error(myAnim8->myMachine, ERR_SEQU, 0x02fe, "open_ss_stream() failed.");
		}
		if (!InitCCB(myAnim8->myCCB)) {
			ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025d, "open_ss_stream() failed.");
		}
	}

	CCB *myCCB = myAnim8->myCCB;
	ShowCCB(myCCB);
	myCCB->flags |= CCB_SKIP;

	if (!ws_OpenSSstream((SysFile *)(*_GWS(myArg1)), myAnim8)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0258, "open_ss_stream() failed.");
	}

	if (myAnim8->myRegs[IDX_W] < 0)
		myAnim8->myRegs[IDX_W] = -myCCB->source->w << 16;
	else
		myAnim8->myRegs[IDX_W] = myCCB->source->w << 16;

	myAnim8->myRegs[IDX_H] = myCCB->source->h << 16;
	_GWS(mapTheCel) = true;
}

static void op_NEXT_STREAM_SS(Anim8 *myAnim8) {
	if (!myAnim8->myCCB) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0253, "next_ss_stream() failed.");
	}
	CCB *myCCB = myAnim8->myCCB;
	myCCB->flags |= CCB_SKIP;
	if (!ws_GetNextSSstreamCel(myAnim8)) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x0259, "next_ss_stream() failed.");
	}
	if (myAnim8->myRegs[IDX_W] < 0) {
		myAnim8->myRegs[IDX_W] = -myCCB->source->w << 16;
	} else {
		myAnim8->myRegs[IDX_W] = myCCB->source->w << 16;
	}
	myAnim8->myRegs[IDX_H] = myCCB->source->h << 16;
	_GWS(mapTheCel) = true;
}

static void op_CLOSE_STREAM_SS(Anim8 *myAnim8) {
	if (!myAnim8->myCCB) {
		ws_Error(myAnim8->myMachine, ERR_SEQU, 0x02f3, "close_ss_stream() failed.");
	}
	CCB *myCCB = myAnim8->myCCB;
	ws_CloseSSstream(myCCB);
	HideCCB(myCCB);
}


void (*pCodeJmpTable[])(Anim8 *myAnim8) = {
	&op_END,					//0
	&op_CLEAR,					//1
	&op_SET,					//2
	&op_COMPARE,				//3
	&op_ADD,					//4
	&op_SUB,					//5
	&op_MULT,					//6
	&op_DIV,					//7
	&op_AND,					//8
	&op_OR,						//9
	&op_NOT,					//10
	&op_SIN,					//11
	&op_COS,					//12
	&op_ABS,					//13
	&op_MIN,					//14
	&op_MAX,					//15
	&op_MOD,					//16
	&op_FLOOR,					//17
	&op_ROUND,					//18
	&op_CEIL,					//19
	&op_POINT,					//20
	&op_DIST2D,					//21
	&op_CRUNCH,					//22
	&op_BRANCH,					//23
	&op_SETCEL,					//24
	&op_SEQ_SEND_MSG,			//25
	&op_PUSH,					//26
	&op_POP,					//27
	&op_JSR,					//28
	&op_RETURN,					//29
	&op_GET_CELS_COUNT,			//30
	&op_GET_CELS_FRAME_RATE,	//31
	&op_GET_CELS_PIX_SPEED,		//32
	&op_SET_INDEX,				//33
	&op_SET_LAYER,				//34
	&op_SET_DEPTH,				//35
	&op_SET_DATA,				//36
	&op_OPEN_STREAM_SS,			//37
	&op_NEXT_STREAM_SS,			//38
	&op_CLOSE_STREAM_SS			//39
};


// The guts of the engine.  This proc executes an anim8s program.
bool CrunchAnim8(Anim8 *myAnim8) {
	bool moveTheCel = false;
	frac16 percentDist;
	uint32 *myPC;

	// Get the register set for myAnim8
	frac16 *myRegs = myAnim8->myRegs;

	// Initialize the globals (flags, mostly) used in processing op codes
	_GWS(keepProcessing) = false;
	_GWS(terminated) = false;
	_GWS(mapTheCel) = false;
	_GWS(compareCCR) = 0;

	//store the old values, so we can tell if we need to remap the Sprite
	const frac16 oldX = myRegs[IDX_X];
	const frac16 oldY = myRegs[IDX_Y];
	const frac16 oldS = myRegs[IDX_S];
	const int32 oldW = myRegs[IDX_W] >> 16;
	const int32 oldH = myRegs[IDX_H] >> 16;
	const int32 oldR = myRegs[IDX_R] >> 16;

	// Check to see if we are still in an execution loop, or if it is time to 
	// Interpret further instructions
	if ((myAnim8->switchTime >= 0) && ((int)_GWS(ws_globals)[GLB_TIME] >= myAnim8->switchTime)) {
		_GWS(keepProcessing) = true;
	}

	// Decrement the timer register
	myRegs[IDX_TIMER] -= (_GWS(ws_globals)[GLB_WATCH_DOG] << 16);

	// Interpret pCode instructions until we hit something signalling to stop
	while (_GWS(keepProcessing)) {
		dbg_SetCurrSequInstr(myAnim8, _GWS(compareCCR));

		myPC = (uint32 *)((intptr)*(myAnim8->sequHandle) + myAnim8->pcOffset);
		uint32 *oldPC = myPC;
		_GWS(pcOffsetOld) = myAnim8->pcOffset;

		dbg_SetCurrMachInstr(myAnim8->myMachine, myAnim8->pcOffset, true);

		const int32 myInstruction = ws_PreProcessPcode(&myPC, myAnim8);
		if (myInstruction < 0 || myInstruction > 39) {
			ws_Error(myAnim8->myMachine, ERR_SEQU, 0x025c, nullptr);
		}

		dbg_EndCurrMachInstr();

		myAnim8->pcOffset += (intptr)myPC - (intptr)oldPC;
		pCodeJmpTable[myInstruction](myAnim8);
	}

	if (_GWS(bailOut)) {
		_GWS(bailOut) = false;
		return true;
	}

	if (_GWS(terminated)) {
		if (_GWS(mapTheCel) || (oldR != (int)(myRegs[IDX_R] >> 16)) ||
				(oldW != (int)(myRegs[IDX_W] >> 16)) || (oldH != (int)(myRegs[IDX_H] >> 16)) ||
				(oldS != (int)myRegs[IDX_S])) {
			myAnim8->flags |= TAG_MAP_CEL;
		} else if ((oldX != myRegs[IDX_X]) || (oldY != myRegs[IDX_Y])) {
			myAnim8->flags |= TAG_MOVE_CEL;
		}

		return false;
	}

	if (myAnim8->flags) {
		const frac16 timeElapsed = (_GWS(ws_globals)[GLB_TIME] - myAnim8->startTime) << 16;

		// This must be checked before TAG_TARGS because a bez path can use a target scale and rotate
		// And we don't want to accidentally set up a target x or y.
		// NOTE: for both bez paths, and targets, the time for the anim8 to reach the target or traverse
		//      the path is stored in IDX_TRANS_TIME, however, in order to determine how far aint32 the
		//      path we should be, we normally compute the elapsed time divided by the trans time.
		//      therefore we eliminate a division if we store the reciprocated trans time.
		//      This also serves as an initialization flag - if the trans time is > 1.
		if ((myAnim8->flags >> 16) &TAG_BEZ) {
			if (myAnim8->transTime > 0x10000) {
				myAnim8->transTime = RecipUF16(myAnim8->transTime);
				GetBezCoeffs(&myRegs[IDX_BEZ_CTRL], &myRegs[IDX_BEZ_COEFF]);
				if ((myAnim8->flags >> 16) &TAG_TARGS) {
					myAnim8->start_s = myRegs[IDX_S];
					myAnim8->start_r = myRegs[IDX_R];
				}
			}
			percentDist = MulSF16(timeElapsed, myAnim8->transTime);
			if (percentDist < 0x10000) {
				GetBezPoint(&myRegs[IDX_X], &myRegs[IDX_Y], &myRegs[IDX_BEZ_COEFF], percentDist);
				if ((myAnim8->flags >> 16) &TAG_TARGS) {
					myRegs[IDX_S] = myAnim8->start_s + MulSF16(percentDist, myRegs[IDX_TARG_S] - myAnim8->start_s);
					myRegs[IDX_R] = myAnim8->start_r + MulSF16(percentDist, myRegs[IDX_TARG_R] - myAnim8->start_r);
					_GWS(mapTheCel) = true;
				} else moveTheCel = true;
			} else if ((myRegs[IDX_X] != myRegs[IDX_BEZ_CTRL + 6]) || (myRegs[IDX_Y] != myRegs[IDX_BEZ_CTRL + 7])) {
				myRegs[IDX_X] = myRegs[IDX_BEZ_CTRL + 6];
				myRegs[IDX_Y] = myRegs[IDX_BEZ_CTRL + 7];
				if ((myAnim8->flags >> 16) &TAG_TARGS) {
					myRegs[IDX_S] = myRegs[IDX_TARG_S];
					myRegs[IDX_R] = myRegs[IDX_TARG_R];
					_GWS(mapTheCel) = true;
				} else moveTheCel = true;
			}
		}
		// Vectors must be checked before deltas, since vectors are converted to deltas at
		// Initialization.
		else if (((myAnim8->flags >> 16) &TAG_VECTORS) && (timeElapsed == 0)) {
			myAnim8->start_x = myRegs[IDX_X];
			myAnim8->start_y = myRegs[IDX_Y];
			if ((myAnim8->flags >> 16) &TAG_DELTAS) {
				myAnim8->start_s = myRegs[IDX_S];
				myAnim8->start_r = myRegs[IDX_R];
			}
			const int32 tempAngle = (myRegs[IDX_THETA] >> 16) & 0xff;
			myRegs[IDX_DELTA_X] = MulSF16(myRegs[IDX_VELOCITY], sinTable[tempAngle]);
			myRegs[IDX_DELTA_Y] = MulSF16(myRegs[IDX_VELOCITY], -(int)cosTable[tempAngle]);
			myAnim8->flags |= (TAG_DELTAS << 16);
		} else if ((myAnim8->flags >> 16) &TAG_DELTAS) {
			if (timeElapsed == 0) {
				myAnim8->start_x = myRegs[IDX_X];
				myAnim8->start_y = myRegs[IDX_Y];
				myAnim8->start_s = myRegs[IDX_S];
				myAnim8->start_r = myRegs[IDX_R];
			} else {
				myRegs[IDX_X] = myAnim8->start_x + MulSF16(timeElapsed, myRegs[IDX_DELTA_X]);
				myRegs[IDX_Y] = myAnim8->start_y + MulSF16(timeElapsed, myRegs[IDX_DELTA_Y]);
				if (myRegs[IDX_DELTA_R] || myRegs[IDX_DELTA_S]) {
					myRegs[IDX_S] = myAnim8->start_s + MulSF16(timeElapsed, myRegs[IDX_DELTA_S]);
					myRegs[IDX_R] = myAnim8->start_r + MulSF16(timeElapsed, myRegs[IDX_DELTA_R]);
					_GWS(mapTheCel) = true;
				} else moveTheCel = true;
			}
		} else if ((myAnim8->flags >> 16) &TAG_TARGS) {
			if (myAnim8->transTime > 0x10000) {
				myAnim8->start_s = myRegs[IDX_S];
				myAnim8->start_r = myRegs[IDX_R];
				myAnim8->start_x = myRegs[IDX_X];
				myAnim8->start_y = myRegs[IDX_Y];
				myAnim8->transTime = RecipUF16(myAnim8->transTime);
			}
			percentDist = MulSF16(timeElapsed, myAnim8->transTime);
			if (percentDist < 0x10000) {
				myRegs[IDX_X] = myAnim8->start_x + MulSF16(percentDist, myRegs[IDX_TARG_X] - myAnim8->start_x);
				myRegs[IDX_Y] = myAnim8->start_y + MulSF16(percentDist, myRegs[IDX_TARG_Y] - myAnim8->start_y);
				if (myRegs[IDX_TARG_R] || (myRegs[IDX_TARG_S] != myRegs[IDX_S])) {
					myRegs[IDX_S] = myAnim8->start_s + MulSF16(percentDist, myRegs[IDX_TARG_S] - myAnim8->start_s);
					myRegs[IDX_R] = myAnim8->start_r + MulSF16(percentDist, myRegs[IDX_TARG_R] - myAnim8->start_r);
					_GWS(mapTheCel) = true;
				} else moveTheCel = true;
			} else {
				myRegs[IDX_X] = myRegs[IDX_TARG_X];
				myRegs[IDX_Y] = myRegs[IDX_TARG_Y];
				myRegs[IDX_S] = myRegs[IDX_TARG_S];
				myRegs[IDX_R] = myRegs[IDX_TARG_R];
				myAnim8->flags &= ~TAG_TARGS;
				_GWS(mapTheCel) = true;
			}
		}
	}
	if (_GWS(mapTheCel) || (oldR != (int)(myRegs[IDX_R] >> 16)) || (oldW != (int)(myRegs[IDX_W] >> 16)) ||
			(oldH != (int)(myRegs[IDX_H] >> 16)) || (oldS != (int)myRegs[IDX_S])) {
		_GWS(mapTheCel) = true;
	} else if ((oldX != myRegs[IDX_X]) || (oldY != myRegs[IDX_Y])) {
		moveTheCel = true;
	}
	if (moveTheCel || _GWS(mapTheCel) || (myAnim8->flags & (TAG_MAP_CEL | TAG_MOVE_CEL))) {
		Cel_msr(myAnim8);
	}
	return true;
}

void ws_CrunchAnim8s(int16 *depth_table) {

	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_CrunchAnim8s()");

	//set up some of the _GWS(ws_globals) vars used for processing
	_GWS(myDepthTable) = depth_table;

	_GWS(crunchNext) = nullptr;
	Anim8 *currAnim8 = _GWS(myCruncher)->firstAnim8ToCrunch;
	while (currAnim8) {
		_GWS(crunchNext) = currAnim8->next;
		if (currAnim8->active) {
			if (!CrunchAnim8(currAnim8)) {
				// If false was returned, this implies that an "END" op has been reached.
				// Remove from the active list.  Note: the machine still points to the anim8.
				currAnim8->active = false;
				if (currAnim8->eosReqOffset >= 0) {
					// If the above field has a value, this implies that an On end sequence
					//signal has been requested. If so, report back to the machine.
					EOSreq *tempEOSreq = (EOSreq *)mem_get_from_stash(_GWS(memtypeEOS), "+EOS");
					if (tempEOSreq == nullptr)
						return;
					tempEOSreq->myAnim8 = currAnim8;
					tempEOSreq->prev = nullptr;
					tempEOSreq->next = _GWS(EOSreqList);
					if (_GWS(EOSreqList)) {
						_GWS(EOSreqList)->prev = tempEOSreq;
					}
					_GWS(EOSreqList) = tempEOSreq;
				}
			}
		}
		currAnim8 = _GWS(crunchNext);
	}
	_GWS(crunchNext) = nullptr;
}

void ws_CrunchEOSreqs(void) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_CrunchEOSreqs()");

	// Loop through, and handle all the eos requests
	EOSreq *tempEOSreq = _GWS(EOSreqList);
	while (tempEOSreq) {
		_GWS(EOSreqList) = _GWS(EOSreqList)->next;
		if (_GWS(EOSreqList)) {
			_GWS(EOSreqList)->prev = nullptr;
		}
		const int32 pcOffset = tempEOSreq->myAnim8->eosReqOffset;
		const int32 pcCount = tempEOSreq->myAnim8->eosReqCount;
		machine *myXM = tempEOSreq->myAnim8->myMachine;
		tempEOSreq->myAnim8->eosReqOffset = -1;
		mem_free_to_stash((void *)tempEOSreq, _GWS(memtypeEOS));
		ws_StepWhile(myXM, pcOffset, pcCount);
		tempEOSreq = _GWS(EOSreqList);
	}
}

// This proc was designed to allow the state machine to issue an OnEndSeq request
// In which the cruncher will signal the state machine should an anim8 ever finish.
bool ws_OnEndSeqRequest(Anim8 *myAnim8, int32 pcOffset, int32 pcCount) {

	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_OnEndSeqRequest()");

	myAnim8->eosReqOffset = pcOffset;
	myAnim8->eosReqCount = pcCount;

	return true;
}

void ws_CancelOnEndSeq(Anim8 *myAnim8) {
	// Make sure the cruncher has been initialized
	VERIFY_INTIALIZED("ws_CancelOnEndSeq()");

	myAnim8->eosReqOffset = -1;
}

} // End of namespace M4
