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

/*
 * This file is based on WME Lite.
 * http://dead-code.org/redir.php?target=wmelite
 * Copyright (c) 2011 Jan Nedoma
 */

#include "engines/wintermute/ad/ad_actor.h"
#include "engines/wintermute/ad/ad_game.h"
#include "engines/wintermute/ad/ad_entity.h"
#include "engines/wintermute/ad/ad_inventory.h"
#include "engines/wintermute/ad/ad_inventory_box.h"
#include "engines/wintermute/ad/ad_item.h"
#include "engines/wintermute/ad/ad_response.h"
#include "engines/wintermute/ad/ad_response_box.h"
#include "engines/wintermute/ad/ad_response_context.h"
#include "engines/wintermute/ad/ad_scene.h"
#include "engines/wintermute/ad/ad_scene_state.h"
#include "engines/wintermute/ad/ad_sentence.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/base/font/base_font.h"
#include "engines/wintermute/base/base_object.h"
#include "engines/wintermute/base/base_parser.h"
#include "engines/wintermute/base/sound/base_sound.h"
#include "engines/wintermute/base/base_surface_storage.h"
#include "engines/wintermute/base/base_transition_manager.h"
#include "engines/wintermute/base/base_sprite.h"
#include "engines/wintermute/base/base_viewport.h"
#include "engines/wintermute/base/particles/part_emitter.h"
#include "engines/wintermute/base/saveload.h"
#include "engines/wintermute/base/gfx/base_renderer.h"
#include "engines/wintermute/base/scriptables/script_engine.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "engines/wintermute/base/scriptables/script_value.h"
#include "engines/wintermute/ui/ui_entity.h"
#include "engines/wintermute/ui/ui_window.h"
#include "engines/wintermute/utils/utils.h"
#include "engines/wintermute/video/video_player.h"
#include "engines/wintermute/video/video_theora_player.h"
#include "engines/wintermute/platform_osystem.h"
#include "common/str.h"

