/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/str-array.h"

#include "tsage/tsage.h"
#include "tsage/globals.h"
#include "tsage/staticres.h"
#include "ringworld2/ringworld2_speakers.h"

namespace TsAGE {

#define STRIP_WORD_DELAY 30


SequenceManager::SequenceManager() : Action() {
	Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
	_sequenceData.clear();
	_fontNum = 0;
	_sequenceOffset = 0;
	_resNum = 0;
	_field26 = 0;
	_objectIndex = 0;
	_keepActive = false;
	_onCallback = NULL;
	for (int i = 0; i < 6; i ++)
		_objectList[i] = NULL;
	setup();
}

void SequenceManager::setup() {
	_sequenceOffset = 0;
	_objectIndex = 0;
	_sceneObject = _objectList[0];
}

void SequenceManager::synchronize(Serializer &s) {
	if (s.getVersion() >= 2)
		Action::synchronize(s);

	s.syncAsSint32LE(_resNum);
	s.syncAsSint32LE(_sequenceOffset);
	s.syncAsByte(_keepActive);
	s.syncAsSint32LE(_fontNum);
	s.syncAsSint32LE(_field26);

	s.syncAsSint32LE(_objectIndex);
	SYNC_POINTER(_sceneObject);
	for (int i = 0; i < 6; ++i)
		SYNC_POINTER(_objectList[i]);

	int seqSize = _sequenceData.size();
	s.syncAsUint32LE(seqSize);
	if (s.isLoading())
		_sequenceData.resize(seqSize);
	if (seqSize > 0)
		s.syncBytes(&_sequenceData[0], seqSize);
}

void SequenceManager::remove() {
	if ((!_sequenceData.empty()) && !_keepActive) {
		_sequenceData.clear();
	}

	if (g_globals->_sceneObjects->contains(&_sceneText))
		_sceneText.remove();

	Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
	Action::remove();
}

void SequenceManager::signal() {
	if (g_globals->_sceneObjects->contains(&_sceneText))
		_sceneText.hide();

	bool continueFlag = true;
	while (continueFlag) {
		if (_sequenceOffset >=_sequenceData.size()) {
			// Reached the end of the sequence
			if (!_keepActive)
				remove();
			break;
		}

		uint16 idx = static_cast<uint16>(getNextValue() - 32000);

		int16 v1, v2, v3;
		switch (idx) {
		case 0:
			// Stop sequence
			continueFlag = false;
			break;
		case 1:
			_sceneObject->animate(ANIM_MODE_1, NULL);
			break;
		case 2:
			_sceneObject->animate(ANIM_MODE_2, NULL);
			break;
		case 3:
			_sceneObject->animate(ANIM_MODE_3);
			break;
		case 4:
			v1 = getNextValue();
			v2 = getNextValue();
			_sceneObject->animate(ANIM_MODE_8, v1, v2 ? this : NULL);
			break;
		case 5:
			v1 = getNextValue();
			v2 = getNextValue();
			_sceneObject->animate(ANIM_MODE_7, v1, v2 ? this : NULL);
			break;
		case 6:
			v2 = getNextValue();
			_sceneObject->animate(ANIM_MODE_5, v2 ? this : NULL);
			break;
		case 7:
			v2 = getNextValue();
			_sceneObject->animate(ANIM_MODE_6, v2 ? this : NULL);
			break;
		case 8:
			v1 = getNextValue();
			v3 = getNextValue();
			v2 = getNextValue();
			_sceneObject->animate(ANIM_MODE_4, v1, v3, v2 ? this : NULL);
			break;
		case 9:
			v1 = getNextValue();
			v3 = getNextValue();
			v2 = getNextValue();
			g_globals->_sceneManager._scene->_sceneBounds.moveTo(v3, v2);
			g_globals->_sceneManager._scene->loadScene(v1);
			break;
		case 10: {
			int resNum= getNextValue();
			int lineNum = getNextValue();
			int color = getNextValue();
			int xp = getNextValue();
			int yp = getNextValue();
			int width = getNextValue();
			setMessage(resNum, lineNum, color, Common::Point(xp, yp), width);
			break;
		}
		case 11:
			v1 = getNextValue();
			v2 = getNextValue();
			setAction(globalManager(), v2 ? this : NULL, v1, _objectList[0], _objectList[1], _objectList[2], _objectList[3], NULL);
			break;
		case 12:
			v1 = getNextValue();
			setDelay(v1);
			break;
		case 13: {
			v1 = getNextValue();
			v3 = getNextValue();
			v2 = getNextValue();
			NpcMover *mover = new NpcMover();
			Common::Point destPos(v1, v3);
			_sceneObject->addMover(mover, &destPos, v2 ? this : NULL);
			break;
		}
		case 14:
			v1 = getNextValue();
			_sceneObject->_numFrames = v1;
			break;
		case 15:
			v1 = getNextValue();
			_sceneObject->_moveRate = v1;
			break;
		case 16:
			v1 = getNextValue();
			v2 = getNextValue();
			_sceneObject->_moveDiff = Common::Point(v1, v2);
			break;
		case 17:
			_sceneObject->hide();
			break;
		case 18:
			_sceneObject->show();
			break;
		case 19:
			v1 = getNextValue();
			_sceneObject->setVisage(v1);
			break;
		case 20:
			v1 = getNextValue();
			_sceneObject->setStrip(v1);
			break;
		case 21:
			v1 = getNextValue();
			_sceneObject->setFrame(v1);
			break;
		case 22:
			v1 = getNextValue();
			_sceneObject->fixPriority(v1);
			break;
		case 23:
			v1 = getNextValue();
			_sceneObject->changeZoom(v1);
			break;
		case 24:
			v1 = getNextValue();
			v2 = getNextValue();
			v3 = getNextValue();
			_sceneObject->setPosition(Common::Point(v1, v2), v3);
			break;
		case 25: {
			int yStart = getNextValue();
			int minPercent = getNextValue();
			int yEnd = getNextValue();
			int maxPercent = getNextValue();
			g_globals->_sceneManager._scene->setZoomPercents(yStart, minPercent, yEnd, maxPercent);
			break;
		}
		case 26:
			v1 = getNextValue();
			v2 = getNextValue();
			_soundHandler.play(v1, v2 ? this : NULL, 127);
			break;
		case 27: {
			v1 = getNextValue();
			v3 = getNextValue();
			v2 = getNextValue();
			PlayerMover *mover = new PlayerMover();
			Common::Point destPos(v1, v3);
			_sceneObject->addMover(mover, &destPos, v2 ? this : NULL);
			break;
		}
		case 28:
			_objectIndex = getNextValue();
			assert((_objectIndex >= 0) && (_objectIndex < 6));
			_sceneObject = _objectList[_objectIndex];
			assert(_sceneObject);
			break;
		case 29:
			_sceneObject->animate(ANIM_MODE_NONE);
			break;
		case 30:
			v1 = getNextValue();
			g_globals->_scrollFollower = (v1 == -1) ? NULL : _objectList[v1];
			break;
		case 31:
			_sceneObject->setObjectWrapper(new SceneObjectWrapper());
			break;
		case 32:
			_sceneObject->setObjectWrapper(NULL);
			break;
		case 33:
			v1 = getNextValue();
			if (_keepActive)
				setDelay(1);
			else {
				_sceneText.remove();
				g_globals->_sceneManager._scene->_stripManager.start(v1, this);
			}
			break;
		case 34: {
			v1 = getNextValue();
			v2 = getNextValue();
			int objIndex1 = getNextValue() - 1;
			int objIndex2 = getNextValue() - 1;
			int objIndex3 = getNextValue() - 1;
			int objIndex4 = getNextValue() - 1;
			int objIndex5 = getNextValue() - 1;
			int objIndex6 = getNextValue() - 1;

			setAction(globalManager(), v2 ? this : NULL, v1, _objectList[objIndex1], _objectList[objIndex2],
				_objectList[objIndex3], _objectList[objIndex4], _objectList[objIndex5], _objectList[objIndex6], NULL);
			break;
		}
		/* Following indexes were introduced for Blue Force */
		case 35:
			v1 = getNextValue();
			_sceneObject->updateAngle(_objectList[v1]->_position);
			break;
		case 36:
			_sceneObject->animate(ANIM_MODE_9, NULL);
			break;
		case 37:
			v1 = getNextValue();
			v2 = getNextValue();
			if (_onCallback)
				_onCallback(v1, v2);
			break;
		case 38: {
			int resNum = getNextValue();
			int lineNum = getNextValue();
			int fontNum = getNextValue();
			int color1 = getNextValue();
			int color2 = getNextValue();
			int color3 = getNextValue();
			int xp = getNextValue();
			int yp = getNextValue();
			int width = getNextValue();
			setMessage(resNum, lineNum, fontNum, color1, color2, color3, Common::Point(xp, yp), width);
			break;
		}
		default:
			error("SequenceManager::signal - Unknown action %d at offset %xh", idx, _sequenceOffset - 2);
			break;
		}
	}
}

void SequenceManager::process(Event &event) {
	if (((event.eventType == EVENT_BUTTON_DOWN) || (event.eventType == EVENT_KEYPRESS)) &&
		!event.handled && g_globals->_sceneObjects->contains(&_sceneText)) {
		// Remove the text item
		_sceneText.remove();
		setDelay(2);
		event.handled = true;
	} else {
		Action::process(event);
	}
}


void SequenceManager::attached(EventHandler *newOwner, EventHandler *endHandler, va_list va) {
	// Get the sequence number to use
	_resNum = va_arg(va, int);

	byte *seqData = g_resourceManager->getResource(RES_SEQUENCE, _resNum, 0);
	uint seqSize = g_vm->_memoryManager.getSize(seqData);

	_sequenceData.resize(seqSize);
	Common::copy(seqData, seqData + seqSize, &_sequenceData[0]);

	DEALLOCATE(seqData);

	Common::fill(&_objectList[0], &_objectList[6], (SceneObject *)NULL);
	for (int idx = 0; idx < 6; ++idx) {
		_objectList[idx] = va_arg(va, SceneObject *);
		if (!_objectList[idx])
			break;
	}

	setup();
	Action::attached(newOwner, endHandler, va);
}

/**
 * Returns the next Id in the sequence
 */
uint16 SequenceManager::getNextValue() {
	uint16 result = READ_LE_UINT16(&_sequenceData[0] + _sequenceOffset);
	_sequenceOffset += 2;
	return result;
}

void SequenceManager::setMessage(int resNum, int lineNum, int color, const Common::Point &pt, int width) {
	setMessage(resNum, lineNum, 2, color, 0, 0, pt, width);
}

void SequenceManager::setMessage(int resNum, int lineNum, int fontNum, int color1, int color2, int color3,
								 const Common::Point &pt, int width) {
	_sceneText._color1 = color1;
	_sceneText._color2 = color2;
	_sceneText._color3 = color3;
	_sceneText._fontNumber = fontNum;
	_sceneText._width = width;

	// Get the display message
	Common::String msg = g_resourceManager->getMessage(resNum, lineNum);

	// Set the text message
	_sceneText.setup(msg);

	// Move the text to the correct position
	Rect textRect = _sceneText._bounds;
	Rect sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
	sceneBounds.collapse(4, 2);
	textRect.moveTo(pt);
	textRect.contain(sceneBounds);

	_sceneText.setPosition(Common::Point(textRect.left, textRect.top));

	// Draw the text
	_sceneText.fixPriority(255);
	_sceneText.show();

	// Set the delay based on the number of words
	int numWords = 0;
	const char *msgP = msg.c_str();
	while (*msgP) {
		if (*msgP++ == ' ')
			++numWords;
	}

	setDelay(numWords * 18 + 120);
}

SequenceManager *SequenceManager::globalManager() {
	return &g_globals->_sequenceManager;
}

/*--------------------------------------------------------------------------*/

ConversationChoiceDialog::ConversationChoiceDialog() {
	_stdColor = 23;
	_highlightColor = g_globals->_scenePalette._colors.background;
	_fontNumber = (g_vm->getGameID() == GType_Ringworld2) ? 3 : 1;
	_savedFgColor = _savedFontNumber = 0;
	_selectedIndex = 0;
}

int ConversationChoiceDialog::execute(const Common::StringArray &choiceList) {
	_gfxManager._font.setFontNumber(_fontNumber);

	_bounds = Rect(40, 0, 40, 0);
	_choiceList.clear();

	// Set up the list of choices
	int yp = 0;
	for (uint idx = 0; idx < choiceList.size(); ++idx) {
		Rect tempRect;
		_gfxManager._font.getStringBounds(choiceList[idx].c_str(), tempRect, textMaxWidth());
		tempRect.moveTo(textLeft(), yp + 10);

		_choiceList.push_back(ChoiceEntry(choiceList[idx], tempRect));
		yp += tempRect.height() + 5;
		_bounds.extend(tempRect);
	}
	_selectedIndex = _choiceList.size();

	// Set the position for the dialog
	_bounds.bottom -= 10;
	yp = 180 - _bounds.height();
	_bounds.translate(0, yp);
	_bounds.setWidth(textMaxWidth() + 15);
	_bounds.moveTo(160 - (_bounds.width() / 2), _bounds.top);

	// Draw the dialog
	draw();

	g_globals->_events.showCursor();

	// Force the display of an arrow cursor during discussions in R2R
	if (g_vm->getGameID() == GType_Ringworld2)
		R2_GLOBALS._events.setCursor(CURSOR_ARROW);

	// WORKAROUND: On-screen dialogs are really meant to use a GfxManager instance
	// for their lifetime, which prevents saving or loading. Since I don't want to spend a lot
	// of time refactoring this already working dialog, fake it by putting a dummy gfxmanager at
	// the end of the gfx manager list so as to prevent saving or loading
	GfxManager gfxManager;
	GLOBALS._gfxManagers.push_back(&gfxManager);

	// Event handling loop
	Event event;
	while (!g_vm->shouldQuit()) {
		while (!g_globals->_events.getEvent(event, EVENT_KEYPRESS | EVENT_BUTTON_DOWN | EVENT_MOUSE_MOVE) &&
				!g_vm->shouldQuit()) {
			g_system->delayMillis(10);
			GLOBALS._screenSurface.updateScreen();
		}
		if (g_vm->shouldQuit())
			break;

		if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode >= Common::KEYCODE_1) &&
			(event.kbd.keycode <= (Common::KEYCODE_0 + (int)_choiceList.size()))) {
			// Selected an option by number
			_selectedIndex = event.kbd.keycode - Common::KEYCODE_1;
			break;
		} else if ((_selectedIndex != _choiceList.size()) && ((event.eventType == EVENT_BUTTON_DOWN) ||
					(event.eventType == EVENT_BUTTON_UP))) {
			// Item selected
			break;
		} else {
			// Check if any item is highlighted
			event.mousePos.x -= _gfxManager._bounds.left;
			event.mousePos.y -= _gfxManager._bounds.top;

			uint idx = 0;
			while ((idx < _choiceList.size()) && !_choiceList[idx]._bounds.contains(event.mousePos.x, event.mousePos.y))
				++idx;

			if (idx != _selectedIndex) {
				if (_selectedIndex != _choiceList.size()) {
					// De-highlight previously selected item
					_gfxManager._font._colors.foreground = _stdColor;
					_gfxManager._font.writeLines(_choiceList[_selectedIndex]._msg.c_str(),
						_choiceList[_selectedIndex]._bounds, ALIGN_LEFT);
				}

				_selectedIndex = idx;

				if (_selectedIndex != _choiceList.size()) {
					// Highlight the new item
					_gfxManager._font._colors.foreground = _highlightColor;
					_gfxManager._font.writeLines(_choiceList[idx]._msg.c_str(), _choiceList[idx]._bounds, ALIGN_LEFT);
				}

			}
		}
	}

	// Remove the dialog
	remove();
	GLOBALS._gfxManagers.remove(&gfxManager);

	return _selectedIndex;
}

