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

/*
 * Based on
 * WebVenture (c) 2010, Sean Kasun
 * https://github.com/mrkite/webventure, http://seancode.com/webventure/
 *
 * Used with explicit permission from the author
 */

#include "common/system.h"
#include "common/debug-channels.h"
#include "common/debug.h"
#include "common/error.h"
#include "common/config-manager.h"
#include "engines/util.h"

#include "macventure/macventure.h"

// To move
#include "common/file.h"

namespace MacVenture {

// HACK, see below
void toASCII(Common::String &str) {
	debugC(3, kMVDebugMain, "toASCII: %s", str.c_str());
	Common::String::iterator it = str.begin();
	for (; it != str.end(); it++) {
		if (*it == '\216') {
			str.replace(it, it + 1, "e");
		}
		if (*it == '\210') {
			str.replace(it, it + 1, "a");
		}
	}
}

enum {
	kMaxMenuTitleLength = 30
};

MacVentureEngine::MacVentureEngine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst) {
	_gameDescription = gameDesc;
	_rnd = new Common::RandomSource("macventure");

	initDebugChannels();

	_debugger = NULL;
	_resourceManager = NULL;
	_globalSettings = NULL;
	_gui = NULL;
	_world = NULL;
	_scriptEngine = NULL;
	_filenames = NULL;

	_decodingDirectArticles = NULL;
	_decodingNamingArticles = NULL;
	_decodingIndirectArticles = NULL;
	_textHuffman = NULL;

	_soundManager = NULL;

	_dataBundle = NULL;

	debug("MacVenture::MacVentureEngine()");
}

MacVentureEngine::~MacVentureEngine() {
	debug("MacVenture::~MacVentureEngine()");

	DebugMan.clearAllDebugChannels();

	if (_rnd)
		delete _rnd;

	if (_debugger)
		delete _debugger;

	if (_resourceManager)
		delete _resourceManager;

	if (_globalSettings)
		delete _globalSettings;

	if (_gui)
		delete _gui;

	if (_world)
		delete _world;

	if (_scriptEngine)
		delete _scriptEngine;

	if (_filenames)
		delete _filenames;

	if (_decodingDirectArticles)
		delete _decodingDirectArticles;

	if (_decodingNamingArticles)
		delete _decodingNamingArticles;

	if (_decodingIndirectArticles)
		delete _decodingIndirectArticles;

	if (_textHuffman)
		delete _textHuffman;

	if (_soundManager)
		delete _soundManager;

	if (_dataBundle)
		delete _dataBundle;
}

void MacVentureEngine::initDebugChannels() {
	DebugMan.addDebugChannel(kMVDebugMain, "main", "Engine state");
	DebugMan.addDebugChannel(kMVDebugGUI, "gui", "Gui");
	DebugMan.addDebugChannel(kMVDebugText, "text", "Text decoders and printers");
	DebugMan.addDebugChannel(kMVDebugImage, "image", "Image decoders and renderers");
	DebugMan.addDebugChannel(kMVDebugScript, "script", "Script engine");
	DebugMan.addDebugChannel(kMVDebugSound, "sound", "Sound decoders");
	DebugMan.addDebugChannel(kMVDebugContainer, "container", "Containers");
}

Common::Error MacVentureEngine::run() {
	debug("MacVenture::MacVentureEngine::init()");
	initGraphics(kScreenWidth, kScreenHeight);

	_debugger = new Console(this);

	// Additional setup.
	debug("MacVentureEngine::init");

	_resourceManager = new Common::MacResManager();
	if (!_resourceManager->open(getGameFileName()))
		error("ENGINE: Could not open %s as a resource fork", getGameFileName());

	// Engine-wide loading
	if (!loadGlobalSettings())
		error("ENGINE: Could not load the engine settings");

	_oldTextEncoding = !loadTextHuffman();

	_filenames = new StringTable(this, _resourceManager, kFilenamesStringTableID);
	_decodingDirectArticles = new StringTable(this, _resourceManager, kCommonArticlesStringTableID);
	_decodingNamingArticles = new StringTable(this, _resourceManager, kNamingArticlesStringTableID);
	_decodingIndirectArticles = new StringTable(this, _resourceManager, kIndirectArticlesStringTableID);

	loadDataBundle();

	// Big class instantiation
	_gui = new Gui(this, _resourceManager);
	_world = new World(this, _resourceManager);
	_scriptEngine = new ScriptEngine(this, _world);

	_soundManager = new SoundManager(this, _mixer);

	setInitialFlags();

	int directSaveSlotLoading = ConfMan.getInt("save_slot");
	if (directSaveSlotLoading >= 0) {
		if (loadGameState(directSaveSlotLoading).getCode() != Common::kNoError) {
			error("ENGINE: Could not load game from slot '%d'", directSaveSlotLoading);
		}
	} else {
		setNewGameState();
	}
	selectControl(kStartOrResume);

	_gui->addChild(kSelfWindow, 1);
	_gui->updateWindow(kSelfWindow, false);

	while (_gameState != kGameStateQuitting) {
		processEvents();

		if (_gameState != kGameStateQuitting && !_gui->isDialogOpen()) {

			if (_prepared) {
				_prepared = false;

				if (!_halted)
					updateState(false);

				if (_cmdReady || _halted) {
					_halted = false;
					if (runScriptEngine()) {
						_halted = true;
						_paused = true;
					} else {
						_paused = false;
						updateState(true);
						updateControls();
						updateExits();
					}
				}

				if (_gameState == kGameStateWinnig || _gameState == kGameStateLosing) {
					endGame();
				}
			}
		}
		refreshScreen();
	}

	return Common::kNoError;
}

