/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/config-manager.h"
#include "common/endian.h"
#include "common/events.h"
#include "common/system.h"	// for g_system->getEventManager()
#include "common/textconsole.h"

#include "cruise/cruise.h"
#include "cruise/cruise_main.h"
#include "cruise/cell.h"
#include "cruise/staticres.h"

namespace Cruise {

enum RelationType {RT_REL = 30, RT_MSG = 50};

static int playerDontAskQuit;
unsigned int timer = 0;

gfxEntryStruct* linkedMsgList = NULL;

typedef CruiseEngine::MemInfo MemInfo;

void MemoryList() {
	if (!_vm->_memList.empty()) {
		debug("Current list of un-freed memory blocks:");
		Common::List<MemInfo *>::iterator i;
		for (i = _vm->_memList.begin(); i != _vm->_memList.end(); ++i) {
			MemInfo const *const v = *i;
			debug("%s - %d", v->fname, v->lineNum);
		}
	}
}

void *MemoryAlloc(uint32 size, bool clearFlag, int32 lineNum, const char *fname) {
	void *result;

	if (gDebugLevel > 0) {
		// Find the point after the final slash
		const char *fnameP = fname + strlen(fname);
		while ((fnameP > fname) && (*(fnameP - 1) != '/') && (*(fnameP - 1) != '\\'))
			--fnameP;

		// Create the new memory block and add it to the memory list
		MemInfo *const v = (MemInfo *)malloc(sizeof(MemInfo) + size);
		v->lineNum = lineNum;
		strncpy(v->fname, fnameP, sizeof(v->fname));
		v->fname[ARRAYSIZE(v->fname) - 1] = '\0';
		v->magic = MemInfo::cookie;

		// Add the block to the memory list
		_vm->_memList.push_back(v);
		result = v + 1;
	} else
		result = malloc(size);

	if (clearFlag)
		memset(result, 0, size);

	return result;
}

void MemoryFree(void *v) {
	if (!v)
		return;

	if (gDebugLevel > 0) {
		MemInfo *const p = (MemInfo *)v - 1;
		assert(p->magic == MemInfo::cookie);

		_vm->_memList.remove(p);
		free(p);
	} else
		free(v);
}

void drawBlackSolidBoxSmall() {
//  gfxModuleData.drawSolidBox(64,100,256,117,0);
	drawSolidBox(64, 100, 256, 117, 0);
}

void loadPackedFileToMem(int fileIdx, uint8 *buffer) {
	changeCursor(CURSOR_DISK);

	_vm->_currentVolumeFile.seek(volumePtrToFileDescriptor[fileIdx].offset, SEEK_SET);
	_vm->_currentVolumeFile.read(buffer, volumePtrToFileDescriptor[fileIdx].size);
}

int getNumObjectsByClass(int scriptIdx, int param) {
	objDataStruct *ptr2;
	int counter;
	int i;

	if (!overlayTable[scriptIdx].ovlData)
		return (0);

	ptr2 = overlayTable[scriptIdx].ovlData->arrayObject;

	if (!ptr2)
		return (0);

	if (overlayTable[scriptIdx].ovlData->numObj == 0)
		return (0);

	counter = 0;

	for (i = 0; i < overlayTable[scriptIdx].ovlData->numObj; i++) {
		if (ptr2[i]._class == param) {
			counter++;
		}
	}

	return (counter);
}

void resetFileEntryRange(int start, int count) {
	int i;

	for (i = 0; i < count; ++i)
		resetFileEntry(start + i);
}

int getProcParam(int overlayIdx, int param2, const char *name) {
	int numSymbGlob;
	int i;
	exportEntryStruct *arraySymbGlob;
	char *exportNamePtr;
	char exportName[80];

	if (!overlayTable[overlayIdx].alreadyLoaded)
		return 0;

	if (!overlayTable[overlayIdx].ovlData)
		return 0;

	numSymbGlob = overlayTable[overlayIdx].ovlData->numSymbGlob;
	arraySymbGlob = overlayTable[overlayIdx].ovlData->arraySymbGlob;
	exportNamePtr = overlayTable[overlayIdx].ovlData->arrayNameSymbGlob;

	if (!exportNamePtr)
		return 0;

	for (i = 0; i < numSymbGlob; i++) {
		if (arraySymbGlob[i].var4 == param2) {
			strcpy(exportName, arraySymbGlob[i].offsetToName + exportNamePtr);

			if (!strcmp(exportName, name)) {
				return (arraySymbGlob[i].idx);
			}
		}
	}

	return 0;
}

void changeScriptParamInList(int param1, int param2, scriptInstanceStruct *pScriptInstance, int oldFreeze, int newValue) {
	pScriptInstance = pScriptInstance->nextScriptPtr;
	while (pScriptInstance) {
		if ((pScriptInstance->overlayNumber == param1) || (param1 == -1))
			if ((pScriptInstance->scriptNumber == param2) || (param2 == -1))
				if ((pScriptInstance->freeze == oldFreeze) || (oldFreeze == -1)) {
					pScriptInstance->freeze = newValue;
				}

		pScriptInstance = pScriptInstance->nextScriptPtr;
	}
}

void initBigVar3() {
	int i;

	for (i = 0; i < NUM_FILE_ENTRIES; i++) {
		if (filesDatabase[i].subData.ptr) {
			MemFree(filesDatabase[i].subData.ptr);
		}

		filesDatabase[i].subData.ptr = NULL;
		filesDatabase[i].subData.ptrMask = NULL;

		filesDatabase[i].subData.index = -1;
		filesDatabase[i].subData.resourceType = 0;
	}
}

void resetPtr2(scriptInstanceStruct *ptr) {
	ptr->nextScriptPtr = NULL;
	ptr->scriptNumber = -1;
}

void resetActorPtr(actorStruct *ptr) {
	actorStruct *p = ptr;

	if (p->next) {
		p = p->next;
		do {
			actorStruct *pNext = p->next;
			MemFree(p);
			p = pNext;
		} while (p);
	}

	ptr->next = NULL;
	ptr->prev = NULL;
}

ovlData3Struct *getOvlData3Entry(int32 scriptNumber, int32 param) {
	ovlDataStruct *ovlData = overlayTable[scriptNumber].ovlData;

	if (!ovlData) {
		return NULL;
	}

	if (param < 0) {
		return NULL;
	}

	if (ovlData->numProc <= param) {
		return NULL;
	}

	if (!ovlData->arrayProc) {
		return NULL;
	}

	return (&ovlData->arrayProc[param]);
}

ovlData3Struct *scriptFunc1Sub2(int32 scriptNumber, int32 param) {
	ovlDataStruct *ovlData = overlayTable[scriptNumber].ovlData;

	if (!ovlData) {
		return NULL;
	}

	if (param < 0) {
		return NULL;
	}

	if (ovlData->numRel <= param) {
		return NULL;
	}

	if (!ovlData->ptr1) {
		return NULL;
	}

	return &ovlData->ptr1[param];
}

void scriptFunc2(int scriptNumber, scriptInstanceStruct * scriptHandle,
                 int param, int param2) {
	if (scriptHandle->nextScriptPtr) {
		if (scriptNumber == scriptHandle->nextScriptPtr->overlayNumber
		        || scriptNumber != -1) {
			if (param2 == scriptHandle->nextScriptPtr->scriptNumber
			        || param2 != -1) {
				scriptHandle->nextScriptPtr->sysKey = param;
			}
		}
	}
}

uint8 *getDataFromData3(ovlData3Struct *ptr, int param) {
	uint8 *dataPtr;

	if (!ptr)
		return (NULL);

	dataPtr = ptr->dataPtr;

	if (!dataPtr)
		return (NULL);

	switch (param) {
	case 0:
		return (dataPtr);
	case 1:
		return (dataPtr + ptr->offsetToSubData3);	// strings
	case 2:
		return (dataPtr + ptr->offsetToSubData2);
	case 3:
		return (dataPtr + ptr->offsetToImportData);	// import data
	case 4:
		return (dataPtr + ptr->offsetToImportName);	// import names
	case 5:
		return (dataPtr + ptr->offsetToSubData5);
	default:
		return NULL;
	}
}

void printInfoBlackBox(const char *string) {
}

void waitForPlayerInput() {
}

void getFileExtention(const char *name, char *buffer) {
	while (*name != '.' && *name) {
		name++;
	}

	strcpy(buffer, name);
}

void removeExtention(const char *name, char *buffer) {	// not like in original
	char *ptr;

	strcpy(buffer, name);

	ptr = strchr(buffer, '.');

	if (ptr)
		*ptr = 0;
}

int lastFileSize;

int loadFileSub1(uint8 **ptr, const char *name, uint8 *ptr2) {
	int i;
	char buffer[256];
	int fileIdx;
	int unpackedSize;
	uint8 *unpackedBuffer;

	for (i = 0; i < 64; i++) {
		if (preloadData[i].ptr) {
			if (!strcmp(preloadData[i].name, name)) {
				error("Unsupported code in loadFIleSub1");
			}
		}
	}

	getFileExtention(name, buffer);

	if (!strcmp(buffer, ".SPL")) {
		removeExtention(name, buffer);

		/* if (useH32)
		 *{
		 *	strcat(buffer, ".H32");
		 *}
		 * else
		 * if (useAdLib)
		 * { */
		 strcat(buffer,".ADL");
		/* }
		 * else
		 * {
		 * strcatuint8(buffer,".HP");
		 * } */
	} else {
		strcpy(buffer, name);
	}

	fileIdx = findFileInDisks(buffer);

	if (fileIdx < 0)
		return (-18);

	unpackedSize = loadFileVar1 = volumePtrToFileDescriptor[fileIdx].extSize + 2;

	unpackedBuffer = (uint8 *)mallocAndZero(unpackedSize);

	if (!unpackedBuffer) {
		return (-2);
	}

	lastFileSize = unpackedSize;

	if (volumePtrToFileDescriptor[fileIdx].size + 2 != unpackedSize) {
		uint8 *pakedBuffer = (uint8 *) mallocAndZero(volumePtrToFileDescriptor[fileIdx].size + 2);

		loadPackedFileToMem(fileIdx, pakedBuffer);

		uint32 realUnpackedSize = READ_BE_UINT32(pakedBuffer + volumePtrToFileDescriptor[fileIdx].size - 4);

		lastFileSize = realUnpackedSize;

		delphineUnpack(unpackedBuffer, pakedBuffer, volumePtrToFileDescriptor[fileIdx].size);

		MemFree(pakedBuffer);
	} else {
		loadPackedFileToMem(fileIdx, unpackedBuffer);
	}

	*ptr = unpackedBuffer;

	return (1);
}

void resetFileEntry(int32 entryNumber) {
	if (entryNumber >= NUM_FILE_ENTRIES)
		return;

	if (!filesDatabase[entryNumber].subData.ptr)
		return;

	MemFree(filesDatabase[entryNumber].subData.ptr);
	if (filesDatabase[entryNumber].subData.ptrMask)
		MemFree(filesDatabase[entryNumber].subData.ptrMask);

	filesDatabase[entryNumber].subData.ptr = NULL;
	filesDatabase[entryNumber].subData.ptrMask = NULL;
	filesDatabase[entryNumber].widthInColumn = 0;
	filesDatabase[entryNumber].width = 0;
	filesDatabase[entryNumber].resType = OBJ_TYPE_LINE;
	filesDatabase[entryNumber].height = 0;
	filesDatabase[entryNumber].subData.index = -1;
	filesDatabase[entryNumber].subData.resourceType = 0;
	filesDatabase[entryNumber].subData.compression = 0;
	filesDatabase[entryNumber].subData.name[0] = 0;
}

uint8 *mainProc14(uint16 overlay, uint16 idx) {
	ASSERT(0);

	return NULL;
}

void CruiseEngine::initAllData() {
	int i;

	setupFuncArray();
	initOverlayTable();

	stateID = 0;
	masterScreen = 0;

	freeDisk();

	soundList[0].frameNum = -1;
	soundList[1].frameNum = -1;
	soundList[2].frameNum = -1;
	soundList[3].frameNum = -1;

	menuTable[0] = NULL;

	for (i = 0; i < 2000; i++) {
		globalVars[i] = 0;
	}

	for (i = 0; i < 8; i++) {
		backgroundTable[i].name[0] = 0;
	}

	for (i = 0; i < NUM_FILE_ENTRIES; i++) {
		filesDatabase[i].subData.ptr = NULL;
		filesDatabase[i].subData.ptrMask = NULL;
	}

	initBigVar3();

	resetPtr2(&procHead);
	resetPtr2(&relHead);

	resetPtr(&cellHead);

	resetActorPtr(&actorHead);
	resetBackgroundIncrustList(&backgroundIncrustHead);

	bootOverlayNumber = loadOverlay("AUTO00");

#ifdef DUMP_SCRIPT
	loadOverlay("TITRE");
	loadOverlay("TOM");
	loadOverlay("XX2");
	loadOverlay("SUPER");
	loadOverlay("BEBE1");
	loadOverlay("BIBLIO");
	loadOverlay("BRACAGE");
	loadOverlay("CONVERS");
	loadOverlay("DAF");
	loadOverlay("DAPHNEE");
	loadOverlay("DESIRE");
	loadOverlay("FAB");
	loadOverlay("FABIANI");
	loadOverlay("FIN");
	loadOverlay("FIN01");
	loadOverlay("FINBRAC");
	loadOverlay("GEN");
	loadOverlay("GENDEB");
	loadOverlay("GIFLE");
	loadOverlay("HECTOR");
	loadOverlay("HECTOR2");
	loadOverlay("I00");
	loadOverlay("I01");
	loadOverlay("I04");
	loadOverlay("I06");
	loadOverlay("I07");
	loadOverlay("INVENT");
	loadOverlay("JULIO");
	loadOverlay("LOGO");
	loadOverlay("MANOIR");
	loadOverlay("MISSEL");
	loadOverlay("POKER");
	loadOverlay("PROJ");
	loadOverlay("REB");
	loadOverlay("REBECCA");
	loadOverlay("ROS");
	loadOverlay("ROSE");
	loadOverlay("S01");
	loadOverlay("S02");
	loadOverlay("S03");
	loadOverlay("S04");
	loadOverlay("S06");
	loadOverlay("S07");
	loadOverlay("S08");
	loadOverlay("S09");
	loadOverlay("S10");
	loadOverlay("S103");
	loadOverlay("S11");
	loadOverlay("S113");
	loadOverlay("S12");
	loadOverlay("S129");
	loadOverlay("S131");
	loadOverlay("S132");
	loadOverlay("S133");
	loadOverlay("int16");
	loadOverlay("S17");
	loadOverlay("S18");
	loadOverlay("S19");
	loadOverlay("S20");
	loadOverlay("S21");
	loadOverlay("S22");
	loadOverlay("S23");
	loadOverlay("S24");
	loadOverlay("S25");
	loadOverlay("S26");
	loadOverlay("S27");
	loadOverlay("S29");
	loadOverlay("S30");
	loadOverlay("S31");
	loadOverlay("int32");
	loadOverlay("S33");
	loadOverlay("S33B");
	loadOverlay("S34");
	loadOverlay("S35");
	loadOverlay("S36");
	loadOverlay("S37");
	loadOverlay("SHIP");
	loadOverlay("SUPER");
	loadOverlay("SUZAN");
	loadOverlay("SUZAN2");
	loadOverlay("TESTA1");
	loadOverlay("TESTA2");
	//exit(1);
#endif

	if (bootOverlayNumber) {
		positionInStack = 0;

		attacheNewScriptToTail(&procHead, bootOverlayNumber, 0, 20, 0, 0, scriptType_PROC);
		scriptFunc2(bootOverlayNumber, &procHead, 1, 0);
	}

	strcpy(lastOverlay, "AUTO00");

	_gameSpeed = GAME_FRAME_DELAY_1;
	_speedFlag = false;

	return;
}

int removeFinishedScripts(scriptInstanceStruct *ptrHandle) {
	scriptInstanceStruct *ptr = ptrHandle->nextScriptPtr;	// can't destruct the head
	scriptInstanceStruct *oldPtr = ptrHandle;

	if (!ptr)
		return (0);

	do {
		if (ptr->scriptNumber == -1) {
			oldPtr->nextScriptPtr = ptr->nextScriptPtr;

			if (ptr->data)
				MemFree(ptr->data);

			MemFree(ptr);

			ptr = oldPtr->nextScriptPtr;
		} else {
			oldPtr = ptr;
			ptr = ptr->nextScriptPtr;
		}
	} while (ptr);

	return (0);
}

void removeAllScripts(scriptInstanceStruct *ptrHandle) {
	scriptInstanceStruct *ptr = ptrHandle->nextScriptPtr;	// can't destruct the head
	scriptInstanceStruct *oldPtr = ptrHandle;

	if (!ptr)
		return;

	do {
		oldPtr->nextScriptPtr = ptr->nextScriptPtr;

		if (ptr->data)
			MemFree(ptr->data);

		MemFree(ptr);

		ptr = oldPtr->nextScriptPtr;
	} while (ptr);
}


bool testMask(int x, int y, unsigned char* pData, int stride) {
	unsigned char* ptr = y * stride + x / 8 + pData;

	unsigned char bitToTest = 0x80 >> (x & 7);

	if ((*ptr) & bitToTest)
		return true;
	return false;
}

int buttonDown;
int selectDown = 0;
int menuDown = 0;

int findObject(int mouseX, int mouseY, int *outObjOvl, int *outObjIdx) {
	char objectName[80];

	cellStruct *currentObject = cellHead.prev;

	while (currentObject) {
		if (currentObject->overlay > 0 && overlayTable[currentObject->overlay].alreadyLoaded &&
				(currentObject->type == OBJ_TYPE_SPRITE || currentObject->type == OBJ_TYPE_MASK ||
				currentObject->type == OBJ_TYPE_EXIT || currentObject->type == OBJ_TYPE_VIRTUAL)) {
			const char* pObjectName = getObjectName(currentObject->idx, overlayTable[currentObject->overlay].ovlData->arrayNameObj);

			strcpy(objectName, pObjectName);

			if (strlen(objectName) && (currentObject->freeze == 0)) {
				int objIdx = currentObject->idx;
				int objOvl = currentObject->overlay;
				int linkedObjIdx = currentObject->followObjectIdx;
				int linkedObjOvl = currentObject->followObjectOverlayIdx;

				objectParamsQuery params;
				getMultipleObjectParam(objOvl, objIdx, &params);

				int x2 = 0;
				int y2 = 0;
				int j2 = 0;

				if ((objOvl != linkedObjOvl) || (objIdx != linkedObjIdx)) {
					objectParamsQuery params2;
					getMultipleObjectParam(linkedObjOvl, linkedObjIdx, &params2);

					x2 = params2.X;
					y2 = params2.Y;
					j2 = params2.fileIdx;
				}

				if (params.state >= 0 && params.fileIdx >= 0) {
					if (currentObject->type == OBJ_TYPE_SPRITE || currentObject->type == OBJ_TYPE_MASK || currentObject->type == OBJ_TYPE_EXIT) {
						int x = params.X + x2;
						int y = params.Y + y2;
						int j = params.fileIdx;

						if (j >= 0) {
							j += j2;
						}

						if ((filesDatabase[j].subData.resourceType == OBJ_TYPE_POLY) && (filesDatabase[j].subData.ptr)) {
							int zoom = params.scale;

							int16* dataPtr = (int16 *)filesDatabase[j].subData.ptr;

							if (*dataPtr == 0) {
								int16 offset;
								int16 newX;
								int16 newY;

								dataPtr ++;

								offset = (int16)READ_BE_UINT16(dataPtr);
								dataPtr++;

								newX = (int16)READ_BE_UINT16(dataPtr);
								dataPtr++;

								newY = (int16)READ_BE_UINT16(dataPtr);
								dataPtr++;

								offset += j;

								if (offset >= 0) {
									if (filesDatabase[offset].resType == OBJ_TYPE_LINE &&
											filesDatabase[offset].subData.ptr) {
										dataPtr = (int16 *)filesDatabase[offset].subData.ptr;
									}
								}

								zoom = -zoom;
								x -= newX;
								y -= newY;
							}

							if (dataPtr && findPoly((char *)dataPtr, x, y, zoom, mouseX, mouseY)) {
								*outObjOvl = linkedObjOvl;
								*outObjIdx = linkedObjIdx;

								return (currentObject->type);
							}
						} else {
							// int numBitPlanes = filesDatabase[j].resType;

							int nWidth;
							int nHeight;

							nWidth = filesDatabase[j].width;
							nHeight = filesDatabase[j].height;

							int offsetX = mouseX - x;
							int offsetY = mouseY - y;

							if ((offsetX >= 0) && (offsetX < nWidth) && (offsetY >= 0) && (offsetY <= nHeight) && filesDatabase[j].subData.ptr) {
								if (testMask(offsetX, offsetY, filesDatabase[j].subData.ptrMask, filesDatabase[j].width / 8)) {
									*outObjOvl = linkedObjOvl;
									*outObjIdx = linkedObjIdx;
									return currentObject->type;
								}
							}
						}
					} else if (currentObject->type == OBJ_TYPE_VIRTUAL) {
						int x = params.X + x2;
						int y = params.Y + y2;
						int width = params.fileIdx;
						int height = params.scale;

						if ((mouseX >= x) && (mouseX <= x + width) && (mouseY >= y) && (mouseY <= y + height)) {
							*outObjOvl = linkedObjOvl;
							*outObjIdx = linkedObjIdx;

							return (currentObject->type);
						}
					}
				}
			}
		}

		currentObject = currentObject->prev;
	}

	*outObjOvl = 0;
	*outObjIdx = 0;

	return -1;
}

Common::KeyCode keyboardCode = Common::KEYCODE_INVALID;

void freeStuff2() {
	warning("implement freeStuff2");
}

void *allocAndZero(int size) {
	void *ptr;

	ptr = MemAlloc(size);
	memset(ptr, 0, size);

	return ptr;
}

void buildInventory(int X, int Y) {
	menuStruct *pMenu;

	pMenu = createMenu(X, Y, _vm->langString(ID_INVENTORY));
	menuTable[1] = pMenu;

	if (pMenu == NULL)
		return;

	int numObjectInInventory = 0;
	for (int i = 1; i < numOfLoadedOverlay; i++) {
		ovlDataStruct *pOvlData = overlayTable[i].ovlData;

		if (overlayTable[i].alreadyLoaded) {
			if (overlayTable[i].ovlData->arrayObject) {
				for (int j = 0; j < pOvlData->numObj; j++) {
					if (getObjectClass(i, j) != 3) {
						int16 returnVar;

						getSingleObjectParam(i, j, 5, &returnVar);

						if (returnVar < -1) {
							addSelectableMenuEntry(i, j, pMenu, 1, -1, getObjectName(j, pOvlData->arrayNameObj));
							numObjectInInventory++;
						}
					}
				}
			}
		}
	}

	if (numObjectInInventory == 0) {
		freeMenu(menuTable[1]);
		menuTable[1] = NULL;
	}
}

int currentMenuElementX;
int currentMenuElementY;
menuElementStruct *currentMenuElement;

menuElementSubStruct *getSelectedEntryInMenu(menuStruct *pMenu) {
	menuElementStruct *pMenuElement;

	if (pMenu == NULL) {
		return NULL;
	}

	if (pMenu->numElements == 0) {
		return NULL;
	}

	pMenuElement = pMenu->ptrNextElement;

	while (pMenuElement) {
		if (pMenuElement->selected) {
			currentMenuElementX = pMenuElement->x;
			currentMenuElementY = pMenuElement->y;
			currentMenuElement = pMenuElement;

			return pMenuElement->ptrSub;
		}

		pMenuElement = pMenuElement->next;
	}

	return NULL;
}

bool createDialog(int objOvl, int objIdx, int x, int y) {
	bool found = false;
	int testState1 = -1;
	int testState2 = -1;
	int j;
	int16 objectState;
	int16 objectState2;

	getSingleObjectParam(objOvl, objIdx, 5, &objectState);

	menuTable[0] = createMenu(x, y, _vm->langString(ID_SPEAK_ABOUT));

	for (j = 1; j < numOfLoadedOverlay; j++) {
		if (overlayTable[j].alreadyLoaded) {
			int idHeader = overlayTable[j].ovlData->numMsgRelHeader;

			for (int i = 0; i < idHeader; i++) {
				linkDataStruct* ptrHead = &overlayTable[j].ovlData->arrayMsgRelHeader[i];
				int thisOvl = ptrHead->obj1Overlay;

				if (!thisOvl) {
					thisOvl = j;
				}

				objDataStruct* pObject = getObjectDataFromOverlay(thisOvl, ptrHead->obj1Number);

				getSingleObjectParam(thisOvl, ptrHead->obj1Number, 5, &objectState2);

				if (pObject && (pObject->_class == THEME) && (objectState2 < -1)) {

					thisOvl = ptrHead->obj2Overlay;
					if (!thisOvl) {
						thisOvl = j;
					}

					if ((thisOvl == objOvl) && (ptrHead->obj2Number == objIdx)) {
						int verbeOvl = ptrHead->verbOverlay;
						int obj1Ovl = ptrHead->obj1Overlay;
						int obj2Ovl = ptrHead->obj2Overlay;

						if (!verbeOvl) verbeOvl = j;
						if (!obj1Ovl)  obj1Ovl = j;
						if (!obj2Ovl)  obj2Ovl = j;

						char verbe_name[80];

						verbe_name[0]	= 0;

						ovlDataStruct *ovl2 = NULL;
						ovlDataStruct *ovl3 = NULL;
						ovlDataStruct *ovl4 = NULL;

						if (verbeOvl > 0)
							ovl2 = overlayTable[verbeOvl].ovlData;

						if (obj1Ovl > 0)
							ovl3 = overlayTable[obj1Ovl].ovlData;

						if (obj2Ovl > 0)
							ovl4 = overlayTable[obj2Ovl].ovlData;

						if ((ovl3) && (ptrHead->obj1Number >= 0)) {
							testState1 = ptrHead->obj1OldState;
						}
						if ((ovl4) && (ptrHead->obj2Number >= 0)) {
							testState2 = ptrHead->obj2OldState;
						}

						if ((ovl4) && (ptrHead->verbNumber >= 0) &&
						        ((testState1 == -1) || (testState1 == objectState2)) &&
						        ((testState2 == -1) || (testState2 == objectState))) {
							if (ovl2->nameVerbGlob) {
								const char *ptr = getObjectName(ptrHead->verbNumber, ovl2->nameVerbGlob);
								strcpy(verbe_name, ptr);

								if (!strlen(verbe_name))
									attacheNewScriptToTail(&relHead, j, ptrHead->id, 30, currentScriptPtr->scriptNumber, currentScriptPtr->overlayNumber, scriptType_REL);
								else if (ovl2->nameVerbGlob) {
									found = true;
									int color;

									if (objectState2 == -2)
										color = subColor;
									else
										color = -1;

									ptr = getObjectName(ptrHead->obj1Number, ovl3->arrayNameObj);
									addSelectableMenuEntry(j, i, menuTable[0], 1, color, ptr);
								}
							}
						}
					}
				}
			}
		}
	}

	return found;
}

bool findRelation(int objOvl, int objIdx, int x, int y) {
	bool found = false;
	bool first = true;
	int testState = -1;
	int j;
	int16 objectState;

	getSingleObjectParam(objOvl, objIdx, 5, &objectState);

	for (j = 1; j < numOfLoadedOverlay; j++) {
		if (overlayTable[j].alreadyLoaded) {
			int idHeader = overlayTable[j].ovlData->numMsgRelHeader;

			for (int i = 0; i < idHeader; i++) {
				linkDataStruct* ptrHead = &overlayTable[j].ovlData->arrayMsgRelHeader[i];
				int thisOvl = ptrHead->obj1Overlay;

				if (!thisOvl) {
					thisOvl = j;
				}

				//const char* pName = getObjectName(ptrHead->obj1Number, overlayTable[thisOvl].ovlData->arrayNameObj);

				objDataStruct* pObject = getObjectDataFromOverlay(thisOvl, ptrHead->obj1Number);

				if ((thisOvl == objOvl) && (objIdx == ptrHead->obj1Number) && pObject && (pObject->_class != THEME)) {
					int verbeOvl = ptrHead->verbOverlay;
					int obj1Ovl = ptrHead->obj1Overlay;
					int obj2Ovl = ptrHead->obj2Overlay;

					if (!verbeOvl) verbeOvl = j;
					if (!obj1Ovl)  obj1Ovl = j;
					if (!obj2Ovl)  obj2Ovl = j;

					char verbe_name[80];
					verbe_name[0]	= 0;

					ovlDataStruct *ovl2 = NULL;
					ovlDataStruct *ovl3 = NULL;
					//ovlDataStruct *ovl4 = NULL;

					if (verbeOvl > 0)
						ovl2 = overlayTable[verbeOvl].ovlData;

					if (obj1Ovl > 0)
						ovl3 = overlayTable[obj1Ovl].ovlData;

					//if (obj2Ovl > 0)
					//	ovl4 = overlayTable[obj2Ovl].ovlData;

					if ((ovl3) && (ptrHead->obj1Number >= 0)) {
						testState = ptrHead->obj1OldState;

						if ((first) && (ovl3->arrayNameObj) && ((testState == -1) || (testState == objectState))) {
							const char *ptrName = getObjectName(ptrHead->obj1Number, ovl3->arrayNameObj);

							menuTable[0] = createMenu(x, y, ptrName);
							first = false;
						}
					}
					if ((ovl2) && (ptrHead->verbNumber >= 0)) {
						if (ovl2->nameVerbGlob) {
							const char *ptr = getObjectName(ptrHead->verbNumber, ovl2->nameVerbGlob);
							strcpy(verbe_name, ptr);

							if ((!first) && ((testState == -1) || (testState == objectState))) {
								if (!strlen(verbe_name)) {
									if (currentScriptPtr) {
										attacheNewScriptToTail(&relHead, j, ptrHead->id, 30, currentScriptPtr->scriptNumber, currentScriptPtr->overlayNumber, scriptType_REL);
									} else {
										attacheNewScriptToTail(&relHead, j, ptrHead->id, 30, 0, 0, scriptType_REL);
									}
								} else if (ovl2->nameVerbGlob) {
									found = true;
									ptr = getObjectName(ptrHead->verbNumber, ovl2->nameVerbGlob);
									addSelectableMenuEntry(j, i, menuTable[0], 1, -1, ptr);
								}
							}
						}
					}
				}
			}
		}
	}

	return found;
}

int processInventory() {
	if (menuTable[1]) {
		menuElementSubStruct *pMenuElementSub = getSelectedEntryInMenu(menuTable[1]);

		if (pMenuElementSub) {
			int var2 = pMenuElementSub->ovlIdx;
			int var4 = pMenuElementSub->header;

			freeMenu(menuTable[1]);
			menuTable[1] = NULL;

			findRelation(var2, var4, currentMenuElementX + 80, currentMenuElementY);

			return 1;
		} else {
			freeMenu(menuTable[1]);
			menuTable[1] = NULL;
		}
	}

	return 0;
}

void callSubRelation(menuElementSubStruct *pMenuElement, int nOvl, int nObj) {
	if (pMenuElement == NULL)
		return;

	menuElementSubStruct* pCurrent = pMenuElement;

	while (pCurrent != NULL) {
		int ovlIdx = pCurrent->ovlIdx;
		int header = pCurrent->header;

		linkDataStruct* pHeader = &overlayTable[ovlIdx].ovlData->arrayMsgRelHeader[header];

		int obj2Ovl = pHeader->obj2Overlay;
		if (obj2Ovl == 0) {
			obj2Ovl = ovlIdx;
		}

		if ((obj2Ovl == nOvl) && (pHeader->obj2Number != -1) && (pHeader->obj2Number == nObj)) {
			int x = 60;
			int y = 60;

			objectParamsQuery params;
			memset(&params, 0, sizeof(objectParamsQuery)); // to remove warning

			if (pHeader->obj2Number >= 0) {
				getMultipleObjectParam(obj2Ovl, pHeader->obj2Number, &params);
			}

			if ((pHeader->obj2OldState == -1) || (params.state == pHeader->obj2OldState)) {
				if (pHeader->type == RT_REL) { // REL
					if (currentScriptPtr) {
						attacheNewScriptToTail(&relHead, ovlIdx, pHeader->id, 30, currentScriptPtr->scriptNumber, currentScriptPtr->overlayNumber, scriptType_REL);
					} else {
						attacheNewScriptToTail(&relHead, ovlIdx, pHeader->id, 30, 0, 0, scriptType_REL);
					}

					if ((narratorOvl > 0) && (pHeader->trackX != -1) && (pHeader->trackY != -1)) {
						actorStruct* pTrack = findActor(&actorHead, narratorOvl, narratorIdx, 0);

						if (pTrack) {
							animationStart = false;

							if (pHeader->trackDirection == 9999) {
								objectParamsQuery naratorParams;
								getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
								pTrack->x_dest = naratorParams.X;
								pTrack->y_dest = naratorParams.Y;
								pTrack->endDirection = direction(naratorParams.X, naratorParams.Y, pTrack->x_dest, pTrack->y_dest, 0, 0);
							} else if ((pHeader->trackX == 9999) && (pHeader->trackY == 9999)) {
								objectParamsQuery naratorParams;
								getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
								pTrack->x_dest = naratorParams.X;
								pTrack->y_dest = naratorParams.Y;
								pTrack->endDirection = pHeader->trackDirection;
							} else {
								pTrack->x_dest = pHeader->trackX;
								pTrack->y_dest = pHeader->trackY;
								pTrack->endDirection = pHeader->trackDirection;
							}

							pTrack->flag = 1;

							autoTrack = true;
							userEnabled = 0;
							changeScriptParamInList(ovlIdx, pHeader->id, &relHead, 0, 9998);
						}
					}
				} else if (pHeader->type == RT_MSG) {

					if (pHeader->obj2Number >= 0) {
						if ((pHeader->trackX !=-1) && (pHeader->trackY !=-1) &&
								(pHeader->trackX != 9999) && (pHeader->trackY != 9999)) {
							x = pHeader->trackX - 100;
							y = pHeader->trackY - 150;
						} else if (params.scale >= 0) {
							x = params.X - 100;
							y = params.Y - 40;
						}

						if (pHeader->obj2NewState != -1) {
							objInit(obj2Ovl, pHeader->obj2Number, pHeader->obj2NewState);
						}
					}

					if ((pHeader->obj1Number >= 0) && (pHeader->obj1NewState != -1)) {
						int obj1Ovl = pHeader->obj1Overlay;
						if (!obj1Ovl) obj1Ovl = ovlIdx;
						objInit(obj1Ovl, pHeader->obj1Number, pHeader->obj1NewState);
					}

					if (currentScriptPtr) {
						createTextObject(&cellHead, ovlIdx, pHeader->id, x, y, 200, findHighColor(), masterScreen, currentScriptPtr->overlayNumber, currentScriptPtr->scriptNumber);
					} else {
						createTextObject(&cellHead, ovlIdx, pHeader->id, x, y, 200, findHighColor(), masterScreen, 0, 0);
					}

					userWait = 1;
					autoOvl = ovlIdx;
					autoMsg = pHeader->id;

					if ((narratorOvl > 0) && (pHeader->trackX != -1) && (pHeader->trackY != -1)) {
						actorStruct *pTrack = findActor(&actorHead, narratorOvl, narratorIdx, 0);

						if (pTrack)	 {
							objectParamsQuery naratorParams;
							animationStart = false;

							if (pHeader->trackDirection == 9999) {
								getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
								pTrack->x_dest = naratorParams.X;
								pTrack->y_dest = naratorParams.Y;
								pTrack->endDirection = direction(naratorParams.X, naratorParams.Y, pHeader->trackX,pHeader->trackY, 0, 0);
							} else if ((pHeader->trackX == 9999) && (pHeader->trackY == 9999)) {
								getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
								pTrack->x_dest = naratorParams.X;
								pTrack->y_dest = naratorParams.Y;
								pTrack->endDirection = pHeader->trackDirection;
							} else {
								pTrack->x_dest = pHeader->trackX;
								pTrack->y_dest = pHeader->trackY;
								pTrack->endDirection = pHeader->trackDirection;
							}

							pTrack->flag = 1;
							autoTrack = true;
							userWait = 0;
							userEnabled = 0;
							freezeCell(&cellHead, ovlIdx, pHeader->id, 5, -1, 0, 9998);
						}
					}
				}
			}
		}

		pCurrent = pCurrent->pNext;
	}
}

int findHighColor() {
	int bestColorResult = -1;
	int bestColorIdx = -1;

	for (unsigned long int i = 0; i < 256; i++) {
		int colorResult = (workpal[i*3+0] + workpal[i*3+1] + workpal[i*3+2]) / 256;

		if (colorResult > bestColorResult) {
			bestColorIdx = i;
			bestColorResult =  colorResult;
		}
	}
	return bestColorIdx;
}

void callRelation(menuElementSubStruct *pMenuElement, int nObj2) {
	if (pMenuElement == NULL)
		return;

	menuElementSubStruct* pCurrent = pMenuElement;

	while (pCurrent != NULL) {
		int ovlIdx = pCurrent->ovlIdx;
		int header = pCurrent->header;

		linkDataStruct* pHeader = &overlayTable[ovlIdx].ovlData->arrayMsgRelHeader[header];

		if (pHeader->obj2Number == nObj2) {
			// REL
			if (pHeader->type == RT_REL) {
				if (currentScriptPtr) {
					attacheNewScriptToTail(&relHead, ovlIdx, pHeader->id, 30, currentScriptPtr->scriptNumber, currentScriptPtr->overlayNumber, scriptType_REL);
				} else {
					attacheNewScriptToTail(&relHead, ovlIdx, pHeader->id, 30, 0, 0, scriptType_REL);
				}

				if ((narratorOvl > 0) && (pHeader->trackX != -1) && (pHeader->trackY != -1)) {
					actorStruct* pTrack = findActor(&actorHead, narratorOvl, narratorIdx, 0);

					if (pTrack) {
						animationStart = false;

						if (pHeader->trackDirection == 9999) {
							objectParamsQuery naratorParams;
							getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
							pTrack->x_dest = naratorParams.X;
							pTrack->y_dest = naratorParams.Y;
							pTrack->endDirection = direction(naratorParams.X, naratorParams.Y, pTrack->x_dest, pTrack->y_dest, 0, 0);
						} else if ((pHeader->trackX == 9999) && (pHeader->trackY == 9999)) {
							objectParamsQuery naratorParams;
							getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
							pTrack->x_dest = naratorParams.X;
							pTrack->y_dest = naratorParams.Y;
							pTrack->endDirection = pHeader->trackDirection;
						} else {
							pTrack->x_dest = pHeader->trackX;
							pTrack->y_dest = pHeader->trackY;
							pTrack->endDirection = pHeader->trackDirection;
						}

						pTrack->flag = 1;

						autoTrack = true;
						userEnabled = 0;
						changeScriptParamInList(ovlIdx, pHeader->id, &relHead, 0, 9998);
					}
				}
			} else if (pHeader->type == RT_MSG) { // MSG
				int obj1Ovl = pHeader->obj1Overlay;
				if (!obj1Ovl)
					obj1Ovl = ovlIdx;

				int x = 60;
				int y = 40;

				if (pHeader->obj1Number >= 0) {
					objectParamsQuery params;
					getMultipleObjectParam(obj1Ovl, pHeader->obj1Number, &params);

					if (narratorOvl > 0) {
						if ((pHeader->trackX != -1) && (pHeader->trackY != -1) && (pHeader->trackX != 9999) && (pHeader->trackY != 9999)) {
							x = pHeader->trackX - 100;
							y = pHeader->trackY - 150;
						} else {
							getMultipleObjectParam(narratorOvl, narratorIdx, &params);
							x = params.X - 100;
							y = params.Y - 150;
						}
					} else if (params.scale >= 0) {
						x = params.X - 100;
						y = params.Y - 40;
					}

					if (pHeader->obj1NewState != -1) {
						objInit(obj1Ovl, pHeader->obj1Number, pHeader->obj1NewState);
					}
				}

				if (currentScriptPtr) {
					createTextObject(&cellHead, ovlIdx, pHeader->id, x, y, 200, findHighColor(), masterScreen, currentScriptPtr->overlayNumber, currentScriptPtr->scriptNumber);
				} else {
					createTextObject(&cellHead, ovlIdx, pHeader->id, x, y, 200, findHighColor(), masterScreen, 0, 0);
				}

				userWait = 1;
				autoOvl = ovlIdx;
				autoMsg = pHeader->id;

				if ((narratorOvl > 0) && (pHeader->trackX != -1) && (pHeader->trackY != -1)) {
					actorStruct* pTrack = findActor(&actorHead, narratorOvl, narratorIdx, 0);

					if (pTrack) {
						animationStart = false;

						if (pHeader->trackDirection == 9999) {
							objectParamsQuery naratorParams;
							getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
							pTrack->x_dest = naratorParams.X;
							pTrack->y_dest = naratorParams.Y;
							pTrack->endDirection = direction(naratorParams.X, naratorParams.Y, pTrack->x_dest, pTrack->y_dest, 0, 0);
						} else if ((pHeader->trackX == 9999) && (pHeader->trackY == 9999)) {
							objectParamsQuery naratorParams;
							getMultipleObjectParam(narratorOvl, narratorIdx, &naratorParams);
							pTrack->x_dest = naratorParams.X;
							pTrack->y_dest = naratorParams.Y;
							pTrack->endDirection = pHeader->trackDirection;
						} else {
							pTrack->x_dest = pHeader->trackX;
							pTrack->y_dest = pHeader->trackY;
							pTrack->endDirection = pHeader->trackDirection;
						}

						pTrack->flag = 1;

						autoTrack = true;
						userWait = 0;
						userEnabled = 0;
						freezeCell(&cellHead, ovlIdx, pHeader->id, 5, -1, 0, 9998);
					}
				}
			}
		} else {
			linkedRelation = pMenuElement;
		}

		pCurrent = pCurrent->pNext;
	}
}

void closeAllMenu() {
	if (menuTable[0]) {
		freeMenu(menuTable[0]);
		menuTable[0] = NULL;
	}

	if (menuTable[1]) {
		freeMenu(menuTable[1]);
		menuTable[1] = NULL;
	}
	if (linkedMsgList) {
		ASSERT(0);
//					freeMsgList(linkedMsgList);
	}

	linkedMsgList = NULL;
	linkedRelation = NULL;
}

bool checkInput(int16 *buttonPtr) {
	int16 handle, button;
	Common::Point pt;

	getMouseStatus(&handle, &pt.x, &button, &pt.y);

	if (!button)
		buttonDown = 0;
	else if (!buttonDown && button) {
		*buttonPtr = button;
		buttonDown = 1;
	}

	return false;
}

extern bool manageEvents();

int CruiseEngine::processInput() {
	int16 mouseX = 0;
	int16 mouseY = 0;
	int16 button = 0;

	/*if (inputSub1keyboad())
	 * {
	 * return 1;
	 * } */

	button = 0;

	if (sysKey != -1) {
		button = sysKey;
		mouseX = sysX;
		mouseY = sysY;
		sysKey = -1;
	} else if (automaticMode == 0) {
		getMouseStatus(&main10, &mouseX, &button, &mouseY);
	}

	if (!button) {
		buttonDown = 0;
	}

	// Check for Exit 'X' key
	if (keyboardCode == Common::KEYCODE_x)
		return 1;

	// Check for Pause 'P' key
	if (keyboardCode == Common::KEYCODE_p) {
		keyboardCode = Common::KEYCODE_INVALID;
		_vm->pauseEngine(true);
		mouseOff();

		bool pausedButtonDown = false;
		while (!_vm->shouldQuit()) {
			manageEvents();
			getMouseStatus(&main10, &mouseX, &button, &mouseY);

			if (button) pausedButtonDown = true;
			else if (pausedButtonDown)
				// Button released, so exit pause
				break;
			else if (keyboardCode != Common::KEYCODE_INVALID)
				break;

			g_system->delayMillis(10);
		}

		if (keyboardCode == Common::KEYCODE_x)
			// Exit the game
			return 1;

		keyboardCode = Common::KEYCODE_INVALID;
		_vm->pauseEngine(false);
		mouseOn();
		return 0;
	}

	// Player Menu - test for both buttons or the F10 key
	if (((button & CRS_MB_BOTH) == CRS_MB_BOTH) || (keyboardCode == Common::KEYCODE_F10)) {
		changeCursor(CURSOR_NORMAL);
		keyboardCode = Common::KEYCODE_INVALID;
		return (playerMenu(mouseX, mouseY));
	}

	if (userWait) {
		// Check for left mouse button click or Space to end user waiting
		if ((keyboardCode == Common::KEYCODE_SPACE) || (button == CRS_MB_LEFT))
			userWait = 0;

		keyboardCode = Common::KEYCODE_INVALID;
		return 0;
	}

	// Handle any changes in game speed
	if (_speedFlag) {
		if ((keyboardCode == Common::KEYCODE_KP_PLUS) && (_gameSpeed >= 30)) {
			_gameSpeed -= 10;
			keyboardCode = Common::KEYCODE_INVALID;
		}
		if ((keyboardCode == Common::KEYCODE_KP_MINUS) && (_gameSpeed <= 200)) {
			_gameSpeed += 10;
			keyboardCode = Common::KEYCODE_INVALID;
		}
	}

	if (!userEnabled) {
		return 0;
	}

	if ((currentActiveMenu != -1) && menuTable[currentActiveMenu]) {
		updateMenuMouse(mouseX, mouseY, menuTable[currentActiveMenu]);
	}

	if (dialogueEnabled) {

		if (menuDown || selectDown || linkedRelation) {
			closeAllMenu();
			menuDown = 0;
			selectDown = 0;
			currentActiveMenu = -1;
			changeCursor(CURSOR_NORMAL);
		}

		if ((menuTable[0] == NULL) && (!buttonDown)) {
			int dialogFound = createDialog(dialogueOvl, dialogueObj, xdial, 0);

			if (menuTable[0]) {
				if (dialogFound) {
					currentActiveMenu = 0;
				} else {
					freeMenu(menuTable[0]);
					menuTable[0] = NULL;
					currentActiveMenu = -1;
				}
			} else {
				menuDown = 0;
			}
		} else {
			if ((button & CRS_MB_LEFT) && (buttonDown == 0)) {
				if (menuTable[0]) {
					callRelation(getSelectedEntryInMenu(menuTable[0]), dialogueObj);

					freeMenu(menuTable[0]);
					menuTable[0] = NULL;

					if (linkedMsgList) {
						ASSERT(0);
						//					freeMsgList(linkedMsgList);
					}

					linkedMsgList = NULL;
					linkedRelation = NULL;

					changeCursor(CURSOR_NORMAL);
					currentActiveMenu = -1;
				}
				buttonDown = 1;
			}
		}

	} else if ((button & CRS_MB_LEFT) && (buttonDown == 0)) {
		// left click
		buttonDown = 1;

		// is there a relation
		if (linkedRelation) {
			// call sub relation when clicking on an object
			if (menuDown == 0) {
				if (menuTable[0]) {
					int objOvl;
					int objIdx;
					int objType;

					objType = findObject(mouseX, mouseY, &objOvl, &objIdx);

					if (objType != -1) {
						callSubRelation(linkedRelation, objOvl, objIdx);
					}
					freeMenu(menuTable[0]);
					menuTable[0] = NULL;
				}

				if (linkedMsgList) {
//					freeMsgList(linkedMsgList);
				}
				linkedMsgList = NULL;
				linkedRelation = NULL;
				changeCursor(CURSOR_NORMAL);
			} else { // call sub relation when clicking in inventory
				if (menuTable[0] && menuTable[1]) {
					menuElementSubStruct * p0 = getSelectedEntryInMenu(menuTable[1]);

					if (p0)
						callSubRelation(linkedRelation, p0->ovlIdx, p0->header);

					closeAllMenu();
					changeCursor(CURSOR_NORMAL);
				}
			}
			selectDown = 0;
			menuDown = 0;
		} else {
			// manage click on object menu
			if (menuDown == 0) {
				// Handle left click on an object
				if (menuTable[0] == 0) {
					int objOvl;
					int objIdx;
					int objType;

					objType = findObject(mouseX, mouseY, &objOvl, &objIdx);

					if (objType != -1) {
						int relation = findRelation(objOvl, objIdx, mouseX, mouseY);
						if (menuTable[0]) {
							if (relation) {
								currentActiveMenu = 0;
								selectDown = 1;
							} else {
								// object has a name but no relation, just move the character
								freeMenu(menuTable[0]);
								menuTable[0] = NULL;

								aniX = mouseX;
								aniY = mouseY;
								animationStart = true;
							}
						} else {
							aniX = mouseX;
							aniY = mouseY;
							animationStart = true;
						}
					} else {
						// No object found, we move the character to the cursor
						aniX = mouseX;
						aniY = mouseY;
						animationStart = true;
					}
				} else {
					// handle click in menu
					if (menuTable[0]) {
						menuElementSubStruct *pMenuElementSub = getSelectedEntryInMenu(menuTable[0]);

						callRelation(pMenuElementSub, -1);

						// if there is a linked relation, close menu
						if (!linkedRelation) {
							freeMenu(menuTable[0]);
							menuTable[0] = NULL;
							changeCursor(CURSOR_NORMAL);
						} else { // else create the message for the linked relation
							char text[80];
							strcpy(text, menuTable[0]->stringPtr);
							strcat(text, ":");
							strcat(text, currentMenuElement->string);
							linkedMsgList = renderText(320, (const char *)text);
							changeCursor(CURSOR_CROSS);
						}
					}

					currentActiveMenu = -1;
					selectDown = 0;
				}
			} else {
				// Handle left click in inventory
				if (processInventory()) {
					currentActiveMenu = 0;
					selectDown = 1;
					menuDown = 0;
				} else {
					currentActiveMenu = -1;
					menuDown = 0;
				}
			}
		}
	} else if ((button & CRS_MB_RIGHT) || (keyboardCode == Common::KEYCODE_F9)) {
		if (buttonDown == 0) {
			keyboardCode = Common::KEYCODE_INVALID;

			// close object menu if there is no linked relation
			if ((linkedRelation == 0) && (menuTable[0])) {
				freeMenu(menuTable[0]);
				menuTable[0] = NULL;
				selectDown = 0;
				menuDown = 0;
				currentActiveMenu = -1;
			}

			if ((!selectDown) && (!menuDown) && (menuTable[1] == NULL)) {
				buildInventory(mouseX, mouseY);

				if (menuTable[1]) {
					currentActiveMenu = 1;
					menuDown = 1;
				} else {
					menuDown = 1;
				}
			}
			buttonDown = 1;
		}
	}

	return 0;
}

int currentMouseX = 0;
int currentMouseY = 0;
int currentMouseButton = 0;

bool bFastMode = false;

bool manageEvents() {
	Common::Event event;

	Common::EventManager * eventMan = g_system->getEventManager();
	while (eventMan->pollEvent(event)) {
		bool abortFlag = true;

		switch (event.type) {
		case Common::EVENT_LBUTTONDOWN:
			currentMouseButton |= CRS_MB_LEFT;
			break;
		case Common::EVENT_LBUTTONUP:
			currentMouseButton &= ~CRS_MB_LEFT;
			break;
		case Common::EVENT_RBUTTONDOWN:
			currentMouseButton |= CRS_MB_RIGHT;
			break;
		case Common::EVENT_RBUTTONUP:
			currentMouseButton &= ~CRS_MB_RIGHT;
			break;
		case Common::EVENT_MOUSEMOVE:
			currentMouseX = event.mouse.x;
			currentMouseY = event.mouse.y;
			abortFlag = false;
			break;
		case Common::EVENT_QUIT:
		case Common::EVENT_RTL:
			playerDontAskQuit = 1;
			break;
		case Common::EVENT_KEYUP:
			switch (event.kbd.keycode) {
			case Common::KEYCODE_ESCAPE:
				currentMouseButton &= ~CRS_MB_MIDDLE;
				break;
			default:
				break;
			}
			break;
		case Common::EVENT_KEYDOWN:
			switch (event.kbd.keycode) {
			case Common::KEYCODE_ESCAPE:
				currentMouseButton |= CRS_MB_MIDDLE;
				break;
			default:
				keyboardCode = event.kbd.keycode;
				break;
			}

			if (event.kbd.hasFlags(Common::KBD_CTRL)) {
				if (event.kbd.keycode == Common::KEYCODE_d) {
					// Start the debugger
					_vm->getDebugger()->attach();
					keyboardCode = Common::KEYCODE_INVALID;
				} else if (event.kbd.keycode == Common::KEYCODE_f) {
					bFastMode = !bFastMode;
					keyboardCode = Common::KEYCODE_INVALID;
				}
			}

		default:
			break;
		}

		if (abortFlag)
			return true;
	}

	return false;
}

void getMouseStatus(int16 *pMouseVar, int16 *pMouseX, int16 *pMouseButton, int16 *pMouseY) {
	*pMouseX = currentMouseX;
	*pMouseY = currentMouseY;
	*pMouseButton = currentMouseButton;
}


void CruiseEngine::mainLoop() {
	//int32 t_start,t_left;
	//uint32 t_end;
	//int32 q=0;                     /* Dummy */
	int16 mouseX, mouseY;
	int16 mouseButton;

	int enableUser = 0;

	strcpy(nextOverlay, "");
	strcpy(lastOverlay, "");
	strcpy(cmdLine, "");

	currentActiveMenu = -1;
	autoMsg = -1;
	linkedRelation = 0;
	main21 = 0;
	main22 = 0;
	userWait = 0;
	autoTrack = false;

	initAllData();

	playerDontAskQuit = 0;
	int quitValue2 = 1;
	int quitValue = 0;

	if (ConfMan.hasKey("save_slot"))
		loadGameState(ConfMan.getInt("save_slot"));

	do {
		// Handle frame delay
		uint32 currentTick = g_system->getMillis();

		// Delay for the specified amount of time, but still respond to events
		bool skipEvents = false;

		do {
			if (userEnabled && !userWait && !autoTrack) {
				if (currentActiveMenu == -1) {
					static int16 oldMouseX = -1;
					static int16 oldMouseY = -1;

					getMouseStatus(&main10, &mouseX, &mouseButton, &mouseY);

					if (mouseX != oldMouseX || mouseY != oldMouseY) {
						int objectType;
						int newCursor1;
						int newCursor2;

						oldMouseX = mouseX;
						oldMouseY = mouseY;

						objectType = findObject(mouseX, mouseY, &newCursor1, &newCursor2);

						if (objectType == 9) {
							changeCursor(CURSOR_EXIT);
						} else if (objectType != -1) {
							changeCursor(CURSOR_MAGNIFYING_GLASS);
						} else {
							changeCursor(CURSOR_WALK);
						}
					}
				} else {
					changeCursor(CURSOR_NORMAL);
				}
			} else {
				changeCursor(CURSOR_NORMAL);
			}

			g_system->updateScreen();

			if (!skipEvents || bFastMode)
				skipEvents = manageEvents();

			if (bFastMode) {
				if (currentTick >= (lastTickDebug + 10))
					lastTickDebug = currentTick;
			} else {
				g_system->delayMillis(10);
				currentTick = g_system->getMillis();
			}

			if (playerDontAskQuit)
				break;

			_vm->getDebugger()->onFrame();
		} while (currentTick < lastTick + _gameSpeed && !bFastMode);
		if (playerDontAskQuit)
			break;

		lastTick = g_system->getMillis();

		// Handle switchover in game speed after intro
		if (!_speedFlag && canLoadGameStateCurrently()) {
			_speedFlag = true;
			_gameSpeed = GAME_FRAME_DELAY_2;
		}

		// Handle the next frame

//		frames++;
//      t_start=Osystem_GetTicks();

//      readKeyboard();

		bool isUserWait = userWait != 0;
		// WORKAROUND: This prevents hotspots responding during
		// delays i.e. Menu opening if you click fast on another
		// hotspot after trying to open a locked door, which
		// occurred with the original interpreter.
		if (userDelay) {
			currentMouseButton = 0;
		}

		playerDontAskQuit = processInput();
		if (playerDontAskQuit)
			break;

		if (enableUser) {
			userEnabled = 1;
			enableUser = 0;
		}

		if (userDelay && !userWait) {
			userDelay--;
		}

		if (isUserWait & !userWait) {
			// User waiting has ended
			changeScriptParamInList(-1, -1, &procHead, 9999, 0);
			changeScriptParamInList(-1, -1, &relHead, 9999, 0);

			// Disable any mouse click used to end the user wait
			currentMouseButton = 0;
		}

		// FIXME: I suspect that the original game does multiple script executions between game frames; the bug with
		// Raoul appearing when looking at the book is being there are 3 script iterations separation between the
		// scene being changed to the book, and the Raoul actor being frozen/disabled. This loop is a hack to ensure
		// that does a few extra script executions for that scene
		bool bgChanged;
		int numIterations = 1;

		while (numIterations-- > 0) {
			bgChanged = backgroundChanged[masterScreen];

			manageScripts(&relHead);
			manageScripts(&procHead);

			removeFinishedScripts(&relHead);
			removeFinishedScripts(&procHead);

			if (!bgChanged && backgroundChanged[masterScreen] &&
					!strcmp(backgroundTable[0].name, "S06B.PI1")) {
				bgChanged = true;
				numIterations += 2;
			}
		}

		processAnimation();

		if (remdo) {
			// ASSERT(0);
			/*    main3 = 0;
			 * var24 = 0;
			 * var23 = 0;
			 *
			 * freeStuff2(); */
		}

		if (cmdLine[0]) {
			ASSERT(0);
			/*        redrawStrings(0,&cmdLine,8);

			        waitForPlayerInput();

			        cmdLine = 0; */
		}

		if (displayOn) {
			if (doFade)
				PCFadeFlag = 0;

			/*if (!PCFadeFlag)*/
			mainDraw(userWait);
			flipScreen();

			if (userWait == 1) {
				// Waiting for press - original wait loop has been integrated into the
				// main event loop
				continue;
			}

			// wait for character to finish auto track
			if (autoTrack) {
				if (isAnimFinished(narratorOvl, narratorIdx, &actorHead, ATP_MOUSE)) {
					if (autoMsg != -1) {
						freezeCell(&cellHead, autoOvl, autoMsg, 5, -1, 9998, 0);

						char* pText = getText(autoMsg, autoOvl);

						if (strlen(pText))
							userWait = 1;
					}

					changeScriptParamInList(-1, -1, &relHead, 9998, 0);
					autoTrack = false;
					enableUser = 1;
				} else {
					userEnabled = false;
				}
			} else if (autoMsg != -1) {
				removeCell(&cellHead, autoOvl, autoMsg, 5, masterScreen);
				autoMsg = -1;
			}
		} else {
			// Keep ScummVM being responsive even when displayOn is false
			g_system->updateScreen();
		}

	} while (!playerDontAskQuit && quitValue2 && quitValue != 7);

	// Free data
	removeAllScripts(&relHead);
	removeAllScripts(&procHead);
	resetActorPtr(&actorHead);
	freeOverlayTable();
	closeCnf();
	closeBase();
	resetFileEntryRange(0, NUM_FILE_ENTRIES);
	freeObjectList(&cellHead);
	freeBackgroundIncrustList(&backgroundIncrustHead);
}

} // End of namespace Cruise