void ConversationChoiceDialog::draw() {
	// Make a backup copy of the area the dialog will occupy
	Rect tempRect = _bounds;
	tempRect.collapse(-10, -10);
	_savedArea = surfaceGetArea(g_globals->_gfxManagerInstance.getSurface(), tempRect);

	// Fill in the contents of the entire dialog
	_gfxManager._bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);

	drawFrame();

	_gfxManager._bounds = tempRect;
	_gfxManager._font._colors.foreground = _stdColor;
	_gfxManager.activate();

	// Loop through writing the conversation choices
	for (uint idx = 0; idx < _choiceList.size(); ++idx) {
		Common::String strNum = Common::String::format("%d", idx + 1);

		// Write the choice number
		_gfxManager._font.setPosition(numberLeft(), _choiceList[idx]._bounds.top);
		_gfxManager._font.writeString(strNum.c_str());

		_gfxManager._font.writeLines(_choiceList[idx]._msg.c_str(), _choiceList[idx]._bounds, ALIGN_LEFT);
	}

	_gfxManager.deactivate();
}

int ConversationChoiceDialog::textLeft() const {
	return (g_vm->getGameID() == GType_Ringworld2) ? 20 : 25;
}

int ConversationChoiceDialog::textMaxWidth() const {
	return (g_vm->getGameID() == GType_Ringworld2) ? 250 : 265;
}