void MacVentureEngine::refreshScreen() {
	_gui->draw();
	g_system->updateScreen();
	g_system->delayMillis(50);
}

void MacVentureEngine::newGame() {
	_world->startNewGame();
	reset();
	setInitialFlags();
	setNewGameState();
}

void MacVentureEngine::setInitialFlags() {
	_paused = false;
	_halted = false;
	_cmdReady = false;
	_haltedAtEnd = false;
	_haltedInSelection = false;
	_clickToContinue = true;
	_gameState = kGameStateInit;
	_destObject = 0;
	_prepared = true;
}

void MacVentureEngine::setNewGameState() {
	_cmdReady = true;
	ObjID playerParent = _world->getObjAttr(1, kAttrParentObject);
	_currentSelection.push_back(playerParent);// Push the parent of the player
	_world->setObjAttr(playerParent, kAttrContainerOpen, 1);
}

void MacVentureEngine::reset() {
	resetInternals();
	resetGui();
}

void MacVentureEngine::resetInternals() {
	_scriptEngine->reset();
	_currentSelection.clear();
	_objQueue.clear();
	_textQueue.clear();
}

void MacVentureEngine::resetGui() {
	_gui->reloadInternals();
	_gui->updateWindowInfo(kMainGameWindow, getParent(1), _world->getChildren(getParent(1), true));
	// HACK! should update all inventories
	_gui->ensureInventoryOpen(kInventoryStart, 1);
	_gui->updateWindowInfo(kInventoryStart, 1, _world->getChildren(1, true));
	updateControls();
	updateExits();
	refreshScreen();
}

void MacVentureEngine::requestQuit() {
	// TODO: Display save game dialog and such
	_gameState = kGameStateQuitting;
}

void MacVentureEngine::requestUnpause() {
	_paused = false;
	_gameState = kGameStatePlaying;
}

void MacVentureEngine::selectControl(ControlAction id) {
	debugC(2, kMVDebugMain, "Select control %x", id);
	if (id == kClickToContinue) {
		_clickToContinue = false;
		_paused = true;
		return;
	}

	_selectedControl = id;
	refreshReady();
}

void MacVentureEngine::refreshReady() {
	switch (getInvolvedObjects()) {
	case 0: // No selected object
		_cmdReady = true;
		break;
	case 1: // We have some selected object
		_cmdReady = _currentSelection.size() != 0;
		break;
	case 2:
		if (_destObject > 0) // We have a destination seleted
			_cmdReady = true;
		break;
	}
}

void MacVentureEngine::preparedToRun() {
	_prepared = true;
}

void MacVentureEngine::gameChanged() {
	_gameChanged = true;
}

void MacVentureEngine::winGame() {
	_gui->showPrebuiltDialog(kWinGameDialog);
	_gameState = kGameStateWinnig;
}

void MacVentureEngine::loseGame() {
	_gui->showPrebuiltDialog(kLoseGameDialog);
	_paused = true;
	//_gameState = kGameStateLosing;
}

void MacVentureEngine::clickToContinue() {
	_clickToContinue = true;
}