namespace Wintermute {

IMPLEMENT_PERSISTENT(AdGame, true)

//////////////////////////////////////////////////////////////////////////
AdGame::AdGame(const Common::String &gameId) : BaseGame(gameId) {
	_responseBox = nullptr;
	_inventoryBox = nullptr;

	_scene = new AdScene(_gameRef);
	_scene->setName("");
	registerObject(_scene);

	_prevSceneName = nullptr;
	_prevSceneFilename = nullptr;
	_scheduledScene = nullptr;
	_scheduledFadeIn = false;


	_stateEx = GAME_NORMAL;

	_selectedItem = nullptr;


	_texItemLifeTime = 10000;
	_texWalkLifeTime = 10000;
	_texStandLifeTime = 10000;
	_texTalkLifeTime = 10000;

	_talkSkipButton = TALK_SKIP_LEFT;

	_sceneViewport = nullptr;

	_initialScene = true;
	_debugStartupScene = nullptr;
	_startupScene = nullptr;

	_invObject = new AdObject(this);
	_inventoryOwner = _invObject;

	_tempDisableSaveState = false;
	_itemsFile = nullptr;

	_smartItemCursor = false;

	addSpeechDir("speech");
}


//////////////////////////////////////////////////////////////////////////
AdGame::~AdGame() {
	cleanup();
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::cleanup() {
	for (uint32 i = 0; i < _objects.size(); i++) {
		unregisterObject(_objects[i]);
		_objects[i] = nullptr;
	}
	_objects.clear();


	for (uint32 i = 0; i < _dlgPendingBranches.size(); i++) {
		delete[] _dlgPendingBranches[i];
	}
	_dlgPendingBranches.clear();

	for (uint32 i = 0; i < _speechDirs.size(); i++) {
		delete[] _speechDirs[i];
	}
	_speechDirs.clear();


	unregisterObject(_scene);
	_scene = nullptr;

	// remove items
	for (uint32 i = 0; i < _items.size(); i++) {
		_gameRef->unregisterObject(_items[i]);
	}
	_items.clear();


	// clear remaining inventories
	delete _invObject;
	_invObject = nullptr;

	for (uint32 i = 0; i < _inventories.size(); i++) {
		delete _inventories[i];
	}
	_inventories.clear();


	if (_responseBox) {
		_gameRef->unregisterObject(_responseBox);
		_responseBox = nullptr;
	}

	if (_inventoryBox) {
		_gameRef->unregisterObject(_inventoryBox);
		_inventoryBox = nullptr;
	}

	delete[] _prevSceneName;
	delete[] _prevSceneFilename;
	delete[] _scheduledScene;
	delete[] _debugStartupScene;
	delete[] _itemsFile;
	_prevSceneName = nullptr;
	_prevSceneFilename = nullptr;
	_scheduledScene = nullptr;
	_debugStartupScene = nullptr;
	_startupScene = nullptr;
	_itemsFile = nullptr;

	delete _sceneViewport;
	_sceneViewport = nullptr;

	for (uint32 i = 0; i < _sceneStates.size(); i++) {
		delete _sceneStates[i];
	}
	_sceneStates.clear();

	for (uint32 i = 0; i < _responsesBranch.size(); i++) {
		delete _responsesBranch[i];
	}
	_responsesBranch.clear();

	for (uint32 i = 0; i < _responsesGame.size(); i++) {
		delete _responsesGame[i];
	}
	_responsesGame.clear();

	return BaseGame::cleanup();
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::initLoop() {
	if (_scheduledScene && _transMgr->isReady()) {
		changeScene(_scheduledScene, _scheduledFadeIn);
		delete[] _scheduledScene;
		_scheduledScene = nullptr;

		_gameRef->_activeObject = nullptr;
	}


	bool res;
	res = BaseGame::initLoop();
	if (DID_FAIL(res)) {
		return res;
	}

	if (_scene) {
		res = _scene->initLoop();
	}

	_sentences.clear();

	return res;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::addObject(AdObject *object) {
	_objects.add(object);
	return registerObject(object);
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::removeObject(AdObject *object) {
	// in case the user called Scene.CreateXXX() and Game.DeleteXXX()
	if (_scene) {
		bool res = _scene->removeObject(object);
		if (DID_SUCCEED(res)) {
			return res;
		}
	}

	for (uint32 i = 0; i < _objects.size(); i++) {
		if (_objects[i] == object) {
			_objects.remove_at(i);
			break;
		}
	}
	return unregisterObject(object);
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::changeScene(const char *filename, bool fadeIn) {
	if (_scene == nullptr) {
		_scene = new AdScene(_gameRef);
		registerObject(_scene);
	} else {
		_scene->applyEvent("SceneShutdown", true);

		setPrevSceneName(_scene->getName());
		setPrevSceneFilename(_scene->getFilename());

		if (!_tempDisableSaveState) {
			_scene->saveState();
		}
		_tempDisableSaveState = false;
	}

	if (_scene) {
		// reset objects
		for (uint32 i = 0; i < _objects.size(); i++) {
			_objects[i]->reset();
		}

		// reset scene properties
		_scene->_sFXVolume = 100;
		if (_scene->_scProp) {
			_scene->_scProp->cleanup();
		}

		bool ret;
		if (_initialScene && _debugDebugMode && _debugStartupScene) {
			_initialScene = false;
			ret = _scene->loadFile(_debugStartupScene);
		} else {
			ret = _scene->loadFile(filename);
		}

		if (DID_SUCCEED(ret)) {
			// invalidate references to the original scene
			for (uint32 i = 0; i < _objects.size(); i++) {
				_objects[i]->invalidateCurrRegions();
				_objects[i]->_stickRegion = nullptr;
			}

			_scene->loadState();
		}
		if (fadeIn) {
			_gameRef->_transMgr->start(TRANSITION_FADE_IN);
		}
		return ret;
	} else {
		return STATUS_FAILED;
	}
}


//////////////////////////////////////////////////////////////////////////
void AdGame::addSentence(AdSentence *sentence) {
	_sentences.add(sentence);
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::displaySentences(bool frozen) {
	for (uint32 i = 0; i < _sentences.size(); i++) {
		if (frozen && _sentences[i]->_freezable) {
			continue;
		} else {
			_sentences[i]->display();
		}
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
void AdGame::finishSentences() {
	for (uint32 i = 0; i < _sentences.size(); i++) {
		if (_sentences[i]->canSkip()) {
			_sentences[i]->_duration = 0;
			if (_sentences[i]->_sound) {
				_sentences[i]->_sound->stop();
			}
		}
	}
}


//////////////////////////////////////////////////////////////////////////
// high level scripting interface
//////////////////////////////////////////////////////////////////////////
bool AdGame::scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) {
	//////////////////////////////////////////////////////////////////////////
	// ChangeScene
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "ChangeScene") == 0) {
		stack->correctParams(3);
		const char *filename = stack->pop()->getString();
		ScValue *valFadeOut = stack->pop();
		ScValue *valFadeIn = stack->pop();

		bool transOut = valFadeOut->isNULL() ? true : valFadeOut->getBool();
		bool transIn  = valFadeIn->isNULL() ? true : valFadeIn->getBool();

		scheduleChangeScene(filename, transIn);
		if (transOut) {
			_transMgr->start(TRANSITION_FADE_OUT, true);
		}
		stack->pushNULL();


		//bool ret = ChangeScene(stack->pop()->getString());
		//if (DID_FAIL(ret)) stack->pushBool(false);
		//else stack->pushBool(true);

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// LoadActor
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "LoadActor") == 0) {
		stack->correctParams(1);
		AdActor *act = new AdActor(_gameRef);
		if (act && DID_SUCCEED(act->loadFile(stack->pop()->getString()))) {
			addObject(act);
			stack->pushNative(act, true);
		} else {
			delete act;
			act = nullptr;
			stack->pushNULL();
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// LoadEntity
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "LoadEntity") == 0) {
		stack->correctParams(1);
		AdEntity *ent = new AdEntity(_gameRef);
		if (ent && DID_SUCCEED(ent->loadFile(stack->pop()->getString()))) {
			addObject(ent);
			stack->pushNative(ent, true);
		} else {
			delete ent;
			ent = nullptr;
			stack->pushNULL();
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// UnloadObject / UnloadActor / UnloadEntity / DeleteEntity
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "UnloadObject") == 0 || strcmp(name, "UnloadActor") == 0 || strcmp(name, "UnloadEntity") == 0 || strcmp(name, "DeleteEntity") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();
		AdObject *obj = (AdObject *)val->getNative();
		removeObject(obj);
		if (val->getType() == VAL_VARIABLE_REF) {
			val->setNULL();
		}

		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// CreateEntity
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "CreateEntity") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();

		AdEntity *ent = new AdEntity(_gameRef);
		addObject(ent);
		if (!val->isNULL()) {
			ent->setName(val->getString());
		}
		stack->pushNative(ent, true);
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// CreateItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "CreateItem") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();

		AdItem *item = new AdItem(_gameRef);
		addItem(item);
		if (!val->isNULL()) {
			item->setName(val->getString());
		}
		stack->pushNative(item, true);
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// DeleteItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "DeleteItem") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();

		AdItem *item = nullptr;
		if (val->isNative()) {
			item = (AdItem *)val->getNative();
		} else {
			item = getItemByName(val->getString());
		}

		if (item) {
			deleteItem(item);
		}

		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// QueryItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "QueryItem") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();

		AdItem *item = nullptr;
		if (val->isInt()) {
			int index = val->getInt();
			if (index >= 0 && index < (int32)_items.size()) {
				item = _items[index];
			}
		} else {
			item = getItemByName(val->getString());
		}

		if (item) {
			stack->pushNative(item, true);
		} else {
			stack->pushNULL();
		}

		return STATUS_OK;
	}


	//////////////////////////////////////////////////////////////////////////
	// AddResponse/AddResponseOnce/AddResponseOnceGame
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "AddResponse") == 0 || strcmp(name, "AddResponseOnce") == 0 || strcmp(name, "AddResponseOnceGame") == 0) {
		stack->correctParams(6);
		int id = stack->pop()->getInt();
		const char *text = stack->pop()->getString();
		ScValue *val1 = stack->pop();
		ScValue *val2 = stack->pop();
		ScValue *val3 = stack->pop();
		ScValue *val4 = stack->pop();

		if (_responseBox) {
			AdResponse *res = new AdResponse(_gameRef);
			if (res) {
				res->setID(id);

				char *expandedText = new char[strlen(text) + 1];
				Common::strlcpy(expandedText, text, strlen(text) + 1);
				expandStringByStringTable(&expandedText);
				res->setText(expandedText);
				delete[] expandedText;

				if (!val1->isNULL()) {
					res->setIcon(val1->getString());
				}
				if (!val2->isNULL()) {
					res->setIconHover(val2->getString());
				}
				if (!val3->isNULL()) {
					res->setIconPressed(val3->getString());
				}
				if (!val4->isNULL()) {
					res->setFont(val4->getString());
				}

				if (strcmp(name, "AddResponseOnce") == 0) {
					res->_responseType = RESPONSE_ONCE;
				} else if (strcmp(name, "AddResponseOnceGame") == 0) {
					res->_responseType = RESPONSE_ONCE_GAME;
				}

				_responseBox->addResponse(res);
			}
		} else {
			script->runtimeError("Game.AddResponse: response box is not defined");
		}
		stack->pushNULL();

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// ResetResponse
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "ResetResponse") == 0) {
		stack->correctParams(1);
		int id = stack->pop()->getInt(-1);
		resetResponse(id);
		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// ClearResponses
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "ClearResponses") == 0) {
		stack->correctParams(0);
		_responseBox->clearResponses();
		_responseBox->clearButtons();
		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetResponse
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetResponse") == 0) {
		stack->correctParams(1);
		bool autoSelectLast = stack->pop()->getBool();

		if (_responseBox) {
			_responseBox->weedResponses();

			if (_responseBox->getNumResponses() == 0) {
				stack->pushNULL();
				return STATUS_OK;
			}


			if (_responseBox->getNumResponses() == 1 && autoSelectLast) {
				stack->pushInt(_responseBox->getIdForResponseNum(0));
				_responseBox->handleResponseNum(0);
				_responseBox->clearResponses();
				return STATUS_OK;
			}

			_responseBox->createButtons();
			_responseBox->_waitingScript = script;
			script->waitForExclusive(_responseBox);
			_state = GAME_SEMI_FROZEN;
			_stateEx = GAME_WAITING_RESPONSE;
		} else {
			script->runtimeError("Game.GetResponse: response box is not defined");
			stack->pushNULL();
		}
		return STATUS_OK;
	}