int ConversationChoiceDialog::numberLeft() const {
	return (g_vm->getGameID() == GType_Ringworld2) ? 8 : 13;
}

/*--------------------------------------------------------------------------*/

void Obj44::load(const byte *dataP) {
	Common::MemoryReadStream s(dataP, (g_vm->getGameID() == GType_Ringworld2) ? 126 : 68);

	if (g_vm->getGameID() == GType_Ringworld2) {
		_mode = s.readSint16LE();
		_lookupValue = s.readSint16LE();
		_lookupIndex = s.readSint16LE();
		_exitMode = s.readSint16LE();
		_speakerMode = s.readSint16LE();
	}

	_id = s.readSint16LE();
	for (int idx = 0; idx < OBJ44_LIST_SIZE; ++idx)
		_callbackId[idx] = s.readSint16LE();

	if (g_vm->getGameID() == GType_Ringworld2) {
		for (int i = 0; i < 11; ++i)
			_field16[i] = s.readSint16LE();
	} else {
		s.skip(4);
	}

	for (int idx = 0; idx < OBJ0A_LIST_SIZE; ++idx) {
		_list[idx]._id = s.readSint16LE();
		_list[idx]._scriptOffset = s.readSint16LE();
		s.skip(6);
	}

	_speakerOffset = s.readSint16LE();
}