void MacVentureEngine::enqueueObject(ObjectQueueID type, ObjID objID, ObjID target) {
	QueuedObject obj;
	obj.id = type;

	if (type == kUpdateObject && isObjEnqueued(objID)) {
		return;
	}

	if (type == kUpdateWindow) {
		obj.target = target;
	}

	if (type != kHightlightExits) {
		obj.object = objID;
		obj.parent = _world->getObjAttr(objID, kAttrParentObject);
		obj.x = _world->getObjAttr(objID, kAttrPosX);
		obj.y = _world->getObjAttr(objID, kAttrPosY);
		obj.exitx = _world->getObjAttr(objID, kAttrExitX);
		obj.exity = _world->getObjAttr(objID, kAttrExitY);
		obj.hidden = _world->getObjAttr(objID, kAttrHiddenExit);
		obj.offscreen = _world->getObjAttr(objID, kAttrInvisible);
		obj.invisible = _world->getObjAttr(objID, kAttrUnclickable);
	}
	_objQueue.push_back(obj);
}

void MacVentureEngine::enqueueText(TextQueueID type, ObjID target, ObjID source, ObjID text) {
	QueuedText newText;
	newText.id = type;
	newText.destination = target;
	newText.source = source;
	newText.asset = text;
	_textQueue.push_back(newText);
}

void MacVentureEngine::enqueueSound(SoundQueueID type, ObjID target) {
	QueuedSound newSound;
	newSound.id = type;
	newSound.reference = target;
	_soundQueue.push_back(newSound);
}

void MacVentureEngine::handleObjectSelect(ObjID objID, WindowReference win, bool shiftPressed, bool isDoubleClick) {
	if (win == kExitsWindow) {
		win = kMainGameWindow;
	}

	const WindowData &windata = _gui->getWindowData(win);

	if (shiftPressed) {
		// TODO: Implement shift functionality.
	} else {
		if (_selectedControl && _currentSelection.size() > 0 && getInvolvedObjects() > 1) {
			if (objID == 0) {
				selectPrimaryObject(windata.objRef);
			} else {
				selectPrimaryObject(objID);
			}
			preparedToRun();
		} else {
			if (objID == 0) {
				unselectAll();
				objID = win;
			}
			if (objID > 0) {
				int currentObjectIndex = findObjectInArray(objID, _currentSelection);

				if (currentObjectIndex >= 0)
					unselectAll();

				if (isDoubleClick) {
					selectObject(objID);
					_destObject = objID;
					setDeltaPoint(Common::Point(0, 0));
					if (!_cmdReady) {
						selectControl(kActivateObject);
						_cmdReady = true;
					}
				} else {
					selectObject(objID);
					if (getInvolvedObjects() == 1)
						_cmdReady = true;
				}
				preparedToRun();
			}
		}
	}
}

void MacVentureEngine::handleObjectDrop(ObjID objID, Common::Point delta, ObjID newParent) {
	_destObject = newParent;
	setDeltaPoint(delta);
	selectControl(kMoveObject);
	refreshReady();
	preparedToRun();
}

void MacVentureEngine::setDeltaPoint(Common::Point newPos) {
	debugC(4, kMVDebugMain, "Update delta: Old(%d, %d), New(%d, %d)",
		_deltaPoint.x, _deltaPoint.y,
		newPos.x, newPos.y);
	_deltaPoint = newPos;
}

void MacVentureEngine::focusObjWin(ObjID objID) {
	_gui->bringToFront(getObjWindow(objID));
}

void MacVentureEngine::updateWindow(WindowReference winID) {
	_gui->updateWindow(winID, true);
}

bool MacVentureEngine::showTextEntry(ObjID text, ObjID srcObj, ObjID destObj) {
	debugC(3, kMVDebugMain, "Showing speech dialog, asset %d from %d to %d", text, srcObj, destObj);
	_gui->getTextFromUser();

	_prepared = false;
	warning("Show text entry: not fully tested");
	return true;
}

void MacVentureEngine::setTextInput(Common::String content) {
	_prepared = true;
	_userInput = content;
	_clickToContinue = false;
}

Common::String MacVentureEngine::getUserInput() {
	return _userInput;
}


Common::String MacVentureEngine::getStartGameFileName() {
	Common::SeekableReadStream *res;
	res = _resourceManager->getResource(MKTAG('S', 'T', 'R', ' '), kStartGameFilenameID);
	if (!res)
		return "";

	byte length = res->readByte();
	char *fileName = new char[length + 1];
	res->read(fileName, length);
	fileName[length] = '\0';
	Common::String result = Common::String(fileName, length);
	// HACK, see definition of toASCII
	toASCII(result);

	delete[] fileName;
	delete res;

	return result;
}

const GlobalSettings& MacVentureEngine::getGlobalSettings() const {
	return *_globalSettings;
}