	//////////////////////////////////////////////////////////////////////////
	// GetNumResponses
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetNumResponses") == 0) {
		stack->correctParams(0);
		if (_responseBox) {
			_responseBox->weedResponses();
			stack->pushInt(_responseBox->getNumResponses());
		} else {
			script->runtimeError("Game.GetNumResponses: response box is not defined");
			stack->pushNULL();
		}
		return STATUS_OK;
	}


	//////////////////////////////////////////////////////////////////////////
	// StartDlgBranch
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "StartDlgBranch") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();
		Common::String branchName;
		if (val->isNULL()) {
			branchName.format("line%d", script->_currentLine);
		} else {
			branchName = val->getString();
		}

		startDlgBranch(branchName.c_str(), script->_filename == nullptr ? "" : script->_filename, script->_threadEvent == nullptr ? "" : script->_threadEvent);
		stack->pushNULL();

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// EndDlgBranch
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "EndDlgBranch") == 0) {
		stack->correctParams(1);

		const char *branchName = nullptr;
		ScValue *val = stack->pop();
		if (!val->isNULL()) {
			branchName = val->getString();
		}
		endDlgBranch(branchName, script->_filename == nullptr ? "" : script->_filename, script->_threadEvent == nullptr ? "" : script->_threadEvent);

		stack->pushNULL();

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetCurrentDlgBranch
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetCurrentDlgBranch") == 0) {
		stack->correctParams(0);

		if (_dlgPendingBranches.size() > 0) {
			stack->pushString(_dlgPendingBranches[_dlgPendingBranches.size() - 1]);
		} else {
			stack->pushNULL();
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// TakeItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "TakeItem") == 0) {
		return _invObject->scCallMethod(script, stack, thisStack, name);
	}

	//////////////////////////////////////////////////////////////////////////
	// DropItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "DropItem") == 0) {
		return _invObject->scCallMethod(script, stack, thisStack, name);
	}

	//////////////////////////////////////////////////////////////////////////
	// GetItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetItem") == 0) {
		return _invObject->scCallMethod(script, stack, thisStack, name);
	}

	//////////////////////////////////////////////////////////////////////////
	// HasItem
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "HasItem") == 0) {
		return _invObject->scCallMethod(script, stack, thisStack, name);
	}

	//////////////////////////////////////////////////////////////////////////
	// IsItemTaken
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "IsItemTaken") == 0) {
		stack->correctParams(1);

		ScValue *val = stack->pop();
		if (!val->isNULL()) {
			for (uint32 i = 0; i < _inventories.size(); i++) {
				AdInventory *inv = _inventories[i];

				for (uint32 j = 0; j < inv->_takenItems.size(); j++) {
					if (val->getNative() == inv->_takenItems[j]) {
						stack->pushBool(true);
						return STATUS_OK;
					} else if (scumm_stricmp(val->getString(), inv->_takenItems[j]->getName()) == 0) {
						stack->pushBool(true);
						return STATUS_OK;
					}
				}
			}
		} else {
			script->runtimeError("Game.IsItemTaken: item name expected");
		}

		stack->pushBool(false);
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetInventoryWindow
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetInventoryWindow") == 0) {
		stack->correctParams(0);
		if (_inventoryBox && _inventoryBox->_window) {
			stack->pushNative(_inventoryBox->_window, true);
		} else {
			stack->pushNULL();
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// GetResponsesWindow
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "GetResponsesWindow") == 0 || strcmp(name, "GetResponseWindow") == 0) {
		stack->correctParams(0);
		if (_responseBox && _responseBox->getResponseWindow()) {
			stack->pushNative(_responseBox->getResponseWindow(), true);
		} else {
			stack->pushNULL();
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// LoadResponseBox
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "LoadResponseBox") == 0) {
		stack->correctParams(1);
		const char *filename = stack->pop()->getString();

		_gameRef->unregisterObject(_responseBox);
		_responseBox = new AdResponseBox(_gameRef);
		if (_responseBox && !DID_FAIL(_responseBox->loadFile(filename))) {
			registerObject(_responseBox);
			stack->pushBool(true);
		} else {
			delete _responseBox;
			_responseBox = nullptr;
			stack->pushBool(false);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// LoadInventoryBox
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "LoadInventoryBox") == 0) {
		stack->correctParams(1);
		const char *filename = stack->pop()->getString();

		_gameRef->unregisterObject(_inventoryBox);
		_inventoryBox = new AdInventoryBox(_gameRef);
		if (_inventoryBox && !DID_FAIL(_inventoryBox->loadFile(filename))) {
			registerObject(_inventoryBox);
			stack->pushBool(true);
		} else {
			delete _inventoryBox;
			_inventoryBox = nullptr;
			stack->pushBool(false);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// LoadItems
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "LoadItems") == 0) {
		stack->correctParams(2);
		const char *filename = stack->pop()->getString();
		bool merge = stack->pop()->getBool(false);

		bool ret = loadItemsFile(filename, merge);
		stack->pushBool(DID_SUCCEED(ret));

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// AddSpeechDir
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "AddSpeechDir") == 0) {
		stack->correctParams(1);
		const char *dir = stack->pop()->getString();
		stack->pushBool(DID_SUCCEED(addSpeechDir(dir)));

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// RemoveSpeechDir
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "RemoveSpeechDir") == 0) {
		stack->correctParams(1);
		const char *dir = stack->pop()->getString();
		stack->pushBool(DID_SUCCEED(removeSpeechDir(dir)));

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SetSceneViewport
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SetSceneViewport") == 0) {
		stack->correctParams(4);
		int x = stack->pop()->getInt();
		int y = stack->pop()->getInt();
		int width = stack->pop()->getInt();
		int height = stack->pop()->getInt();

		if (width <= 0) {
			width = _renderer->getWidth();
		}
		if (height <= 0) {
			height = _renderer->getHeight();
		}

		if (!_sceneViewport) {
			_sceneViewport = new BaseViewport(_gameRef);
		}
		if (_sceneViewport) {
			_sceneViewport->setRect(x, y, x + width, y + height);
		}

		stack->pushBool(true);

		return STATUS_OK;
	}


	else {
		return BaseGame::scCallMethod(script, stack, thisStack, name);
	}
}