void Obj44::synchronize(Serializer &s) {
	s.syncAsSint32LE(_id);
	for (int idx = 0; idx < OBJ44_LIST_SIZE; ++idx)
		s.syncAsSint32LE(_callbackId[idx]);
	for (int idx = 0; idx < OBJ0A_LIST_SIZE; ++idx)
		_list[idx].synchronize(s);
	s.syncAsUint32LE(_speakerOffset);

	if (g_vm->getGameID() == GType_Ringworld2) {
		s.syncAsSint16LE(_mode);
		s.syncAsSint16LE(_lookupValue);
		s.syncAsSint16LE(_lookupIndex);
		s.syncAsSint16LE(_exitMode);
		s.syncAsSint16LE(_speakerMode);

		for (int i = 0; i < 11; ++i)
			s.syncAsSint16LE(_field16[i]);
	}
}

/*--------------------------------------------------------------------------*/

StripManager::StripManager() {
	_callbackObject = NULL;
	_activeSpeaker = NULL;
	_onBegin = NULL;
	_onEnd = NULL;
	_sceneNumber = 0;
	_lookupList = NULL;
	reset();
}

StripManager::~StripManager() {
}

void StripManager::start(int stripNum, EventHandler *owner, StripCallback *callback) {
	if (_onBegin)
		_onBegin();
	reset();

	_stripNum = stripNum;
	_callbackObject = callback;
	_sceneNumber = g_globals->_sceneManager._scene->_screenNumber;
	_sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
	_script.clear();

	assert(owner);
	owner->setAction(this, owner);
}