// Private engine methods
void MacVentureEngine::processEvents() {
	Common::Event event;

	while (_eventMan->pollEvent(event)) {
		if (_gui->processEvent(event))
			continue;

		switch (event.type) {
		case Common::EVENT_QUIT:
			_gameState = kGameStateQuitting;
			break;
		default:
			break;
		}
	}
}

bool MacVenture::MacVentureEngine::runScriptEngine() {
	debugC(3, kMVDebugMain, "Running script engine");
	if (_haltedAtEnd) {
		_haltedAtEnd = false;
		if (_scriptEngine->resume(false)) {
			_haltedAtEnd = true;
			return true;
		}
		return false;
	}

	if (_haltedInSelection) {
		_haltedInSelection = false;
		if (_scriptEngine->resume(false)) {
			_haltedInSelection = true;
			return true;
		}
		updateState(true);
	}

	while (!_currentSelection.empty()) {
		ObjID obj = _currentSelection.front();
		_currentSelection.remove_at(0);
		if (isGameRunning() && _world->isObjActive(obj)) {
			if (_scriptEngine->runControl(_selectedControl, obj, _destObject, _deltaPoint)) {
				_haltedInSelection = true;
				return true;
			}
			updateState(true);
		}
	}
	if (_selectedControl == 1) {
		_gameChanged = false;
	} else if (isGameRunning()) {
		if (_scriptEngine->runControl(kTick, _selectedControl, _destObject, _deltaPoint)) {
			_haltedAtEnd = true;
			return true;
		}
	}
	return false;
}

void MacVentureEngine::endGame() {
	requestQuit();
}

void MacVentureEngine::updateState(bool pause) {
	_prepared = false;
	runObjQueue();
	printTexts();
	playSounds(pause);
}

void MacVentureEngine::revert() {
	_gui->invertWindowColors(kMainGameWindow);
	preparedToRun();
}

void MacVentureEngine::runObjQueue() {
	while (!_objQueue.empty()) {
		uint32 biggest = 0;
		uint32 index = 0;
		uint32 temp;
		for (uint i = 0; i < _objQueue.size(); i++) {
			temp = _objQueue[i].id;
			if (temp > biggest) {
				biggest = temp;
				index = i;
			}
		}
		QueuedObject obj = _objQueue[index];
		_objQueue.remove_at(index);
		switch (obj.id) {
		case 0x2:
			focusObjectWindow(obj.object);
			break;
		case 0x3:
			openObject(obj.object);
			break;
		case 0x4:
			closeObject(obj.object);
			break;
		case 0x7:
			checkObject(obj);
			break;
		case 0x8:
			reflectSwap(obj.object, obj.target);
			break;
		case 0xc:
			_world->setObjAttr(_gui->getWindowData(kMainGameWindow).refcon, kAttrContainerOpen, 0);
			_world->setObjAttr(_world->getObjAttr(1, kAttrParentObject), kAttrContainerOpen, 1);
			break;
		case 0xd:
			toggleExits();
			break;
		case 0xe:
			zoomObject(obj.object);
			break;
		default:
			break;
		}
	}
}

void MacVentureEngine::printTexts() {
	for (uint i = 0; i < _textQueue.size(); i++) {
		QueuedText text = _textQueue.front();
		_textQueue.remove_at(0);
		switch (text.id) {
		case kTextNumber:
			_gui->printText(Common::String(text.asset));
			gameChanged();
			break;
		case kTextNewLine:
			_gui->printText(Common::String(""));
			gameChanged();
			break;
		case kTextPlain:
			_gui->printText(_world->getText(text.asset, text.source, text.destination));
			gameChanged();
			break;
		default:
			break;
		}
	}
}

void MacVentureEngine::playSounds(bool pause) {
	int delay = 0;
	while (!_soundQueue.empty()) {
		QueuedSound item = _soundQueue.front();
		_soundQueue.remove_at(0);
		switch (item.id) {
		case kSoundPlay:
			_soundManager->playSound(item.reference);
			break;
		case kSoundPlayAndWait:
			delay = _soundManager->playSound(item.reference);
			break;
		case kSoundWait:
			// Empty in the original.
			break;
		default:
			break;
		}
	}
	if (pause && delay > 0) {
		warning("Sound pausing not yet tested. Pausing for %d", delay);
		g_system->delayMillis(delay);
		preparedToRun();
	}
}

void MacVentureEngine::updateControls() {
	selectControl(kNoCommand);
	_gui->clearControls();
	toggleExits();
	resetVars();
}

