/* 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 "illusions/bbdou/illusions_bbdou.h"
#include "illusions/bbdou/bbdou_menukeys.h"
#include "illusions/bbdou/bbdou_videoplayer.h"
#include "illusions/bbdou/gamestate_bbdou.h"
#include "illusions/bbdou/menusystem_bbdou.h"
#include "illusions/actor.h"
#include "illusions/camera.h"
#include "illusions/cursor.h"
#include "illusions/dictionary.h"
#include "illusions/fileresourcereader.h"
#include "illusions/graphics.h"
#include "illusions/input.h"
#include "illusions/resources/actorresource.h"
#include "illusions/resources/backgroundresource.h"
#include "illusions/resources/fontresource.h"
#include "illusions/resources/scriptresource.h"
#include "illusions/resources/soundresource.h"
#include "illusions/resources/talkresource.h"
#include "illusions/resourcesystem.h"
#include "illusions/screen.h"
#include "illusions/screentext.h"
#include "illusions/scriptstack.h"
#include "illusions/bbdou/scriptopcodes_bbdou.h"
#include "illusions/sound.h"
#include "illusions/specialcode.h"
#include "illusions/bbdou/bbdou_specialcode.h"
#include "illusions/thread.h"
#include "illusions/time.h"
#include "illusions/updatefunctions.h"

#include "illusions/threads/abortablethread.h"
#include "illusions/threads/scriptthread.h"
#include "illusions/threads/talkthread.h"
#include "illusions/threads/timerthread.h"

#include "audio/audiostream.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/error.h"
#include "common/fs.h"
#include "common/timer.h"
#include "engines/util.h"
#include "graphics/cursorman.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/palette.h"
#include "graphics/surface.h"

namespace Illusions {

// ActiveScenes

ActiveScenes::ActiveScenes() {
	clear();
}

void ActiveScenes::clear() {
	_stack.clear();
}

void ActiveScenes::push(uint32 sceneId) {
	ActiveScene activeScene;
	activeScene._sceneId = sceneId;
	activeScene._pauseCtr = 0;
	_stack.push(activeScene);
}

void ActiveScenes::pop() {
	_stack.pop();
}

void ActiveScenes::pauseActiveScene() {
	++_stack.top()._pauseCtr;
}

void ActiveScenes::unpauseActiveScene() {
	--_stack.top()._pauseCtr;
}

uint ActiveScenes::getActiveScenesCount() {
	return _stack.size();
}

void ActiveScenes::getActiveSceneInfo(uint index, uint32 *sceneId, int *pauseCtr) {
	if (sceneId)
		*sceneId = _stack[index - 1]._sceneId;
	if (pauseCtr)
		*pauseCtr = _stack[index - 1]._pauseCtr;
}

uint32 ActiveScenes::getCurrentScene() {
	if (_stack.size() > 0)
		return _stack.top()._sceneId;
	return 0;
}

bool ActiveScenes::isSceneActive(uint32 sceneId) {
	for (uint i = 0; i < _stack.size(); ++i) {
		if (_stack[i]._sceneId == sceneId && _stack[i]._pauseCtr <= 0)
			return true;
	}
	return false;
}

// IllusionsEngine_BBDOU

IllusionsEngine_BBDOU::IllusionsEngine_BBDOU(OSystem *syst, const IllusionsGameDescription *gd)
	: IllusionsEngine(syst, gd) {
}

Common::Error IllusionsEngine_BBDOU::run() {

	// Init search paths
	const Common::FSNode gameDataDir(ConfMan.get("path"));
	SearchMan.addSubDirectoryMatching(gameDataDir, "music");
	SearchMan.addSubDirectoryMatching(gameDataDir, "resource");
	SearchMan.addSubDirectoryMatching(gameDataDir, "resrem");
	SearchMan.addSubDirectoryMatching(gameDataDir, "savegame");
	SearchMan.addSubDirectoryMatching(gameDataDir, "sfx", 0, 2);
	SearchMan.addSubDirectoryMatching(gameDataDir, "video");
	SearchMan.addSubDirectoryMatching(gameDataDir, "voice");

	_dict = new Dictionary();

	_resReader = new ResourceReaderFileReader();

	_resSys = new ResourceSystem(this);
	_resSys->addResourceLoader(0x00060000, new ActorResourceLoader(this));
	_resSys->addResourceLoader(0x00080000, new SoundGroupResourceLoader(this));
	_resSys->addResourceLoader(0x000D0000, new ScriptResourceLoader(this));
	_resSys->addResourceLoader(0x000F0000, new TalkResourceLoader(this));
	_resSys->addResourceLoader(0x00100000, new ActorResourceLoader(this));
	_resSys->addResourceLoader(0x00110000, new BackgroundResourceLoader(this));
	_resSys->addResourceLoader(0x00120000, new FontResourceLoader(this));
	_resSys->addResourceLoader(0x00170000, new SpecialCodeLoader(this));

	_screen = new Screen16Bit(this, 640, 480);
	_screenPalette = new NullScreenPalette();
	_screenText = new ScreenText(this);
	_input = new Input();
	_actorInstances = new ActorInstanceList(this);
	_backgroundInstances = new BackgroundInstanceList(this);
	_camera = new Camera(this);
	_controls = new Controls(this);
	_cursor = new Cursor(this);
	_talkItems = new TalkInstanceList(this);
	_triggerFunctions = new TriggerFunctions();
	_threads = new ThreadList(this);
	_updateFunctions = new UpdateFunctions();
	_soundMan = new SoundMan(this);
	_menuSystem = new BBDOUMenuSystem(this);
	_videoPlayer = new BBDOUVideoPlayer(this);
	_gameState = new BBDOU_GameState(this);
	_menuKeys = new BBDOUMenuKeys(this);

	_screen->setColorKey1(0xF81F);

	initInput();

	initUpdateFunctions();

	_fader = 0;

	_scriptOpcodes = new ScriptOpcodes_BBDOU(this);
	_stack = new ScriptStack();

	_resGetCtr = 0;
	_unpauseControlActorFlag = false;
	_lastUpdateTime = 0;

	_pauseCtr = 0;
	_field8 = 1;
	_fieldA = 0;
	ConfMan.registerDefault("talkspeed", 240);
	_subtitleDuration = (uint16)ConfMan.getInt("talkspeed");

	_globalSceneId = 0x00010003;

	setDefaultTextCoords();

	_resSys->loadResource(0x000D0001, 0, 0);

	_doScriptThreadInit = false;
	startScriptThread(0x00020004, 0, 0, 0, 0);
	_doScriptThreadInit = true;

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

	_walkthroughStarted = false;
	_canResumeFromSavegame = false;

	while (!shouldQuit()) {
		if (_walkthroughStarted) {
			//enterScene(0x10003, 0);
			startScriptThread(0x00020404, 0, 0, 0, 0);
			_walkthroughStarted = false;
		}
		if (_resumeFromSavegameRequested && _canResumeFromSavegame) {
			resumeFromSavegame();
			_resumeFromSavegameRequested = false;
		}
		runUpdateFunctions();
		_system->updateScreen();
		updateEvents();
	}

	delete _stack;
	delete _scriptOpcodes;

	delete _menuKeys;
	delete _gameState;
	delete _videoPlayer;
	delete _menuSystem;
	delete _soundMan;
	delete _updateFunctions;
	delete _threads;
	delete _triggerFunctions;
	delete _talkItems;
	delete _cursor;
	delete _controls;
	delete _camera;
	delete _backgroundInstances;
	delete _actorInstances;
	delete _input;
	delete _screenText;
	delete _screen;
	delete _resSys;
	delete _resReader;
	delete _dict;

	debug("Ok");

	return Common::kNoError;
}

bool IllusionsEngine_BBDOU::hasFeature(EngineFeature f) const {
	return
		(f == kSupportsRTL) ||
		(f == kSupportsLoadingDuringRuntime) ||
		(f == kSupportsSavingDuringRuntime);
}

void IllusionsEngine_BBDOU::initInput() {
	_input->setInputEvent(kEventLeftClick, 0x01)
		.addMouseButton(MOUSE_LEFT_BUTTON)
		.addKey(Common::KEYCODE_RETURN);
	_input->setInputEvent(kEventRightClick, 0x02)
		.addMouseButton(MOUSE_RIGHT_BUTTON);
	_input->setInputEvent(kEventInventory, 0x04)
		.addMouseButton(MOUSE_RIGHT_BUTTON)
		.addKey(Common::KEYCODE_TAB);
	_input->setInputEvent(kEventAbort, 0x08)
		.addKey(Common::KEYCODE_ESCAPE);
	_input->setInputEvent(kEventSkip, 0x10)
		.addKey(Common::KEYCODE_SPACE);
	_input->setInputEvent(kEventF1, 0x20)
		.addKey(Common::KEYCODE_F1);
	_input->setInputEvent(kEventUp, 0x40)
		.addKey(Common::KEYCODE_UP);
	_input->setInputEvent(kEventDown, 0x80)
		.addMouseButton(MOUSE_RIGHT_BUTTON)
		.addKey(Common::KEYCODE_DOWN);
}

#define UPDATEFUNCTION(priority, sceneId, callback) \
	_updateFunctions->add(priority, sceneId, new Common::Functor1Mem<uint, int, IllusionsEngine_BBDOU> \
		(this, &IllusionsEngine_BBDOU::callback));

void IllusionsEngine_BBDOU::initUpdateFunctions() {
	UPDATEFUNCTION(30, 0, updateScript);
	UPDATEFUNCTION(50, 0, updateActors);
	UPDATEFUNCTION(60, 0, updateMenuKeys);
	UPDATEFUNCTION(60, 0, updateSequences);
	UPDATEFUNCTION(70, 0, updateGraphics);
	UPDATEFUNCTION(70, 0, updateVideoPlayer);
	UPDATEFUNCTION(90, 0, updateSprites);
	UPDATEFUNCTION(120, 0, updateSoundMan);
}

#undef UPDATEFUNCTION

int IllusionsEngine_BBDOU::updateScript(uint flags) {
	_threads->updateThreads();
	return kUFNext;
}

int IllusionsEngine_BBDOU::updateMenuKeys(uint flags) {
	_menuKeys->update();
	return kUFNext;
}

bool IllusionsEngine_BBDOU::causeIsDeclared(uint32 sceneId, uint32 verbId, uint32 objectId2, uint32 objectId) {
	uint32 codeOffs;
	return
		_triggerFunctions->find(sceneId, verbId, objectId2, objectId) ||
		findTriggerCause(sceneId, verbId, objectId2, objectId, codeOffs);
}

void IllusionsEngine_BBDOU::causeDeclare(uint32 verbId, uint32 objectId2, uint32 objectId, TriggerFunctionCallback *callback) {
	_triggerFunctions->add(getCurrentScene(), verbId, objectId2, objectId, callback);
}

uint32 IllusionsEngine_BBDOU::causeTrigger(uint32 sceneId, uint32 verbId, uint32 objectId2, uint32 objectId, uint32 callingThreadId) {
	uint32 codeOffs;
	uint32 causeThreadId = 0;
	TriggerFunction *triggerFunction = _triggerFunctions->find(sceneId, verbId, objectId2, objectId);
	if (triggerFunction) {
		triggerFunction->run(callingThreadId);
	} else if (findTriggerCause(sceneId, verbId, objectId2, objectId, codeOffs)) {
		causeThreadId = startTempScriptThread(_scriptResource->getCode(codeOffs),
			callingThreadId, verbId, objectId2, objectId);
	}
	return causeThreadId;
}

int IllusionsEngine_BBDOU::updateVideoPlayer(uint flags) {
	if (_videoPlayer->isPlaying())
		_videoPlayer->update();
	return kUFNext;
}

void IllusionsEngine_BBDOU::playVideo(uint32 videoId, uint32 objectId, uint32 priority, uint32 callingThreadId) {
	_videoPlayer->start(videoId, objectId, priority, callingThreadId);
}

bool IllusionsEngine_BBDOU::isVideoPlaying() {
	return _videoPlayer->isPlaying();
}

void IllusionsEngine_BBDOU::setDefaultTextCoords() {
	WidthHeight dimensions;
	dimensions._width = 480;
	dimensions._height = 48;
	Common::Point pt(320, 448);
	setDefaultTextDimensions(dimensions);
	setDefaultTextPosition(pt);
}

void IllusionsEngine_BBDOU::loadSpecialCode(uint32 resId) {
	_specialCode = new BbdouSpecialCode(this);
	_specialCode->init();
}

void IllusionsEngine_BBDOU::unloadSpecialCode(uint32 resId) {
	delete _specialCode;
	_specialCode = 0;
}

void IllusionsEngine_BBDOU::notifyThreadId(uint32 &threadId) {
	if (threadId) {
		uint32 tempThreadId = threadId;
		threadId = 0;
		_threads->notifyId(tempThreadId);
	}
}

bool IllusionsEngine_BBDOU::testMainActorFastWalk(Control *control) {
	return false;
}

bool IllusionsEngine_BBDOU::testMainActorCollision(Control *control) {
	// Not used in BBDOU
	return false;
}

Control *IllusionsEngine_BBDOU::getObjectControl(uint32 objectId) {
	return _dict->getObjectControl(objectId);
}

Common::Point IllusionsEngine_BBDOU::getNamedPointPosition(uint32 namedPointId) {
	Common::Point pt;
	if (_backgroundInstances->findActiveBackgroundNamedPoint(namedPointId, pt) ||
		_actorInstances->findNamedPoint(namedPointId, pt) ||
		_controls->findNamedPoint(namedPointId, pt))
		return pt;
	// TODO
	switch (namedPointId) {
	case 0x70001:
		return Common::Point(0, 0);
	case 0x70002:
		return Common::Point(640, 0);
	case 0x70023:
		return Common::Point(320, 240);
	}
	debug("getNamedPointPosition(%08X) UNKNOWN", namedPointId);
	return Common::Point(0, 0);
}

uint32 IllusionsEngine_BBDOU::getPriorityFromBase(int16 priority) {
	return 32000000 * priority;
}

uint32 IllusionsEngine_BBDOU::getCurrentScene() {
	return _activeScenes.getCurrentScene();
}

uint32 IllusionsEngine_BBDOU::getPrevScene() {
	return _prevSceneId;
}

bool IllusionsEngine_BBDOU::isCursorObject(uint32 actorTypeId, uint32 objectId) {
	return actorTypeId == 0x50001 && objectId == Illusions::CURSOR_OBJECT_ID;
}

void IllusionsEngine_BBDOU::setCursorControlRoutine(Control *control) {
	control->_actor->setControlRoutine(new Common::Functor2Mem<Control*, uint32, void, IllusionsEngine_BBDOU>
		(this, &IllusionsEngine_BBDOU::cursorControlRoutine));
}

void IllusionsEngine_BBDOU::placeCursorControl(Control *control, uint32 sequenceId) {
	_cursor->place(control, sequenceId);
}

void IllusionsEngine_BBDOU::setCursorControl(Control *control) {
	_cursor->setControl(control);
}

void IllusionsEngine_BBDOU::showCursor() {
	_cursor->show();
}

void IllusionsEngine_BBDOU::hideCursor() {
	_cursor->hide();
}

void IllusionsEngine_BBDOU::cursorControlRoutine(Control *control, uint32 deltaTime) {
	control->_actor->_seqCodeValue1 = 100 * deltaTime;
	if (control->_actor->_flags & Illusions::ACTOR_FLAG_IS_VISIBLE) {
		switch (_cursor->_status) {
		case 2:
			// Unused nullsub_1(control);
			break;
		case 3:
			_menuSystem->update(control);
			break;
		}
	}
}

void IllusionsEngine_BBDOU::startScriptThreadSimple(uint32 threadId, uint32 callingThreadId) {
	startScriptThread(threadId, callingThreadId, 0, 0, 0);
}

void IllusionsEngine_BBDOU::startScriptThread(uint32 threadId, uint32 callingThreadId,
	uint32 value8, uint32 valueC, uint32 value10) {
	if (threadId == 0x0002041E && ConfMan.hasKey("save_slot")) {
		// Skip intro videos when loading a savegame from the launcher (kludge)
		notifyThreadId(callingThreadId);
		return;
	}
	debug(2, "Starting script thread %08X", threadId);
	byte *scriptCodeIp = _scriptResource->getThreadCode(threadId);
	newScriptThread(threadId, callingThreadId, 0, scriptCodeIp, value8, valueC, value10);
}

void IllusionsEngine_BBDOU::startAnonScriptThread(int32 threadId, uint32 callingThreadId,
	uint32 value8, uint32 valueC, uint32 value10) {
	debug(2, "Starting anonymous script thread %08X", threadId);
	uint32 tempThreadId = newTempThreadId();
	byte *scriptCodeIp = _scriptResource->getThreadCode(threadId);
	scriptCodeIp = _scriptResource->getThreadCode(threadId);
	newScriptThread(tempThreadId, callingThreadId, 0, scriptCodeIp, value8, valueC, value10);
}

uint32 IllusionsEngine_BBDOU::startAbortableTimerThread(uint32 duration, uint32 threadId) {
	return newTimerThread(duration, threadId, true);
}

uint32 IllusionsEngine_BBDOU::startTimerThread(uint32 duration, uint32 threadId) {
	return newTimerThread(duration, threadId, false);
}

uint32 IllusionsEngine_BBDOU::startAbortableThread(byte *scriptCodeIp1, byte *scriptCodeIp2, uint32 callingThreadId) {
	uint32 tempThreadId = newTempThreadId();
	debug(2, "Starting abortable thread %08X", tempThreadId);
	uint32 scriptThreadId = startTempScriptThread(scriptCodeIp1, tempThreadId, 0, 0, 0);
	AbortableThread *abortableThread = new AbortableThread(this, tempThreadId, callingThreadId, 0,
		scriptThreadId, scriptCodeIp2);
	_threads->startThread(abortableThread);
	return tempThreadId;
}

uint32 IllusionsEngine_BBDOU::startTalkThread(int16 duration, uint32 objectId, uint32 talkId, uint32 sequenceId1,
	uint32 sequenceId2, uint32 namedPointId, uint32 callingThreadId) {
	debug(2, "Starting talk thread");
	uint32 tempThreadId = newTempThreadId();
	_threads->endTalkThreadsNoNotify();
	TalkThread *talkThread = new TalkThread(this, tempThreadId, callingThreadId, 0,
		duration, objectId, talkId, sequenceId1, sequenceId2, namedPointId);
	_threads->startThread(talkThread);
	return tempThreadId;
}

uint32 IllusionsEngine_BBDOU::startTempScriptThread(byte *scriptCodeIp, uint32 callingThreadId,
	uint32 value8, uint32 valueC, uint32 value10) {
	uint32 tempThreadId = newTempThreadId();
	debug(2, "Starting temp script thread %08X", tempThreadId);
	newScriptThread(tempThreadId, callingThreadId, 0, scriptCodeIp, value8, valueC, value10);
	return tempThreadId;
}

void IllusionsEngine_BBDOU::newScriptThread(uint32 threadId, uint32 callingThreadId, uint notifyFlags,
	byte *scriptCodeIp, uint32 value8, uint32 valueC, uint32 value10) {
	ScriptThread *scriptThread = new ScriptThread(this, threadId, callingThreadId, notifyFlags,
		scriptCodeIp, value8, valueC, value10);
	_threads->startThread(scriptThread);
	if (_pauseCtr > 0)
		scriptThread->pause();
	if (_doScriptThreadInit) {
		int updateResult = kTSRun;
		while (scriptThread->_pauseCtr <= 0 && updateResult != kTSTerminate && updateResult != kTSYield) {
			updateResult = scriptThread->update();
		}
	}
}

uint32 IllusionsEngine_BBDOU::newTimerThread(uint32 duration, uint32 callingThreadId, bool isAbortable) {
	uint32 tempThreadId = newTempThreadId();
	TimerThread *timerThread = new TimerThread(this, tempThreadId, callingThreadId, 0,
		duration, isAbortable);
	_threads->startThread(timerThread);
	return tempThreadId;
}

uint32 IllusionsEngine_BBDOU::newTempThreadId() {
	uint32 threadId = _nextTempThreadId + 2 * _scriptResource->_codeCount;
	if (threadId > 65535) {
		_nextTempThreadId = 0;
		threadId = 2 * _scriptResource->_codeCount;
	}
	++_nextTempThreadId;
	return 0x00020000 | threadId;
}

bool IllusionsEngine_BBDOU::enterScene(uint32 sceneId, uint32 threadId) {
	SceneInfo *sceneInfo = _scriptResource->getSceneInfo(sceneId & 0xFFFF);
	if (!sceneInfo) {
		dumpActiveScenes(_globalSceneId, threadId);
		sceneId = _theSceneId;
	}
	_activeScenes.push(sceneId);
	if (sceneId == 0x0001007D) {
		// Savegame loading from the ScummVM GUI or command line is only
		// possible after resources have been initialized by the startup script.
		// Once that script is done, it switches to the start menu scene.
		// After that the game is ready and a savegame can finally be loaded.
		_canResumeFromSavegame = true;
	}
	return sceneInfo != 0;
}

void IllusionsEngine_BBDOU::exitScene(uint32 threadId) {
	uint32 sceneId = _activeScenes.getCurrentScene();
	_updateFunctions->terminateByScene(sceneId);
	_threads->terminateThreadsBySceneId(sceneId, threadId);
	_controls->destroyControlsBySceneId(sceneId);
	_triggerFunctions->removeBySceneId(sceneId);
	_resSys->unloadResourcesBySceneId(sceneId);
	_activeScenes.pop();
}

void IllusionsEngine_BBDOU::enterPause(uint32 threadId) {
	uint32 sceneId = _activeScenes.getCurrentScene();
	_camera->pushCameraMode();
	_threads->suspendThreadsBySceneId(sceneId, threadId);
	_controls->pauseControlsBySceneId(sceneId);
	_actorInstances->pauseBySceneId(sceneId);
	_backgroundInstances->pauseBySceneId(sceneId);
	_activeScenes.pauseActiveScene();
}

void IllusionsEngine_BBDOU::leavePause(uint32 threadId) {
	uint32 sceneId = _activeScenes.getCurrentScene();
	_backgroundInstances->unpauseBySceneId(sceneId);
	_actorInstances->unpauseBySceneId(sceneId);
	_controls->unpauseControlsBySceneId(sceneId);
	_threads->notifyThreadsBySceneId(sceneId, threadId);
	_camera->popCameraMode();
	_activeScenes.unpauseActiveScene();
}

void IllusionsEngine_BBDOU::dumpActiveScenes(uint32 sceneId, uint32 threadId) {
	uint activeScenesCount = _activeScenes.getActiveScenesCount();
	while (activeScenesCount > 0) {
		uint32 activeSceneId;
		_activeScenes.getActiveSceneInfo(activeScenesCount, &activeSceneId, 0);
		if (activeSceneId == sceneId)
			break;
		exitScene(threadId);
		--activeScenesCount;
	}
	_camera->clearCameraModeStack();
}

void IllusionsEngine_BBDOU::pause(uint32 callerThreadId) {
	if (++_pauseCtr == 1) {
		_threads->pauseThreads(callerThreadId);
		_camera->pause();
		pauseFader();
		_controls->pauseActors(0x40004);
	}
}

void IllusionsEngine_BBDOU::unpause(uint32 callerThreadId) {
	if (--_pauseCtr == 0) {
		_controls->unpauseActors(0x40004);
		unpauseFader();
		_camera->unpause();
		_threads->unpauseThreads(callerThreadId);
	}
}

void IllusionsEngine_BBDOU::enterMenuPause() {
	// TODO suspendAudio();
	_screenText->clearText();
}

void IllusionsEngine_BBDOU::leaveMenuPause() {
	_screenText->removeText();
	// TODO unsuspendAudio();
}

void IllusionsEngine_BBDOU::setSceneIdThreadId(uint32 theSceneId, uint32 theThreadId) {
	_theSceneId = theSceneId;
	_theThreadId = theThreadId;
}

bool IllusionsEngine_BBDOU::findTriggerCause(uint32 sceneId, uint32 verbId, uint32 objectId2, uint32 objectId, uint32 &codeOffs) {
	SceneInfo *sceneInfo = _scriptResource->getSceneInfo(sceneId & 0xFFFF);
	if (sceneInfo)
		return sceneInfo->findTriggerCause(verbId, objectId2, objectId, codeOffs);
	return false;
}

void IllusionsEngine_BBDOU::reset() {
	_scriptResource->_blockCounters.clear();
	_scriptResource->_properties.clear();
	setTextDuration(1, 0);
}

void IllusionsEngine_BBDOU::loadSavegameFromScript(int16 slotNum, uint32 callingThreadId) {
	// NOTE Just loads the savegame, doesn't activate it yet
	const char *fileName = getSavegameFilename(_savegameSlotNum);
	_loadGameResult = loadgame(fileName);
}

void IllusionsEngine_BBDOU::saveSavegameFromScript(int16 slotNum, uint32 callingThreadId) {
	// TODO
	// const char *fileName = getSavegameFilename(slotNum);
	_saveGameResult = false;//savegame(fileName, _savegameDescription.c_str());
}

void IllusionsEngine_BBDOU::activateSavegame(uint32 callingThreadId) {
	uint32 sceneId, threadId;
	_prevSceneId = 0x10000;
	_gameState->readState(sceneId, threadId);
	enterScene(sceneId, callingThreadId);
	// TODO Check if value8, valueC, value10 are needed at all
	startAnonScriptThread(threadId, 0, 0, 0, 0);
	_gameState->deleteReadStream();
}

void IllusionsEngine_BBDOU::resumeFromSavegame() {
	// Resetting the game is usually done by the script, when loading from the ScummVM menu or
	// command line this has to be done manually.
	_specialCode->resetBeforeResumeSavegame();
	dumpActiveScenes(0x00010003, 0);
	activateSavegame(0);
}

} // End of namespace Illusions