void StripManager::start3(int stripNum, EventHandler *owner, byte *lookupList) {
	_lookupList = lookupList;
	start(stripNum, owner, NULL);
}

void StripManager::reset() {
	_actionIndex = 0;
	_delayFrames = 0;
	_owner = NULL;
	_endHandler = NULL;
	_uselessFl = false;
	_stripNum = -1;
	_obj44ListIndex = 0;
	_currObj44Id = 0;
	_useless = 0;
	_activeSpeaker = NULL;
	_textShown = false;
	_callbackObject = NULL;
	_exitMode = 0;

	_obj44List.clear();
	if (!_script.empty()) {
		_script.clear();
	}
}

void StripManager::load() {
	// Get the script
	byte *script = g_resourceManager->getResource(RES_STRIP, _stripNum, 2);
	uint scriptSize = g_vm->_memoryManager.getSize(script);

	_script.resize(scriptSize);
	Common::copy(script, script + scriptSize, &_script[0]);

	DEALLOCATE(script);

	// Get the object list
	byte *obj44List = g_resourceManager->getResource(RES_STRIP, _stripNum, 1);
	int dataSize = g_vm->_memoryManager.getSize(obj44List);

	int obj44Size = (g_vm->getGameID() == GType_Ringworld2) ? 126 : 68;
	assert((dataSize % obj44Size) == 0);

	byte *dataP = obj44List;
	for (int idx = 0; idx < (dataSize / obj44Size); ++idx, dataP += obj44Size) {
		Obj44 obj;
		obj.load(dataP);
		_obj44List.push_back(obj);
	}

	DEALLOCATE(obj44List);
}

void StripManager::synchronize(Serializer &s) {
	if (s.getVersion() >= 2)
		Action::synchronize(s);

	s.syncAsSint32LE(_stripNum);
	s.syncAsSint32LE(_obj44ListIndex);
	s.syncAsSint32LE(_useless);
	s.syncAsSint32LE(_sceneNumber);
	_sceneBounds.synchronize(s);
	SYNC_POINTER(_activeSpeaker);
	s.syncAsByte(_textShown);
	s.syncAsByte(_uselessFl);
	s.syncAsSint32LE(_currObj44Id);
	if (g_vm->getGameID() == GType_Ringworld2)
		s.syncAsSint16LE(_exitMode);

	// Synchronize the item list
	int arrSize = _obj44List.size();
	s.syncAsUint16LE(arrSize);
	if (s.isLoading())
		_obj44List.resize(arrSize);
	for (int i = 0; i < arrSize; ++i)
		_obj44List[i].synchronize(s);

	// Synchronize script data
	int scriptSize = _script.size();
	s.syncAsUint16LE(scriptSize);
	if (s.isLoading())
		_script.resize(scriptSize);
	if (scriptSize > 0)
		s.syncBytes(&_script[0], scriptSize);

	// Add speaker list
	arrSize = _speakerList.size();
	s.syncAsUint16LE(arrSize);
	if (s.isLoading())
		_speakerList.resize(arrSize);
	for (int i = 0; i < arrSize; ++i)
		SYNC_POINTER(_speakerList[i]);

	SYNC_POINTER(_callbackObject);
}

void StripManager::remove() {
	if (g_vm->getGameID() == GType_Ringworld2) {
		for (uint i = 0; i < _speakerList.size(); ++i) {
			if (_activeSpeaker != _speakerList[i])
				_speakerList[i]->stopSpeaking();
		}
	}

	if (_textShown) {
		if (_activeSpeaker)
			_activeSpeaker->removeText();
		_textShown = false;
	}

	if (_activeSpeaker) {
		if (g_vm->getGameID() == GType_Ringworld2)
			static_cast<Ringworld2::VisualSpeaker *>(_activeSpeaker)->_speakerMode = 0xff;
		_activeSpeaker->remove();
	}

	if (_sceneNumber != g_globals->_sceneManager._scene->_screenNumber) {
		g_globals->_sceneManager._scene->_sceneBounds = _sceneBounds;
		g_globals->_sceneManager._scene->loadScene(_sceneNumber);
	}

	if (_onEnd)
		_onEnd();

	if (g_vm->getGameID() == GType_Ringworld2)
		_endHandler = NULL;

	Action::remove();
}