void MacVentureEngine::resetVars() {
	selectControl(kNoCommand);
	_currentSelection.clear();
	_destObject = 0;
	setDeltaPoint(Common::Point(0, 0));
	_cmdReady = false;
}

void MacVentureEngine::unselectAll() {
	while (!_currentSelection.empty()) {
		unselectObject(_currentSelection.front());
	}
}

void MacVentureEngine::selectObject(ObjID objID) {
	if (!_currentSelection.empty()) {
		if (findParentWindow(objID) != findParentWindow(_currentSelection[0])) {
			// TODO: Needs further testing, but it doesn't seem necessary.
			//unselectAll();
		}
	}
	if (findObjectInArray(objID, _currentSelection) == -1) {
		_currentSelection.push_back(objID);
		highlightExit(objID);
	}
}

void MacVentureEngine::unselectObject(ObjID objID) {
	int idxCur = findObjectInArray(objID, _currentSelection);
	if (idxCur != -1) {
		_currentSelection.remove_at(idxCur);
		highlightExit(objID);
	}
}


void MacVentureEngine::updateExits() {
	_gui->clearExits();
	_gui->unselectExits();

	Common::Array<ObjID> exits = _world->getChildren(_world->getObjAttr(1, kAttrParentObject), true);
	for (uint i = 0; i < exits.size(); i++)
		_gui->updateExit(exits[i]);

}

int MacVentureEngine::findObjectInArray(ObjID objID, const Common::Array<ObjID> &list) {
	// Find the object in the current selection
	bool found = false;
	uint i = 0;
	while (i < list.size() && !found) {
		if (list[i] == objID) {
			found = true;
		} else {
			i++;
		}
	}
	// HACK, should use iterator
	return found ? (int)i : -1;
}

uint MacVentureEngine::getPrefixNdx(ObjID obj) {
	return _world->getObjAttr(obj, kAttrPrefixes);
}

Common::String MacVentureEngine::getPrefixString(uint flag, ObjID obj) {
	uint ndx = getPrefixNdx(obj);
	ndx = ((ndx) >> flag) & 3;
	return _decodingNamingArticles->getString(ndx);
}

Common::String MacVentureEngine::getNoun(ObjID ndx) {
	return _decodingIndirectArticles->getString(ndx);
}

void MacVentureEngine::highlightExit(ObjID objID) {
	// TODO: It seems unnecessary since the GUI checks whether an object
	//		is selected, which includes exits.
	warning("STUB: highlightExit");
}

void MacVentureEngine::selectPrimaryObject(ObjID objID) {
	if (objID == _destObject) {
		return;
	}
	int idx;
	debugC(4, kMVDebugMain, "Select primary object (%d)", objID);
	if (_destObject > 0 &&
		(idx = findObjectInArray(_destObject, _currentSelection)) != -1) {
		unselectAll();
	}
	_destObject = objID;
	if (findObjectInArray(_destObject, _currentSelection) == -1) {
		selectObject(_destObject);
	}

	_cmdReady = true;
}

void MacVentureEngine::focusObjectWindow(ObjID objID) {
	if (objID) {
		WindowReference win = getObjWindow(objID);
		if (win)
			_gui->bringToFront(win);
	}
}

void MacVentureEngine::openObject(ObjID objID) {
	debugC(3, kMVDebugMain, "Open Object[%d] parent[%d] x[%d] y[%d]",
		objID,
		_world->getObjAttr(objID, kAttrParentObject),
		_world->getObjAttr(objID, kAttrPosX),
		_world->getObjAttr(objID, kAttrPosY));

	if (getObjWindow(objID)) {
		return;
	}
	if (objID == _world->getObjAttr(1, kAttrParentObject)) {
		_gui->updateWindowInfo(kMainGameWindow, objID, _world->getChildren(objID, true));
		_gui->updateWindow(kMainGameWindow, _world->getObjAttr(objID, kAttrContainerOpen));
		updateExits();
		_gui->setWindowTitle(kMainGameWindow, _world->getText(objID, objID, objID)); // it ignores source and target in the original
	} else { // Open inventory window
		Common::Point p(_world->getObjAttr(objID, kAttrPosX), _world->getObjAttr(objID, kAttrPosY));
		WindowReference invID = _gui->createInventoryWindow(objID);
		_gui->setWindowTitle(invID, _world->getText(objID, objID, objID));
		_gui->updateWindowInfo(invID, objID, _world->getChildren(objID, true));
		_gui->updateWindow(invID, _world->getObjAttr(objID, kAttrContainerOpen));
	}
}