//////////////////////////////////////////////////////////////////////////
ScValue *AdGame::scGetProperty(const Common::String &name) {
	_scValue->setNULL();

	//////////////////////////////////////////////////////////////////////////
	// Type
	//////////////////////////////////////////////////////////////////////////
	if (name == "Type") {
		_scValue->setString("game");
		return _scValue;
	}
	//////////////////////////////////////////////////////////////////////////
	// Scene
	//////////////////////////////////////////////////////////////////////////
	else if (name == "Scene") {
		if (_scene) {
			_scValue->setNative(_scene, true);
		} else {
			_scValue->setNULL();
		}

		return _scValue;
	}
	//////////////////////////////////////////////////////////////////////////
	// SelectedItem
	//////////////////////////////////////////////////////////////////////////
	else if (name == "SelectedItem") {
		//if (_selectedItem) _scValue->setString(_selectedItem->_name);
		if (_selectedItem) {
			_scValue->setNative(_selectedItem, true);
		} else {
			_scValue->setNULL();
		}

		return _scValue;
	}
	//////////////////////////////////////////////////////////////////////////
	// NumItems
	//////////////////////////////////////////////////////////////////////////
	else if (name == "NumItems") {
		return _invObject->scGetProperty(name);
	}

	//////////////////////////////////////////////////////////////////////////
	// SmartItemCursor
	//////////////////////////////////////////////////////////////////////////
	else if (name == "SmartItemCursor") {
		_scValue->setBool(_smartItemCursor);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryVisible
	//////////////////////////////////////////////////////////////////////////
	else if (name == "InventoryVisible") {
		_scValue->setBool(_inventoryBox && _inventoryBox->_visible);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryScrollOffset
	//////////////////////////////////////////////////////////////////////////
	else if (name == "InventoryScrollOffset") {
		if (_inventoryBox) {
			_scValue->setInt(_inventoryBox->_scrollOffset);
		} else {
			_scValue->setInt(0);
		}

		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// ResponsesVisible (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "ResponsesVisible") {
		_scValue->setBool(_stateEx == GAME_WAITING_RESPONSE);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// PrevScene / PreviousScene (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "PrevScene" || name == "PreviousScene") {
		if (!_prevSceneName) {
			_scValue->setString("");
		} else {
			_scValue->setString(_prevSceneName);
		}
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// PrevSceneFilename / PreviousSceneFilename (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "PrevSceneFilename" || name == "PreviousSceneFilename") {
		if (!_prevSceneFilename) {
			_scValue->setString("");
		} else {
			_scValue->setString(_prevSceneFilename);
		}
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// LastResponse (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "LastResponse") {
		if (!_responseBox || !_responseBox->getLastResponseText()) {
			_scValue->setString("");
		} else {
			_scValue->setString(_responseBox->getLastResponseText());
		}
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// LastResponseOrig (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "LastResponseOrig") {
		if (!_responseBox || !_responseBox->getLastResponseTextOrig()) {
			_scValue->setString("");
		} else {
			_scValue->setString(_responseBox->getLastResponseTextOrig());
		}
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryObject
	//////////////////////////////////////////////////////////////////////////
	else if (name == "InventoryObject") {
		if (_inventoryOwner == _invObject) {
			_scValue->setNative(this, true);
		} else {
			_scValue->setNative(_inventoryOwner, true);
		}

		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// TotalNumItems
	//////////////////////////////////////////////////////////////////////////
	else if (name == "TotalNumItems") {
		_scValue->setInt(_items.size());
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// TalkSkipButton
	//////////////////////////////////////////////////////////////////////////
	else if (name == "TalkSkipButton") {
		_scValue->setInt(_talkSkipButton);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// ChangingScene
	//////////////////////////////////////////////////////////////////////////
	else if (name == "ChangingScene") {
		_scValue->setBool(_scheduledScene != nullptr);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// StartupScene
	//////////////////////////////////////////////////////////////////////////
	else if (name == "StartupScene") {
		if (!_startupScene) {
			_scValue->setNULL();
		} else {
			_scValue->setString(_startupScene);
		}
		return _scValue;
	}

	else {
		return BaseGame::scGetProperty(name);
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::scSetProperty(const char *name, ScValue *value) {

	//////////////////////////////////////////////////////////////////////////
	// SelectedItem
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "SelectedItem") == 0) {
		if (value->isNULL()) {
			_selectedItem = nullptr;
		} else {
			if (value->isNative()) {
				_selectedItem = nullptr;
				for (uint32 i = 0; i < _items.size(); i++) {
					if (_items[i] == value->getNative()) {
						_selectedItem = (AdItem *)value->getNative();
						break;
					}
				}
			} else {
				// try to get by name
				_selectedItem = getItemByName(value->getString());
			}
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// SmartItemCursor
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "SmartItemCursor") == 0) {
		_smartItemCursor = value->getBool();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryVisible
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "InventoryVisible") == 0) {
		if (_inventoryBox) {
			_inventoryBox->_visible = value->getBool();
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryObject
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "InventoryObject") == 0) {
		if (_inventoryOwner && _inventoryBox) {
			_inventoryOwner->getInventory()->_scrollOffset = _inventoryBox->_scrollOffset;
		}

		if (value->isNULL()) {
			_inventoryOwner = _invObject;
		} else {
			BaseObject *obj = (BaseObject *)value->getNative();
			if (obj == this) {
				_inventoryOwner = _invObject;
			} else if (_gameRef->validObject(obj)) {
				_inventoryOwner = (AdObject *)obj;
			}
		}

		if (_inventoryOwner && _inventoryBox) {
			_inventoryBox->_scrollOffset = _inventoryOwner->getInventory()->_scrollOffset;
		}

		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// InventoryScrollOffset
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "InventoryScrollOffset") == 0) {
		if (_inventoryBox) {
			_inventoryBox->_scrollOffset = value->getInt();
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// TalkSkipButton
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "TalkSkipButton") == 0) {
		int val = value->getInt();
		if (val < 0) {
			val = 0;
		}
		if (val > TALK_SKIP_NONE) {
			val = TALK_SKIP_NONE;
		}
		_talkSkipButton = (TTalkSkipButton)val;
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// StartupScene
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "StartupScene") == 0) {
		if (value == nullptr) {
			delete[] _startupScene;
			_startupScene = nullptr;
		} else {
			BaseUtils::setString(&_startupScene, value->getString());
		}

		return STATUS_OK;
	}

	else {
		return BaseGame::scSetProperty(name, value);
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::externalCall(ScScript *script, ScStack *stack, ScStack *thisStack, char *name) {
	ScValue *thisObj;

	//////////////////////////////////////////////////////////////////////////
	// Actor
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "Actor") == 0) {
		stack->correctParams(0);
		thisObj = thisStack->getTop();

		thisObj->setNative(new AdActor(_gameRef));
		stack->pushNULL();
	}

	//////////////////////////////////////////////////////////////////////////
	// Entity
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "Entity") == 0) {
		stack->correctParams(0);
		thisObj = thisStack->getTop();

		thisObj->setNative(new AdEntity(_gameRef));
		stack->pushNULL();
	}


	//////////////////////////////////////////////////////////////////////////
	// call parent
	else {
		return BaseGame::externalCall(script, stack, thisStack, name);
	}


	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::showCursor() {
	if (_cursorHidden) {
		return STATUS_OK;
	}

	if (_selectedItem && _gameRef->_state == GAME_RUNNING && _stateEx == GAME_NORMAL && _interactive) {
		if (_selectedItem->_cursorCombined) {
			BaseSprite *origLastCursor = _lastCursor;
			BaseGame::showCursor();
			_lastCursor = origLastCursor;
		}
		if (_activeObject && _selectedItem->_cursorHover && _activeObject->getExtendedFlag("usable")) {
			if (!_smartItemCursor || _activeObject->canHandleEvent(_selectedItem->getName())) {
				return drawCursor(_selectedItem->_cursorHover);
			} else {
				return drawCursor(_selectedItem->_cursorNormal);
			}
		} else {
			return drawCursor(_selectedItem->_cursorNormal);
		}
	} else {
		return BaseGame::showCursor();
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::loadFile(const char *filename) {
	char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(filename);
	if (buffer == nullptr) {
		_gameRef->LOG(0, "AdGame::LoadFile failed for file '%s'", filename);
		return STATUS_FAILED;
	}

	bool ret;

	setFilename(filename);

	if (DID_FAIL(ret = loadBuffer(buffer, true))) {
		_gameRef->LOG(0, "Error parsing GAME file '%s'", filename);
	}


	delete[] buffer;

	return ret;
}


TOKEN_DEF_START
TOKEN_DEF(GAME)
TOKEN_DEF(AD_GAME)
TOKEN_DEF(RESPONSE_BOX)
TOKEN_DEF(INVENTORY_BOX)
TOKEN_DEF(ITEMS)
TOKEN_DEF(ITEM)
TOKEN_DEF(TALK_SKIP_BUTTON)
TOKEN_DEF(SCENE_VIEWPORT)
TOKEN_DEF(ENTITY_CONTAINER)
TOKEN_DEF(EDITOR_PROPERTY)
TOKEN_DEF(STARTUP_SCENE)
TOKEN_DEF(DEBUG_STARTUP_SCENE)
TOKEN_DEF_END
//////////////////////////////////////////////////////////////////////////
bool AdGame::loadBuffer(char *buffer, bool complete) {
	TOKEN_TABLE_START(commands)
	TOKEN_TABLE(GAME)
	TOKEN_TABLE(AD_GAME)
	TOKEN_TABLE(RESPONSE_BOX)
	TOKEN_TABLE(INVENTORY_BOX)
	TOKEN_TABLE(ITEMS)
	TOKEN_TABLE(TALK_SKIP_BUTTON)
	TOKEN_TABLE(SCENE_VIEWPORT)
	TOKEN_TABLE(EDITOR_PROPERTY)
	TOKEN_TABLE(STARTUP_SCENE)
	TOKEN_TABLE(DEBUG_STARTUP_SCENE)
	TOKEN_TABLE_END

	char *params;
	char *params2;
	int cmd = 1;
	BaseParser parser;

	bool itemFound = false, itemsFound = false;

	while (cmd > 0 && (cmd = parser.getCommand(&buffer, commands, &params)) > 0) {
		switch (cmd) {
		case TOKEN_GAME:
			if (DID_FAIL(BaseGame::loadBuffer(params, false))) {
				cmd = PARSERR_GENERIC;
			}
			break;

		case TOKEN_AD_GAME:
			while (cmd > 0 && (cmd = parser.getCommand(&params, commands, &params2)) > 0) {
				switch (cmd) {
				case TOKEN_RESPONSE_BOX:
					delete _responseBox;
					_responseBox = new AdResponseBox(_gameRef);
					if (_responseBox && !DID_FAIL(_responseBox->loadFile(params2))) {
						registerObject(_responseBox);
					} else {
						delete _responseBox;
						_responseBox = nullptr;
						cmd = PARSERR_GENERIC;
					}
					break;

				case TOKEN_INVENTORY_BOX:
					delete _inventoryBox;
					_inventoryBox = new AdInventoryBox(_gameRef);
					if (_inventoryBox && !DID_FAIL(_inventoryBox->loadFile(params2))) {
						registerObject(_inventoryBox);
					} else {
						delete _inventoryBox;
						_inventoryBox = nullptr;
						cmd = PARSERR_GENERIC;
					}
					break;

				case TOKEN_ITEMS:
					itemsFound = true;
					BaseUtils::setString(&_itemsFile, params2);
					if (DID_FAIL(loadItemsFile(_itemsFile))) {
						delete[] _itemsFile;
						_itemsFile = nullptr;
						cmd = PARSERR_GENERIC;
					}
					break;

				case TOKEN_TALK_SKIP_BUTTON:
					if (scumm_stricmp(params2, "right") == 0) {
						_talkSkipButton = TALK_SKIP_RIGHT;
					} else if (scumm_stricmp(params2, "both") == 0) {
						_talkSkipButton = TALK_SKIP_BOTH;
					} else {
						_talkSkipButton = TALK_SKIP_LEFT;
					}
					break;

				case TOKEN_SCENE_VIEWPORT: {
					Rect32 rc;
					parser.scanStr(params2, "%d,%d,%d,%d", &rc.left, &rc.top, &rc.right, &rc.bottom);
					if (!_sceneViewport) {
						_sceneViewport = new BaseViewport(_gameRef);
					}
					if (_sceneViewport) {
						_sceneViewport->setRect(rc.left, rc.top, rc.right, rc.bottom);
					}
				}
				break;

				case TOKEN_EDITOR_PROPERTY:
					parseEditorProperty(params2, false);
					break;

				case TOKEN_STARTUP_SCENE:
					BaseUtils::setString(&_startupScene, params2);
					break;

				case TOKEN_DEBUG_STARTUP_SCENE:
					BaseUtils::setString(&_debugStartupScene, params2);
					break;
				}
			}
			break;
		}
	}

	if (cmd == PARSERR_TOKENNOTFOUND) {
		_gameRef->LOG(0, "Syntax error in GAME definition");
		return STATUS_FAILED;
	}
	if (cmd == PARSERR_GENERIC) {
		_gameRef->LOG(0, "Error loading GAME definition");
		return STATUS_FAILED;
	}

	if (itemFound && !itemsFound) {
		_gameRef->LOG(0, "**Warning** Please put the items definition to a separate file.");
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::persist(BasePersistenceManager *persistMgr) {
	if (!persistMgr->getIsSaving()) {
		cleanup();
	}
	BaseGame::persist(persistMgr);

	_dlgPendingBranches.persist(persistMgr);

	_inventories.persist(persistMgr);
	persistMgr->transferPtr(TMEMBER_PTR(_inventoryBox));

	_objects.persist(persistMgr);

	persistMgr->transferCharPtr(TMEMBER(_prevSceneName));
	persistMgr->transferCharPtr(TMEMBER(_prevSceneFilename));

	persistMgr->transferPtr(TMEMBER_PTR(_responseBox));
	_responsesBranch.persist(persistMgr);
	_responsesGame.persist(persistMgr);
	persistMgr->transferPtr(TMEMBER_PTR(_scene));
	_sceneStates.persist(persistMgr);
	persistMgr->transferBool(TMEMBER(_scheduledFadeIn));
	persistMgr->transferCharPtr(TMEMBER(_scheduledScene));
	persistMgr->transferPtr(TMEMBER_PTR(_selectedItem));
	persistMgr->transferSint32(TMEMBER_INT(_talkSkipButton));

	_sentences.persist(persistMgr);

	persistMgr->transferPtr(TMEMBER_PTR(_sceneViewport));
	persistMgr->transferSint32(TMEMBER_INT(_stateEx));
	persistMgr->transferBool(TMEMBER(_initialScene));
	persistMgr->transferCharPtr(TMEMBER(_debugStartupScene));

	persistMgr->transferPtr(TMEMBER_PTR(_invObject));
	persistMgr->transferPtr(TMEMBER_PTR(_inventoryOwner));
	persistMgr->transferBool(TMEMBER(_tempDisableSaveState));
	_items.persist(persistMgr);

	persistMgr->transferCharPtr(TMEMBER(_itemsFile));

	_speechDirs.persist(persistMgr);
	persistMgr->transferBool(TMEMBER(_smartItemCursor));

	if (!persistMgr->getIsSaving()) {
		_initialScene = false;
	}

	persistMgr->transferCharPtr(TMEMBER(_startupScene));


	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
void AdGame::setPrevSceneName(const char *name) {
	delete[] _prevSceneName;
	_prevSceneName = nullptr;
	if (name) {
		_prevSceneName = new char[strlen(name) + 1];
		if (_prevSceneName) {
			strcpy(_prevSceneName, name);
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void AdGame::setPrevSceneFilename(const char *name) {
	delete[] _prevSceneFilename;
	_prevSceneFilename = nullptr;
	if (name) {
		_prevSceneFilename = new char[strlen(name) + 1];
		if (_prevSceneFilename) {
			strcpy(_prevSceneFilename, name);
		}
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::scheduleChangeScene(const char *filename, bool fadeIn) {
	delete[] _scheduledScene;
	_scheduledScene = nullptr;

	if (_scene && !_scene->_initialized) {
		return changeScene(filename, fadeIn);
	} else {
		_scheduledScene = new char [strlen(filename) + 1];
		strcpy(_scheduledScene, filename);

		_scheduledFadeIn = fadeIn;

		return STATUS_OK;
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::getVersion(byte *verMajor, byte *verMinor, byte *extMajor, byte *extMinor) const {
	BaseGame::getVersion(verMajor, verMinor, nullptr, nullptr);

	if (extMajor) {
		*extMajor = 0;
	}
	if (extMinor) {
		*extMinor = 0;
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::loadItemsFile(const char *filename, bool merge) {
	char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(filename);
	if (buffer == nullptr) {
		_gameRef->LOG(0, "AdGame::LoadItemsFile failed for file '%s'", filename);
		return STATUS_FAILED;
	}

	bool ret;

	//_filename = new char [strlen(filename)+1];
	//strcpy(_filename, filename);

	if (DID_FAIL(ret = loadItemsBuffer(buffer, merge))) {
		_gameRef->LOG(0, "Error parsing ITEMS file '%s'", filename);
	}


	delete[] buffer;

	return ret;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::loadItemsBuffer(char *buffer, bool merge) {
	TOKEN_TABLE_START(commands)
	TOKEN_TABLE(ITEM)
	TOKEN_TABLE_END

	char *params;
	int cmd;
	BaseParser parser;

	if (!merge) {
		while (_items.size() > 0) {
			deleteItem(_items[0]);
		}
	}

	while ((cmd = parser.getCommand(&buffer, commands, &params)) > 0) {
		switch (cmd) {
		case TOKEN_ITEM: {
			AdItem *item = new AdItem(_gameRef);
			if (item && !DID_FAIL(item->loadBuffer(params, false))) {
				// delete item with the same name, if exists
				if (merge) {
					AdItem *prevItem = getItemByName(item->getName());
					if (prevItem) {
						deleteItem(prevItem);
					}
				}
				addItem(item);
			} else {
				delete item;
				item = nullptr;
				cmd = PARSERR_GENERIC;
			}
		}
		break;
		}
	}

	if (cmd == PARSERR_TOKENNOTFOUND) {
		_gameRef->LOG(0, "Syntax error in ITEMS definition");
		return STATUS_FAILED;
	}
	if (cmd == PARSERR_GENERIC) {
		_gameRef->LOG(0, "Error loading ITEMS definition");
		return STATUS_FAILED;
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
AdSceneState *AdGame::getSceneState(const char *filename, bool saving) {
	char *filenameCor = new char[strlen(filename) + 1];
	strcpy(filenameCor, filename);
	for (uint32 i = 0; i < strlen(filenameCor); i++) {
		if (filenameCor[i] == '/') {
			filenameCor[i] = '\\';
		}
	}

	for (uint32 i = 0; i < _sceneStates.size(); i++) {
		if (scumm_stricmp(_sceneStates[i]->getFilename(), filenameCor) == 0) {
			delete[] filenameCor;
			return _sceneStates[i];
		}
	}

	if (saving) {
		AdSceneState *ret = new AdSceneState(_gameRef);
		ret->setFilename(filenameCor);

		_sceneStates.add(ret);

		delete[] filenameCor;
		return ret;
	} else {
		delete[] filenameCor;
		return nullptr;
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::windowLoadHook(UIWindow *win, char **buffer, char **params) {
	TOKEN_TABLE_START(commands)
	TOKEN_TABLE(ENTITY_CONTAINER)
	TOKEN_TABLE_END

	int cmd = PARSERR_GENERIC;
	BaseParser parser;

	cmd = parser.getCommand(buffer, commands, params);
	switch (cmd) {
	case TOKEN_ENTITY_CONTAINER: {
		UIEntity *ent = new UIEntity(_gameRef);
		if (!ent || DID_FAIL(ent->loadBuffer(*params, false))) {
			delete ent;
			ent = nullptr;
			cmd = PARSERR_GENERIC;
		} else {
			ent->_parent = win;
			win->_widgets.add(ent);
		}
	}
	break;
	}

	if (cmd == PARSERR_TOKENNOTFOUND || cmd == PARSERR_GENERIC) {
		return STATUS_FAILED;
	}

	return STATUS_OK;

}


//////////////////////////////////////////////////////////////////////////
bool AdGame::windowScriptMethodHook(UIWindow *win, ScScript *script, ScStack *stack, const char *name) {
	if (strcmp(name, "CreateEntityContainer") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();

		UIEntity *ent = new UIEntity(_gameRef);
		if (!val->isNULL()) {
			ent->setName(val->getString());
		}
		stack->pushNative(ent, true);

		ent->_parent = win;
		win->_widgets.add(ent);

		return STATUS_OK;
	} else {
		return STATUS_FAILED;
	}
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::startDlgBranch(const char *branchName, const char *scriptName, const char *eventName) {
	char *name = new char[strlen(branchName) + 1 + strlen(scriptName) + 1 + strlen(eventName) + 1];
	if (name) {
		sprintf(name, "%s.%s.%s", branchName, scriptName, eventName);
		_dlgPendingBranches.add(name);
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::endDlgBranch(const char *branchName, const char *scriptName, const char *eventName) {
	char *name = nullptr;
	bool deleteName = false;
	if (branchName == nullptr && _dlgPendingBranches.size() > 0) {
		name = _dlgPendingBranches[_dlgPendingBranches.size() - 1];
	} else {
		if (branchName != nullptr) {
			name = new char[strlen(branchName) + 1 + strlen(scriptName) + 1 + strlen(eventName) + 1];
			if (name) {
				sprintf(name, "%s.%s.%s", branchName, scriptName, eventName);
				deleteName = true;
			}
		}
	}

	if (name == nullptr) {
		return STATUS_OK;
	}


	int startIndex = -1;
	for (int i = _dlgPendingBranches.size() - 1; i >= 0; i--) {
		if (scumm_stricmp(name, _dlgPendingBranches[i]) == 0) {
			startIndex = i;
			break;
		}
	}
	if (startIndex >= 0) {
		for (uint32 i = startIndex; i < _dlgPendingBranches.size(); i++) {
			//ClearBranchResponses(_dlgPendingBranches[i]);
			delete[] _dlgPendingBranches[i];
			_dlgPendingBranches[i] = nullptr;
		}
		_dlgPendingBranches.remove_at(startIndex, _dlgPendingBranches.size() - startIndex);
	}

	// dialogue is over, forget selected responses
	if (_dlgPendingBranches.size() == 0) {
		for (uint32 i = 0; i < _responsesBranch.size(); i++) {
			delete _responsesBranch[i];
		}
		_responsesBranch.clear();
	}

	if (deleteName) {
		delete[] name;
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::clearBranchResponses(char *name) {
	for (uint32 i = 0; i < _responsesBranch.size(); i++) {
		if (scumm_stricmp(name, _responsesBranch[i]->getContext()) == 0) {
			delete _responsesBranch[i];
			_responsesBranch.remove_at(i);
			i--;
		}
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::addBranchResponse(int id) {
	if (branchResponseUsed(id)) {
		return STATUS_OK;
	}
	AdResponseContext *r = new AdResponseContext(_gameRef);
	r->_id = id;
	r->setContext(_dlgPendingBranches.size() > 0 ? _dlgPendingBranches[_dlgPendingBranches.size() - 1] : nullptr);
	_responsesBranch.add(r);
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::branchResponseUsed(int id) const {
	char *context = _dlgPendingBranches.size() > 0 ? _dlgPendingBranches[_dlgPendingBranches.size() - 1] : nullptr;
	for (uint32 i = 0; i < _responsesBranch.size(); i++) {
		if (_responsesBranch[i]->_id == id) {
			if ((context == nullptr && _responsesBranch[i]->getContext() == nullptr) || scumm_stricmp(context, _responsesBranch[i]->getContext()) == 0) {
				return true;
			}
		}
	}
	return false;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::addGameResponse(int id) {
	if (gameResponseUsed(id)) {
		return STATUS_OK;
	}
	AdResponseContext *r = new AdResponseContext(_gameRef);
	r->_id = id;
	r->setContext(_dlgPendingBranches.size() > 0 ? _dlgPendingBranches[_dlgPendingBranches.size() - 1] : nullptr);
	_responsesGame.add(r);
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::gameResponseUsed(int id) const {
	char *context = _dlgPendingBranches.size() > 0 ? _dlgPendingBranches[_dlgPendingBranches.size() - 1] : nullptr;
	for (uint32 i = 0; i < _responsesGame.size(); i++) {
		const AdResponseContext *respContext = _responsesGame[i];
		if (respContext->_id == id) {
			if ((context == nullptr && respContext->getContext() == nullptr) || ((context != nullptr && respContext->getContext() != nullptr) && scumm_stricmp(context, respContext->getContext()) == 0)) {
				return true;
			}
		}
	}
	return false;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::resetResponse(int id) {
	char *context = _dlgPendingBranches.size() > 0 ? _dlgPendingBranches[_dlgPendingBranches.size() - 1] : nullptr;

	for (uint32 i = 0; i < _responsesGame.size(); i++) {
		if (_responsesGame[i]->_id == id) {
			if ((context == nullptr && _responsesGame[i]->getContext() == nullptr) || scumm_stricmp(context, _responsesGame[i]->getContext()) == 0) {
				delete _responsesGame[i];
				_responsesGame.remove_at(i);
				break;
			}
		}
	}

	for (uint32 i = 0; i < _responsesBranch.size(); i++) {
		if (_responsesBranch[i]->_id == id) {
			if ((context == nullptr && _responsesBranch[i]->getContext() == nullptr) || scumm_stricmp(context, _responsesBranch[i]->getContext()) == 0) {
				delete _responsesBranch[i];
				_responsesBranch.remove_at(i);
				break;
			}
		}
	}
	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::displayContent(bool doUpdate, bool displayAll) {
	// init
	if (doUpdate) {
		initLoop();
	}

	// fill black
	_renderer->fill(0, 0, 0);
	if (!_editorMode) {
		_renderer->setScreenViewport();
	}

	// playing exclusive video?
	if (_videoPlayer->isPlaying()) {
		if (doUpdate) {
			_videoPlayer->update();
		}
		_videoPlayer->display();
	} else if (_theoraPlayer) {
		if (_theoraPlayer->isPlaying()) {
			if (doUpdate) {
				_theoraPlayer->update();
			}
			_theoraPlayer->display();
		}
		if (_theoraPlayer->isFinished()) {
			delete _theoraPlayer;
			_theoraPlayer = nullptr;
		}
	} else {

		// process scripts
		if (doUpdate) {
			_scEngine->tick();
		}

		Point32 p;
		getMousePos(&p);

		_scene->update();
		_scene->display();


		// display in-game windows
		displayWindows(true);
		if (_inventoryBox) {
			_inventoryBox->display();
		}
		if (_stateEx == GAME_WAITING_RESPONSE) {
			_responseBox->display();
		}
		_renderer->displayIndicator();


		if (doUpdate || displayAll) {
			// display normal windows
			displayWindows(false);

			setActiveObject(_gameRef->_renderer->getObjectAt(p.x, p.y));

			// textual info
			displaySentences(_state == GAME_FROZEN);

			showCursor();

			if (_fader) {
				_fader->display();
			}
			_transMgr->update();
		}

	}
	if (_loadingIcon) {
		_loadingIcon->display(_loadingIconX, _loadingIconY);
		if (!_loadingIconPersistent) {
			delete _loadingIcon;
			_loadingIcon = nullptr;
		}
	}

	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::registerInventory(AdInventory *inv) {
	for (uint32 i = 0; i < _inventories.size(); i++) {
		if (_inventories[i] == inv) {
			return STATUS_OK;
		}
	}
	registerObject(inv);
	_inventories.add(inv);

	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::unregisterInventory(AdInventory *inv) {
	for (uint32 i = 0; i < _inventories.size(); i++) {
		if (_inventories[i] == inv) {
			unregisterObject(_inventories[i]);
			_inventories.remove_at(i);
			return STATUS_OK;
		}
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::isItemTaken(char *itemName) {
	for (uint32 i = 0; i < _inventories.size(); i++) {
		AdInventory *inv = _inventories[i];

		for (uint32 j = 0; j < inv->_takenItems.size(); j++) {
			if (scumm_stricmp(itemName, inv->_takenItems[j]->getName()) == 0) {
				return true;
			}
		}
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
AdItem *AdGame::getItemByName(const char *name) const {
	for (uint32 i = 0; i < _items.size(); i++) {
		if (scumm_stricmp(_items[i]->getName(), name) == 0) {
			return _items[i];
		}
	}
	return nullptr;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::addItem(AdItem *item) {
	_items.add(item);
	return _gameRef->registerObject(item);
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::resetContent() {
	// clear pending dialogs
	for (uint32 i = 0; i < _dlgPendingBranches.size(); i++) {
		delete[] _dlgPendingBranches[i];
	}
	_dlgPendingBranches.clear();


	// clear inventories
	for (uint32 i = 0; i < _inventories.size(); i++) {
		_inventories[i]->_takenItems.clear();
	}

	// clear scene states
	for (uint32 i = 0; i < _sceneStates.size(); i++) {
		delete _sceneStates[i];
	}
	_sceneStates.clear();

	// clear once responses
	for (uint32 i = 0; i < _responsesBranch.size(); i++) {
		delete _responsesBranch[i];
	}
	_responsesBranch.clear();

	// clear once game responses
	for (uint32 i = 0; i < _responsesGame.size(); i++) {
		delete _responsesGame[i];
	}
	_responsesGame.clear();

	// reload inventory items
	if (_itemsFile) {
		loadItemsFile(_itemsFile);
	}

	_tempDisableSaveState = true;

	return BaseGame::resetContent();
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::deleteItem(AdItem *item) {
	if (!item) {
		return STATUS_FAILED;
	}

	if (_selectedItem == item) {
		_selectedItem = nullptr;
	}
	_scene->handleItemAssociations(item->getName(), false);

	// remove from all inventories
	for (uint32 i = 0; i < _inventories.size(); i++) {
		_inventories[i]->removeItem(item);
	}

	// remove object
	for (uint32 i = 0; i < _items.size(); i++) {
		if (_items[i] == item) {
			unregisterObject(_items[i]);
			_items.remove_at(i);
			break;
		}
	}

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::addSpeechDir(const char *dir) {
	if (!dir || dir[0] == '\0') {
		return STATUS_FAILED;
	}

	char *temp = new char[strlen(dir) + 2];
	strcpy(temp, dir);
	if (temp[strlen(temp) - 1] != '\\' && temp[strlen(temp) - 1] != '/') {
		strcat(temp, "\\");
	}

	for (uint32 i = 0; i < _speechDirs.size(); i++) {
		if (scumm_stricmp(_speechDirs[i], temp) == 0) {
			delete[] temp;
			return STATUS_OK;
		}
	}
	_speechDirs.add(temp);

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::removeSpeechDir(const char *dir) {
	if (!dir || dir[0] == '\0') {
		return STATUS_FAILED;
	}

	char *temp = new char[strlen(dir) + 2];
	strcpy(temp, dir);
	if (temp[strlen(temp) - 1] != '\\' && temp[strlen(temp) - 1] != '/') {
		strcat(temp, "\\");
	}

	bool found = false;
	for (uint32 i = 0; i < _speechDirs.size(); i++) {
		if (scumm_stricmp(_speechDirs[i], temp) == 0) {
			delete[] _speechDirs[i];
			_speechDirs.remove_at(i);
			found = true;
			break;
		}
	}
	delete[] temp;

	return found;
}


//////////////////////////////////////////////////////////////////////////
char *AdGame::findSpeechFile(char *stringID) {
	char *ret = new char[MAX_PATH_LENGTH];

	for (uint32 i = 0; i < _speechDirs.size(); i++) {
		sprintf(ret, "%s%s.ogg", _speechDirs[i], stringID);
		if (BaseFileManager::getEngineInstance()->hasFile(ret)) {
			return ret;
		}

		sprintf(ret, "%s%s.wav", _speechDirs[i], stringID);
		if (BaseFileManager::getEngineInstance()->hasFile(ret)) {
			return ret;
		}
	}
	delete[] ret;
	return nullptr;
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::validMouse() {
	Point32 pos;
	BasePlatform::getCursorPos(&pos);

	return _renderer->pointInViewport(&pos);
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::onMouseLeftDown() {
	if (!validMouse()) {
		return STATUS_OK;
	}
	if (_state == GAME_RUNNING && !_interactive) {
		if (_talkSkipButton == TALK_SKIP_LEFT || _talkSkipButton == TALK_SKIP_BOTH) {
			finishSentences();
		}
		return STATUS_OK;
	}

	if (_activeObject) {
		_activeObject->handleMouse(MOUSE_CLICK, MOUSE_BUTTON_LEFT);
	}

	bool handled = _state == GAME_RUNNING && DID_SUCCEED(applyEvent("LeftClick"));
	if (!handled) {
		if (_activeObject != nullptr) {
			_activeObject->applyEvent("LeftClick");
		} else if (_state == GAME_RUNNING && _scene && _scene->pointInViewport(_mousePos.x, _mousePos.y)) {
			_scene->applyEvent("LeftClick");
		}
	}

	if (_activeObject != nullptr) {
		_gameRef->_capturedObject = _gameRef->_activeObject;
	}
	_mouseLeftDown = true;

	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::onMouseLeftUp() {
	if (_activeObject) {
		_activeObject->handleMouse(MOUSE_RELEASE, MOUSE_BUTTON_LEFT);
	}

	_capturedObject = nullptr;
	_mouseLeftDown = false;

	bool handled;
	if (BaseEngine::instance().getTargetExecutable() < WME_LITE) {
		handled = _state==GAME_RUNNING && DID_SUCCEED(applyEvent("LeftRelease"));
	} else {
		handled = DID_SUCCEED(applyEvent("LeftRelease"));
	}
	
	if (!handled) {
		if (_activeObject != nullptr) {
			_activeObject->applyEvent("LeftRelease");
		} else if (_state == GAME_RUNNING && _scene && _scene->pointInViewport(_mousePos.x, _mousePos.y)) {
			_scene->applyEvent("LeftRelease");
		}
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::onMouseLeftDblClick() {
	if (!validMouse()) {
		return STATUS_OK;
	}

	if (_state == GAME_RUNNING && !_interactive) {
		return STATUS_OK;
	}

	if (_activeObject) {
		_activeObject->handleMouse(MOUSE_DBLCLICK, MOUSE_BUTTON_LEFT);
	}

	bool handled = _state == GAME_RUNNING && DID_SUCCEED(applyEvent("LeftDoubleClick"));
	if (!handled) {
		if (_activeObject != nullptr) {
			_activeObject->applyEvent("LeftDoubleClick");
		} else if (_state == GAME_RUNNING && _scene && _scene->pointInViewport(_mousePos.x, _mousePos.y)) {
			_scene->applyEvent("LeftDoubleClick");
		}
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::onMouseRightDown() {
	if (!validMouse()) {
		return STATUS_OK;
	}
	if (_state == GAME_RUNNING && !_interactive) {
		if (_talkSkipButton == TALK_SKIP_RIGHT || _talkSkipButton == TALK_SKIP_BOTH) {
			finishSentences();
		}
		return STATUS_OK;
	}

	if ((_state == GAME_RUNNING && !_interactive) || _stateEx == GAME_WAITING_RESPONSE) {
		return STATUS_OK;
	}

	if (_activeObject) {
		_activeObject->handleMouse(MOUSE_CLICK, MOUSE_BUTTON_RIGHT);
	}

	bool handled = _state == GAME_RUNNING && DID_SUCCEED(applyEvent("RightClick"));
	if (!handled) {
		if (_activeObject != nullptr) {
			_activeObject->applyEvent("RightClick");
		} else if (_state == GAME_RUNNING && _scene && _scene->pointInViewport(_mousePos.x, _mousePos.y)) {
			_scene->applyEvent("RightClick");
		}
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::onMouseRightUp() {
	if (_activeObject) {
		_activeObject->handleMouse(MOUSE_RELEASE, MOUSE_BUTTON_RIGHT);
	}

	bool handled = _state == GAME_RUNNING && DID_SUCCEED(applyEvent("RightRelease"));
	if (!handled) {
		if (_activeObject != nullptr) {
			_activeObject->applyEvent("RightRelease");
		} else if (_state == GAME_RUNNING && _scene && _scene->pointInViewport(_mousePos.x, _mousePos.y)) {
			_scene->applyEvent("RightRelease");
		}
	}
	return STATUS_OK;
}

//////////////////////////////////////////////////////////////////////////
bool AdGame::displayDebugInfo() {
	char str[100];
	if (_gameRef->_debugDebugMode) {
		sprintf(str, "Mouse: %d, %d (scene: %d, %d)", _mousePos.x, _mousePos.y, _mousePos.x + (_scene ? _scene->getOffsetLeft() : 0), _mousePos.y + (_scene ? _scene->getOffsetTop() : 0));
		_systemFont->drawText((byte *)str, 0, 90, _renderer->getWidth(), TAL_RIGHT);

		sprintf(str, "Scene: %s (prev: %s)", (_scene && _scene->getName()) ? _scene->getName() : "???", _prevSceneName ? _prevSceneName : "???");
		_systemFont->drawText((byte *)str, 0, 110, _renderer->getWidth(), TAL_RIGHT);
	}
	return BaseGame::displayDebugInfo();
}


//////////////////////////////////////////////////////////////////////////
bool AdGame::onScriptShutdown(ScScript *script) {
	if (_responseBox && _responseBox->_waitingScript == script) {
		_responseBox->_waitingScript = nullptr;
	}

	return STATUS_OK;
}

Common::String AdGame::debuggerToString() const {
	return Common::String::format("%p: Game \"%s\"", (const void *)this, getName());
}
} // End of namespace Wintermute