void StripManager::dispatch() {
	if (g_vm->getGameID() == GType_Ringworld2) {
		if (_activeSpeaker)
			_activeSpeaker->dispatch();
	}

	Action::dispatch();
}

void StripManager::signal() {
	int strIndex = 0;

	if (_textShown) {
		_activeSpeaker->removeText();
		_textShown = false;
	}

	if (_obj44ListIndex < 0) {
		EventHandler *owner = _endHandler;
		int stripNum = ABS(_obj44ListIndex);
		remove();

		start(stripNum, owner);
		return;
	} else if (_obj44ListIndex == 10000) {
		// Reached end of strip
		EventHandler *endHandler = _endHandler;
		remove();

		 if ((g_vm->getGameID() == GType_Ringworld2) && endHandler)
			 endHandler->signal();

		return;
	}

	// Run strip

	if (_obj44List.size() == 0)
		// Load the data for the strip
		load();

	Obj44 &obj44 = _obj44List[_obj44ListIndex];

	if (g_vm->getGameID() == GType_Ringworld2) {
		// Return to Ringworld specific handling
		if (obj44._exitMode)
			_exitMode = obj44._exitMode;

		switch (obj44._mode) {
		case 1:
			++_lookupList[obj44._lookupIndex - 1];
			break;
		case 2:
			--_lookupList[obj44._lookupIndex - 1];
			break;
		case 3:
			_lookupList[obj44._lookupIndex - 1] = obj44._lookupValue;
			break;
		default:
			break;
		}
	}

	_currObj44Id = obj44._id;
	Common::StringArray choiceList;

	// Build up a list of script entries
	int idx;
	bool delayFlag = false;

	if ((g_vm->getGameID() == GType_Ringworld2) && obj44._field16[0]) {
		// Special loading mode used in Return to Ringworld
		for (idx = 0; idx < OBJ44_LIST_SIZE; ++idx) {
			int f16Index = _lookupList[obj44._field16[0] - 1];
			int entryId = obj44._field16[f16Index];

			Obj0A &entry = obj44._list[idx];
			if (entry._id == entryId) {
				// Get the next one
				choiceList.push_back((const char *)&_script[0] + entry._scriptOffset);
				strIndex = idx;
				delayFlag = true;
				break;
			}
		}

		// If no entry found, get the default response
		if (!delayFlag) {
			idx = 0;
			while (obj44._list[idx + 1]._id)
				++idx;

			choiceList.push_back((const char *)&_script[0] + obj44._list[idx]._scriptOffset);
			strIndex = idx;
			delayFlag = true;
		}
	} else {
		// Standard choices loading
		for (idx = 0; idx < OBJ0A_LIST_SIZE; ++idx) {
			if (!obj44._list[idx]._id)
				break;

			// Get the next one
			const char *choiceStr = (const char *)&_script[0] + obj44._list[idx]._scriptOffset;

			if (!*choiceStr) {
				// Choice is empty
				assert(g_vm->getGameID() == GType_Ringworld2);

				if (obj44._list[1]._id) {
					// it's a reference to another list slot
					int listId = obj44._list[idx]._id;

					int obj44Idx = 0;
					while (_obj44List[obj44Idx]._id != listId)
						++obj44Idx;

					if (_obj44List[obj44Idx]._field16[0]) {
						// WORKAROUND: The _lookupList isn't always correctly initialized. But it always
						// seems to be set to the R2_GLOBALS._stripManager_lookupList, so manually set it
						if (!_lookupList)
							_lookupList = R2_GLOBALS._stripManager_lookupList;

						int f16Index = _lookupList[_obj44List[obj44Idx]._field16[0] - 1];
						listId = _obj44List[obj44Idx]._field16[f16Index];

						if (_lookupList[_obj44List[obj44Idx]._field16[0] - 1]) {
							int listIdx = 0;
							while (_obj44List[obj44Idx]._list[listIdx]._id != listId)
								++listIdx;

							choiceStr = (const char *)&_script[0] + _obj44List[obj44Idx]._list[listIdx]._scriptOffset;
						} else {
							for (int listIdx = idx; listIdx < (OBJ0A_LIST_SIZE - 1); ++listIdx) {
								obj44._list[listIdx]._id = obj44._list[listIdx + 1]._id;
								obj44._list[listIdx]._scriptOffset = obj44._list[listIdx + 1]._scriptOffset;

								if (!obj44._list[listIdx + 1]._id)
									obj44._list[listIdx]._id = 0;
							}

							--idx;
							continue;
						}
					}
				}
			}

			// Add entry to the list
			choiceList.push_back(choiceStr);
		}
	}

	if (choiceList.size() > 1)
		// Get the user to select a conversation option
		strIndex = _choiceDialog.execute(choiceList);

	if ((delayFlag || choiceList.size() != 1) && !_uselessFl)
		_delayFrames = 1;
	else {
		Speaker *speakerP = getSpeaker((const char *)&_script[0] + obj44._speakerOffset);
		if (!speakerP)
			error("Speaker not found.  Screenplay: %s %d", (const char *)&_script[0] + obj44._speakerOffset, _stripNum);

		if (speakerP != _activeSpeaker) {
			if (_activeSpeaker)
				_activeSpeaker->remove();
			_activeSpeaker = speakerP;

			if ((_activeSpeaker->_newSceneNumber == -1) && (g_globals->_sceneManager._scene->_screenNumber != _sceneNumber)) {
				g_globals->_sceneManager._scene->_sceneBounds = _sceneBounds;
				g_globals->_sceneManager._scene->loadScene(_sceneNumber);
			}

			_activeSpeaker->startSpeaking(this);
		}

		if (_callbackObject) {
			for (idx = 0; idx < OBJ44_LIST_SIZE; ++idx) {
				if (!obj44._callbackId[idx])
					break;

				_callbackObject->stripCallback(obj44._callbackId[idx]);
			}
		}

		if (g_vm->getGameID() == GType_Ringworld2) {
			Ringworld2::VisualSpeaker *speaker = static_cast<Ringworld2::VisualSpeaker *>(_activeSpeaker);

			if (speaker) {
				speaker->_speakerMode = obj44._speakerMode;
				if (!choiceList[strIndex].empty())
					speaker->animateSpeaker();
			}

			if (!choiceList[strIndex].empty()) {
				_textShown = true;
				_activeSpeaker->setText(choiceList[strIndex]);
			} else if (!obj44._speakerMode) {
				_delayFrames = 1;
			} else {
				_delayFrames = 0;
				speaker->animateSpeaker();
			}
		} else {
			_textShown = true;
			_activeSpeaker->setText(choiceList[strIndex]);
		}
	}

	_obj44ListIndex = getNewIndex(obj44._list[strIndex]._id);
	if (_obj44ListIndex == 10001) {
		MessageDialog::show("Strip Failure: Node not found", OK_BTN_STRING);
		_obj44ListIndex = 0;
	}
}