void MacVentureEngine::closeObject(ObjID objID) {
	warning("closeObject: not fully implemented");
	_gui->tryCloseWindow(getObjWindow(objID));
}

void MacVentureEngine::checkObject(QueuedObject old) {
	bool hasChanged = false;
	debugC(3, kMVDebugMain, "Check Object[%d] parent[%d] x[%d] y[%d]",
		old.object,
		old.parent,
		old.x,
		old.y);
	ObjID id = old.object;
	if (id == 1) {
		if (old.parent != _world->getObjAttr(id, kAttrParentObject)) {
			enqueueObject(kSetToPlayerParent, id);
		}
		if (old.offscreen != !!_world->getObjAttr(id, kAttrInvisible) ||
			old.invisible != !!_world->getObjAttr(id, kAttrUnclickable)) {
			updateWindow(findParentWindow(id));
		}
	} else if (old.parent != _world->getObjAttr(id, kAttrParentObject) ||
				old.x != _world->getObjAttr(id, kAttrPosX) ||
				old.y != _world->getObjAttr(id, kAttrPosY)) {
		WindowReference oldWin = getObjWindow(old.parent);
		if (oldWin) {
			_gui->removeChild(oldWin, id);
			hasChanged = true;
		}

		WindowReference newWin = findParentWindow(id);
		if (newWin) {
			_gui->addChild(newWin, id);
			hasChanged = true;
		}
	} else if (old.offscreen != !!_world->getObjAttr(id, kAttrInvisible) ||
				old.invisible != !!_world->getObjAttr(id, kAttrUnclickable)) {
		updateWindow(findParentWindow(id));
	}

	if (_world->getObjAttr(id, kAttrIsExit)) {
		if (hasChanged ||
			old.hidden != !!_world->getObjAttr(id, kAttrHiddenExit) ||
			old.exitx != _world->getObjAttr(id, kAttrExitX) ||
			old.exity != _world->getObjAttr(id, kAttrExitY))
			_gui->updateExit(id);
	}
	WindowReference win = getObjWindow(id);
	ObjID cur = id;
	ObjID root = _world->getObjAttr(1, kAttrParentObject);
	while (cur != root)	{
		if (cur == 0 || !_world->getObjAttr(cur, kAttrContainerOpen)) {
			break;
		}
		cur = _world->getObjAttr(cur, kAttrParentObject);
	}
	if (cur == root) {
		if (win) {
			return;
		}
		enqueueObject(kOpenWindow, id); //open
	} else {
		if (!win) {
			return;
		}
		enqueueObject(kCloseWindow, id); //close
	}

	// Update children
	Common::Array<ObjID> children = _world->getChildren(id, true);
	for (uint i = 0; i < children.size(); i++) {
		enqueueObject(kUpdateObject, children[i]);
	}
}

void MacVentureEngine::reflectSwap(ObjID fromID, ObjID toID) {
	WindowReference from = getObjWindow(fromID);
	WindowReference to = getObjWindow(toID);
	WindowReference tmp = to;
	debugC(3, kMVDebugMain, "Swap Object[%d] to Object[%d], from win[%d] to win[%d] ",
		fromID, toID, from, to);

	if (!to) {
		tmp = from;
	}
	if (tmp) {
		Common::String newTitle = _world->getText(toID, 0, 0); // Ignores src and targ in the original
		_gui->setWindowTitle(tmp, newTitle);
		_gui->updateWindowInfo(tmp, toID, _world->getChildren(toID, true));
		updateWindow(tmp);
	}
}

void MacVentureEngine::toggleExits() {
	Common::Array<ObjID> exits = _currentSelection;
	while (!exits.empty()) {
		ObjID obj = exits.front();
		exits.remove_at(0);
		highlightExit(obj);
		updateWindow(findParentWindow(obj));
	}
}

void MacVentureEngine::zoomObject(ObjID objID) {
	warning("zoomObject: unimplemented");
}

bool MacVentureEngine::isObjEnqueued(ObjID objID) {
	Common::Array<QueuedObject>::const_iterator it;
	for (it = _objQueue.begin(); it != _objQueue.end(); it++) {
		if ((*it).object == objID) {
			return true;
		}
	}
	return false;
}

bool MacVentureEngine::isGameRunning() {
	return (_gameState == kGameStateInit || _gameState == kGameStatePlaying);
}

ControlAction MacVenture::MacVentureEngine::referenceToAction(ControlType id) {
	switch (id) {
	case MacVenture::kControlExitBox:
		return kActivateObject;//?? Like this in the original
	case MacVenture::kControlExamine:
		return kExamine;
	case MacVenture::kControlOpen:
		return kOpen;
	case MacVenture::kControlClose:
		return kClose;
	case MacVenture::kControlSpeak:
		return kSpeak;
	case MacVenture::kControlOperate:
		return kOperate;
	case MacVenture::kControlGo:
		return kGo;
	case MacVenture::kControlHit:
		return kHit;
	case MacVenture::kControlConsume:
		return kConsume;
	default:
		return kNoCommand;
	}
}

// Data retrieval

bool MacVentureEngine::isPaused() {
	return _paused;
}

bool MacVentureEngine::needsClickToContinue() {
	return _clickToContinue;
}

Common::String MacVentureEngine::getCommandsPausedString() const {
	return Common::String("Click to continue");
}

Common::String MacVentureEngine::getFilePath(FilePathID id) const {
	if (id <= 3) { // We don't want a file in the subdirectory
		return _filenames->getString(id);
	} else { // We want a game file
		return _filenames->getString(3) + "/" + _filenames->getString(id);
	}
}

bool MacVentureEngine::isOldText() const {
	return _oldTextEncoding;
}

const HuffmanLists *MacVentureEngine::getDecodingHuffman() const {
	return _textHuffman;
}

uint32 MacVentureEngine::randBetween(uint32 min, uint32 max) {
	return _rnd->getRandomNumber(max - min) + min;
}

uint32 MacVentureEngine::getInvolvedObjects() {
	// If there is no valid control selected, we return a number too big
	// to be useful. There is no control that uses that many objects.
	return (_selectedControl ? getGlobalSettings()._cmdArgCnts[_selectedControl - 1] : 3000);
}

Common::Point MacVentureEngine::getObjPosition(ObjID objID) {
	return Common::Point(_world->getObjAttr(objID, kAttrPosX), _world->getObjAttr(objID, kAttrPosY));
}

bool MacVentureEngine::isObjVisible(ObjID objID) {
	return _world->getObjAttr(objID, kAttrInvisible) == 0;
}

bool MacVentureEngine::isObjClickable(ObjID objID) {
	return _world->getObjAttr(objID, kAttrUnclickable) == 0;
}

bool MacVentureEngine::isObjSelected(ObjID objID) {
	int idx = findObjectInArray(objID, _currentSelection);
	return idx != -1;
}

bool MacVentureEngine::isObjExit(ObjID objID) {
	return _world->getObjAttr(objID, kAttrIsExit);
}

bool MacVentureEngine::isHiddenExit(ObjID objID) {
	return _world->getObjAttr(objID, kAttrHiddenExit);
}

Common::Point MacVentureEngine::getObjExitPosition(ObjID objID) {
	uint x = _world->getObjAttr(objID, kAttrExitX);
	uint y = _world->getObjAttr(objID, kAttrExitY);
	return Common::Point(x, y);
}

ObjID MacVentureEngine::getParent(ObjID objID) {
	return _world->getObjAttr(objID, kAttrParentObject);
}

Common::Rect MacVentureEngine::getObjBounds(ObjID objID) {
	Common::Point pos = getObjPosition(objID);

	WindowReference win = findParentWindow(objID);
	if (win != kNoWindow) { // If it's not in a window YET, we don't really care about the border
		BorderBounds bounds = borderBounds(_gui->getWindowData(win).type); // HACK
		pos.x += bounds.leftOffset;
		pos.y += bounds.topOffset;
	}
	Common::Point measures = _gui->getObjMeasures(objID);
	uint w = measures.x;
	uint h = measures.y;
	return Common::Rect(pos.x, pos.y, pos.x + w, pos.y + h);
}

uint MacVentureEngine::getOverlapPercent(ObjID one, ObjID other) {
	// If it's not the same parent, there's 0 overlap
	if (_world->getObjAttr(one, kAttrParentObject) !=
		_world->getObjAttr(other, kAttrParentObject))
		return 0;

	Common::Rect oneBounds = getObjBounds(one);
	Common::Rect otherBounds = getObjBounds(other);
	if (otherBounds.intersects(oneBounds) ||
		oneBounds.intersects(otherBounds)) {
		uint areaOne = oneBounds.width() * oneBounds.height();
		uint areaOther = otherBounds.width() * otherBounds.height();
		return (areaOne != 0) ? (areaOther * 100 / areaOne) : 0;
	}
	return 0;
}