void StripManager::process(Event &event) {
	Action::process(event);
	if (event.handled)
		return;

	if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
		if (_obj44ListIndex != 10000) {
			int currIndex = _obj44ListIndex;
			while (!_obj44List[_obj44ListIndex]._list[1]._id) {
				_obj44ListIndex = getNewIndex(_obj44List[_obj44ListIndex]._list[0]._id);
				if ((_obj44ListIndex < 0) || (_obj44ListIndex == 10000))
					break;
				currIndex = _obj44ListIndex;
			}

			_currObj44Id = _obj44List[currIndex]._id;
		}

		// Signal the end of the strip
		_delayFrames = 0;
		event.handled = true;
		signal();
	} else if (event.eventType & (EVENT_BUTTON_DOWN | EVENT_KEYPRESS)) {
		// Move to next sequence in the strip
		_delayFrames = 0;
		event.handled = true;
		signal();
	}
}

void StripManager::addSpeaker(Speaker *speaker) {
	assert(_speakerList.size() < 100);
	_speakerList.push_back(speaker);
}

Speaker *StripManager::getSpeaker(const char *speakerName) {
	for (uint idx = 0; idx < _speakerList.size(); ++idx) {
		if (!strcmp(_speakerList[idx]->_speakerName.c_str(), speakerName))
			return _speakerList[idx];
	}

	// TODO: Check if it necessary to make a strcmp first.
	//
	// If nothing is found, recheck and ignore the case as
	// in R2R, some character names aren't in uppercase.
	if (g_vm->getGameID() == GType_Ringworld2) {
		for (uint idx = 0; idx < _speakerList.size(); ++idx) {
			if (!scumm_stricmp(_speakerList[idx]->_speakerName.c_str(), speakerName))
				return _speakerList[idx];
		}
	}

	return NULL;
}

int StripManager::getNewIndex(int id) {
	if (id == 10000)
		return id;
	if ((g_vm->getGameID() == GType_Ringworld2) && (id < 0))
		return id;

	for (uint idx = 0; idx < _obj44List.size(); ++idx) {
		if (_obj44List[idx]._id == id) {
			return (id == 0) ? 10001 : idx;
		}
	}

	return 10001;
}

/*--------------------------------------------------------------------------*/

Speaker::Speaker() : EventHandler() {
	_newSceneNumber = -1;
	_hideObjects = true;
	_field18 = 0;
	_textWidth = 140;
	_textPos = Common::Point(10, 20);
	_fontNumber = 2;
	_textMode = ALIGN_LEFT;
	_color1 = _color2 = _color3 = g_globals->_scenePalette._colors.foreground;
	_action = NULL;
	_speakerName = "SPEAKER";
	_oldSceneNumber = -1;
}