WindowReference MacVentureEngine::getObjWindow(ObjID objID) {
	return _gui->getObjWindow(objID);
}

WindowReference MacVentureEngine::findParentWindow(ObjID objID) {
	if (objID == 1) {
		return kSelfWindow;
	}
	ObjID parent = _world->getObjAttr(objID, kAttrParentObject);
	if (parent == 0) {
		return kNoWindow;
	}
	return getObjWindow(parent);
}

Common::Point MacVentureEngine::getDeltaPoint() {
	return _deltaPoint;
}

ObjID MacVentureEngine::getDestObject() {
	return _destObject;
}

ControlAction MacVentureEngine::getSelectedControl() {
	return _selectedControl;
}

// Data loading

bool MacVentureEngine::loadGlobalSettings() {
	Common::MacResIDArray resArray;

	if ((resArray = _resourceManager->getResIDArray(MKTAG('G', 'N', 'R', 'L'))).size() == 0)
		return false;

	Common::SeekableReadStream *res;
	res = _resourceManager->getResource(MKTAG('G', 'N', 'R', 'L'), kGlobalSettingsID);
	if (res) {
		_globalSettings = new GlobalSettings();
		_globalSettings->loadSettings(res);
		delete res;
		return true;
	}
	return false;
}

bool MacVentureEngine::loadTextHuffman() {
	Common::MacResIDArray resArray;
	Common::SeekableReadStream *res;

	if ((resArray = _resourceManager->getResIDArray(MKTAG('G', 'N', 'R', 'L'))).size() == 0)
		return false;

	res = _resourceManager->getResource(MKTAG('G', 'N', 'R', 'L'), kTextHuffmanTableID);
	if (res) {
		uint32 numEntries = res->readUint16BE();
		res->readUint16BE(); // Skip

		uint32 *masks = new uint32[numEntries];
		for (uint i = 0; i < numEntries - 1; i++) {
			// For some reason there are one lass mask than entries
			masks[i] = res->readUint16BE();
		}

		uint32 *lengths = new uint32[numEntries];
		for (uint i = 0; i < numEntries; i++) {
			lengths[i] = res->readByte();
		}

		uint32 *values = new uint32[numEntries];
		for (uint i = 0; i < numEntries; i++) {
			values[i] = res->readByte();
		}

		_textHuffman = new HuffmanLists(numEntries, lengths, masks, values);
		debugC(4, kMVDebugMain, "Text is huffman-encoded");

		delete res;
		delete[] masks;
		delete[] lengths;
		delete[] values;
		return true;
	}
	return false;
}

// Global Settings
GlobalSettings::GlobalSettings() {
}

GlobalSettings::~GlobalSettings() {

}

void GlobalSettings::loadSettings(Common::SeekableReadStream *dataStream) {
	_numObjects = dataStream->readUint16BE();
	_numGlobals = dataStream->readUint16BE();
	_numCommands = dataStream->readUint16BE();
	_numAttributes = dataStream->readUint16BE();
	_numGroups = dataStream->readUint16BE();
	dataStream->readUint16BE(); // unknown
	_invTop = dataStream->readUint16BE();
	_invLeft = dataStream->readUint16BE();
	_invWidth = dataStream->readUint16BE();
	_invHeight = dataStream->readUint16BE();
	_invOffsetY = dataStream->readUint16BE();
	_invOffsetX = dataStream->readSint16BE();
	_defaultFont = dataStream->readUint16BE();
	_defaultSize = dataStream->readUint16BE();

	uint8 *attrIndices = new uint8[_numAttributes];
	dataStream->read(attrIndices, _numAttributes);
	_attrIndices = Common::Array<uint8>(attrIndices, _numAttributes);
	delete[] attrIndices;

	for (int i = 0; i < _numAttributes; i++) {
		_attrMasks.push_back(dataStream->readUint16BE());
	}

	uint8 *attrShifts = new uint8[_numAttributes];
	dataStream->read(attrShifts, _numAttributes);
	_attrShifts = Common::Array<uint8>(attrShifts, _numAttributes);
	delete[] attrShifts;

	uint8 *cmdArgCnts = new uint8[_numCommands];
	dataStream->read(cmdArgCnts, _numCommands);
	_cmdArgCnts = Common::Array<uint8>(cmdArgCnts, _numCommands);
	delete[] cmdArgCnts;

	uint8 *commands = new uint8[_numCommands];
	dataStream->read(commands, _numCommands);
	_commands = Common::Array<uint8>(commands, _numCommands);
	delete[] commands;
}

} // End of namespace MacVenture