void Speaker::synchronize(Serializer &s) {
	if (s.getVersion() >= 2)
		EventHandler::synchronize(s);

	_fieldA.synchronize(s);
	SYNC_POINTER(_field18);
	s.syncString(_speakerName);
	s.syncAsSint32LE(_newSceneNumber);
	s.syncAsSint32LE(_oldSceneNumber);
	_sceneBounds.synchronize(s);
	s.syncAsSint32LE(_textWidth);
	s.syncAsSint16LE(_textPos.x); s.syncAsSint16LE(_textPos.y);
	s.syncAsSint32LE(_fontNumber);
	SYNC_ENUM(_textMode, TextAlign);
	s.syncAsSint16LE(_color1);
	s.syncAsSint16LE(_color2);
	s.syncAsSint16LE(_color3);
	s.syncAsByte(_hideObjects);
}

void Speaker::remove() {
	if (_hideObjects)
		SceneObjectList::deactivate();
}

void Speaker::startSpeaking(Action *action) {
	_action = action;
	if (_newSceneNumber != -1) {
		_oldSceneNumber = g_globals->_sceneManager._sceneNumber;
		_sceneBounds = g_globals->_sceneManager._scene->_sceneBounds;
		g_globals->_sceneManager._scene->loadScene(_newSceneNumber);
	}

	if (_hideObjects)
		// Activate the object list for display
		_objectList.activate();

	// Draw the speaker objects without any fading
	FadeMode fadeMode = g_globals->_sceneManager._fadeMode;
	g_globals->_sceneManager._fadeMode = FADEMODE_IMMEDIATE;
	g_globals->_sceneObjects->draw();
	g_globals->_sceneManager._fadeMode = fadeMode;
}

void Speaker::setText(const Common::String &msg) {
	g_globals->_sceneObjects->draw();

	_sceneText._color1 = _color1;
	_sceneText._color2 = _color2;
	_sceneText._color3 = _color3;
	_sceneText._width = _textWidth;
	_sceneText._fontNumber = _fontNumber;
	_sceneText._textMode = _textMode;
	_sceneText.setup(msg);
	_sceneText.setPosition(_textPos);
	_sceneText.fixPriority(256);

	// Count the number of words (by spaces) in the string
	const char *msgP = msg.c_str();
	int spaceCount = 0;
	while (*msgP) {
		if (*msgP++ == ' ')
			++spaceCount;
	}

	int numFrames = spaceCount * STRIP_WORD_DELAY + 120;
	if (_action)
		_action->setDelay(numFrames);
}

void Speaker::removeText() {
	_sceneText.remove();
}

/*--------------------------------------------------------------------------*/

SpeakerGameText::SpeakerGameText() : Speaker() {
	_speakerName = "GAMETEXT";
	_textPos = Common::Point(40, 40);
	_textMode = ALIGN_CENTER;
	_color1 = 7;
	_textWidth = 230;
	_hideObjects = false;
}

/*--------------------------------------------------------------------------*/

ScreenSpeaker::ScreenSpeaker() : Speaker() {
	_npc = NULL;
	_textMode = ALIGN_CENTER;
}

void ScreenSpeaker::setText(const Common::String &msg) {
	GfxManager gfxMan;
	gfxMan.activate();
	gfxMan._font.setFontNumber(_fontNumber);
	Rect textRect;

	g_globals->gfxManager().getStringBounds(msg.c_str(), textRect, _textWidth);
	if (_npc) {
		textRect.center(_npc->_position.x, _npc->_bounds.top - (textRect.height() / 2 + 10));
	} else {
		textRect.center(g_globals->_sceneManager._scene->_sceneBounds.left +
			(g_globals->_sceneManager._scene->_sceneBounds.width() / 2),
			g_globals->_sceneManager._scene->_sceneBounds.top);
	}

	Rect rect2 = g_globals->_sceneManager._scene->_sceneBounds;
	rect2.collapse(10, 6);
	textRect.contain(rect2);

	_textPos.x = textRect.left;
	_textPos.y = textRect.top;
	Speaker::setText(msg);

	gfxMan.deactivate();
}

/*--------------------------------------------------------------------------*/

void SpeakerAction::signal() {
	switch (_actionIndex++) {
	case 0:
		setDelay(g_globals->_randomSource.getRandomNumber(60) + 60);
		break;
	case 1:
		static_cast<SceneObject *>(_owner)->setFrame(1);
		static_cast<SceneObject *>(_owner)->animate(ANIM_MODE_5, this, NULL);
		break;
	case 2:
		setDelay(g_globals->_randomSource.getRandomNumber(10));
		_actionIndex = 0;
		break;
	default:
		break;
	}
}

/*--------------------------------------------------------------------------*/

void AnimatedSpeaker::removeText() {
	Speaker::removeText();
	_object1.remove();
	_object2.remove();

	_objectList.draw();
}

} // end of namespace TsAGE
