/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "lure/res.h"
#include "lure/disk.h"
#include "lure/scripts.h"
#include "lure/screen.h"
#include "lure/lure.h"
#include "common/endian.h"
#include "common/events.h"

namespace Lure {

static Resources *int_resources = nullptr;

Resources &Resources::getReference() {
	return *int_resources;
}

Resources::Resources() : _rnd(LureEngine::getReference().rnd()) {
	int_resources = this;
	reloadData();

	// Load the string list
	MemoryBlock *mb = Disk::getReference().getEntry(STRING_LIST_RESOURCE_ID);
	_stringList.load(mb);
	delete mb;

	// WORKAROUND: In Spanish the look "Obsevar" should be "Observar"
	if (!Common::String(_stringList.getString(LOOK)).compareTo("Obsevar"))
		_stringList.setString(LOOK, "Observar");
}

Resources::~Resources() {
	// Free up any loaded data
	freeData();

	// Free up constant data
	_stringList.clear();
}

void Resources::freeData() {
	_activeHotspots.clear();
	_roomData.clear();
	_hotspotData.clear();
	_hotspotOverrides.clear();
	_animData.clear();
	_exitJoins.clear();
	_delayList.clear();
	_charSchedules.clear();
	_randomActions.clear();
	_indexedRoomExitHospots.clear();
	_pausedList.clear();
	_actionsList.clear();
	_coordinateList.clear();
	_talkHeaders.clear();
	_talkData.clear();
	_giveTalkIds.clear();

	free(_hotspotScriptData);
	delete _paletteSubset;
	delete _scriptData;
	delete _script2Data;
	delete _talkDialogData;
	delete _messagesData;
	delete _cursors;
	delete[] _charOffsets;
}

struct AnimRecordTemp {
	uint16 offset;
	MovementDataList *list;
};

void Resources::reset() {
	freeData();

	_fieldList.reset();
	_barmanLists.reset();
	_talkState = TALK_NONE;
	_activeTalkData = nullptr;

	reloadData();
}

void Resources::reloadData() {
	Disk &d = Disk::getReference();
	bool isEGA = LureEngine::getReference().isEGA();
	MemoryBlock *mb, *paths;
	uint16 *offset, offsetVal;
	uint16 recordId, startOffset;
	int ctr;
	uint16 *v;

	// Get the palette subset data
	_paletteSubset = isEGA ? nullptr : new Palette(ALT_PALETTE_RESOURCE_ID);

	// Load room data
	mb = d.getEntry(ROOM_DATA_RESOURCE_ID);
	paths = d.getEntry(ROOM_PATHS_RESOURCE_ID);

	offset = (uint16 *) mb->data();
	while ((offsetVal = READ_LE_UINT16(offset++)) != 0xffff) {
		if (offsetVal != 0) {
			// Get room resource
			RoomResource *rec = (RoomResource *) (mb->data() + offsetVal);

			RoomData *newEntry = new RoomData(rec, paths);
			_roomData.push_back(RoomDataList::value_type(newEntry));

			uint8 numExits = rec->numExits;
			if (numExits > 0) {
				RoomExitResource *exitRes = (RoomExitResource *)
					(mb->data() + offsetVal + sizeof(RoomResource));

				for (uint16 exitCtr = 0; exitCtr < numExits; ++exitCtr, ++exitRes) {
					RoomExitData *exit = new RoomExitData(exitRes);
					newEntry->exits.push_back(RoomExitList::value_type(exit));
				}
			}
		}
	}
	delete mb;
	delete paths;

	// Load room exits
	mb = d.getEntry(ROOM_EXITS_RESOURCE_ID);
	ctr = 0;
	for (;;) {
		offsetVal = READ_LE_UINT16(mb->data() + (ctr * 2));
		if (offsetVal == 0xffff) break;

		if (offsetVal != 0) {
			RoomData *room = getRoom(ctr);
			if (room) {
				RoomExitHotspotResource *re = (RoomExitHotspotResource *)
					(mb->data() + offsetVal);
				while (READ_LE_UINT16(&re->hotspotId) != 0xffff) {
					RoomExitHotspotData *newEntry = new RoomExitHotspotData(re);
					room->exitHotspots.push_back(RoomExitHotspotList::value_type(newEntry));
					++re;
				}
			}
		}
		++ctr;
	}
	delete mb;

	// Load room joins
	mb = d.getEntry(ROOM_EXIT_JOINS_RESOURCE_ID);
	RoomExitJoinResource *joinRec = (RoomExitJoinResource *) mb->data();
	while (READ_LE_UINT16(&joinRec->hotspot1Id) != 0xffff) {
		RoomExitJoinData *newEntry = new RoomExitJoinData(joinRec);
		_exitJoins.push_back(RoomExitJoinList::value_type(newEntry));

		GET_NEXT(joinRec, RoomExitJoinResource);
	}
	delete mb;

	// Load the set of NPC schedules
	mb = d.getEntry(NPC_SCHEDULES_RESOURCE_ID);

	// Load the lookup list of support data indexes used in the script engine
	numCharOffsets = 0;
	offset = (uint16 *) mb->data();
	while (READ_LE_UINT16(offset++) != 0xffff) ++numCharOffsets;
	_charOffsets = new uint16[numCharOffsets];
	offset = (uint16 *) mb->data();
	for (ctr = 0; ctr < numCharOffsets; ++ctr, ++offset)
		_charOffsets[ctr] = READ_LE_UINT16(offset);

	// Next load up the list of random actions your follower can do in each room

	++offset;
	while (READ_LE_UINT16(offset) != 0xffff) {
		RandomActionSet *actionSet = new RandomActionSet(offset);
		_randomActions.push_back(RandomActionList::value_type(actionSet));
	}

	// Loop through loading the schedules
	ctr = 0;
	while ((startOffset = READ_LE_UINT16(++offset)) != 0xffff) {
		CharacterScheduleResource *res = (CharacterScheduleResource *) (mb->data() + startOffset);
		CharacterScheduleSet *newEntry = new CharacterScheduleSet(res, ++ctr);
		_charSchedules.push_back(CharacterScheduleList::value_type(newEntry));
	}
	delete mb;

	// Load the hotspot list
	mb = d.getEntry(HOTSPOT_DATA_RESOURCE_ID);
	HotspotResource *hsRec = (HotspotResource *) mb->data();
	while (READ_LE_UINT16(&hsRec->hotspotId) != 0xffff) {
		HotspotData *newEntry = new HotspotData(hsRec);
		_hotspotData.push_back(HotspotDataList::value_type(newEntry));

		GET_NEXT(hsRec, HotspotResource);
	}
	delete mb;

	// Load the hotspot overrides
	mb = d.getEntry(HOTSPOT_OVERRIDE_DATA_RESOURCE_ID);
	HotspotOverrideResource *hsoRec = (HotspotOverrideResource *) mb->data();
	while (READ_LE_UINT16(&hsoRec->hotspotId) != 0xffff) {
		HotspotOverrideData *newEntry = new HotspotOverrideData(hsoRec);
		_hotspotOverrides.push_back(HotspotOverrideList::value_type(newEntry));
		++hsoRec;
	}
	delete mb;

	// Load the animation list
	mb = d.getEntry(ANIM_DATA_RESOURCE_ID);
	HotspotAnimResource *animRec = (HotspotAnimResource *) mb->data();
	while (READ_LE_UINT16(&animRec->animRecordId) != 0xffff) {
		HotspotAnimData *newEntry = new HotspotAnimData(animRec);
		_animData.push_back(HotspotAnimList::value_type(newEntry));

		// Handle any direction frames
		AnimRecordTemp dirEntries[4] = {
			{FROM_LE_16(animRec->leftOffset), &newEntry->leftFrames},
			{FROM_LE_16(animRec->rightOffset), &newEntry->rightFrames},
			{FROM_LE_16(animRec->upOffset), &newEntry->upFrames},
			{FROM_LE_16(animRec->downOffset), &newEntry->downFrames}};
		for (int dirCtr = 0; dirCtr < 4; ++dirCtr) {
			offsetVal = dirEntries[dirCtr].offset;
			if (offsetVal != 0) {
				MovementResource *moveRec = (MovementResource *)
					(mb->data() + offsetVal);
				while (READ_LE_UINT16(&moveRec->frameNumber) != 0xffff) {
					MovementData *newMove = new MovementData(moveRec);
					dirEntries[dirCtr].list->push_back(MovementDataList::value_type(newMove));
					++moveRec;
				}
			}
		}

		++animRec;
	}
	delete mb;

	// Hotspot scripts
	mb = d.getEntry(HOTSPOT_SCRIPT_LIST_RESOURCE_ID);
	uint16 numEntries = mb->size() / 2;
	uint16 *srcVal = (uint16 *) mb->data();
	uint16 *destVal = _hotspotScriptData = (uint16 *)
		Memory::alloc(numEntries * sizeof(uint16));
	for (ctr = 0; ctr < numEntries; ++ctr, ++srcVal, ++destVal) {
		*destVal = READ_LE_UINT16(srcVal);
	}
	delete mb;

	// Handle the hotspot action lists
	mb = d.getEntry(ACTION_LIST_RESOURCE_ID);
	v = (uint16 *) mb->data();
	while ((recordId = READ_LE_UINT16(v)) != 0xffff) {
		++v;
		offsetVal = READ_LE_UINT16(v);
		++v;

		HotspotActionList *list = new HotspotActionList(
			recordId, mb->data() + offsetVal);
		_actionsList.push_back(HotspotActionSet::value_type(list));
	}
	delete mb;

	// Read in the talk data header
	mb = d.getEntry(TALK_HEADER_RESOURCE_ID);
	TalkHeaderResource *thHeader = (TalkHeaderResource *) mb->data();
	uint16 hotspotId;
	while ((hotspotId = READ_LE_UINT16(&thHeader->hotspotId)) != 0xffff) {
		uint16 *offsets = (uint16 *) (mb->data() + READ_LE_UINT16(&thHeader->offset));
		TalkHeaderData *newEntry = new TalkHeaderData(hotspotId, offsets);

		_talkHeaders.push_back(TalkHeaderList::value_type(newEntry));
		++thHeader;
	}
	delete mb;

	// Read in the talk data entries
	mb = d.getEntry(TALK_DATA_RESOURCE_ID);

	// First get the list of give talk Ids
	v = (uint16 *) mb->data();

	for (int talkIndex = 0; talkIndex < NUM_GIVE_TALK_IDS; ++talkIndex)
		_giveTalkIds.push_back(READ_LE_UINT16(v++));

	// Get the following talk ata

	byte *dataStart = (byte *) v;
	TalkDataHeaderResource *tdHeader = (TalkDataHeaderResource *) dataStart;

	while ((recordId = READ_LE_UINT16(&tdHeader->recordId)) != 0xffff) {
		TalkData *data = new TalkData(recordId);

		TalkDataResource *entry = (TalkDataResource *) (dataStart + READ_LE_UINT16(&tdHeader->listOffset));
		while (READ_LE_UINT16(&entry->preSequenceId) != 0xffff) {
			TalkEntryData *newEntry = new TalkEntryData(entry);
			data->entries.push_back(TalkEntryList::value_type(newEntry));
			++entry;
		}

		entry = (TalkDataResource *) (dataStart + READ_LE_UINT16(&tdHeader->responsesOffset));
		while (READ_LE_UINT16(&entry->preSequenceId) != 0xffff) {
			TalkEntryData *newEntry = new TalkEntryData(entry);
			data->responses.push_back(TalkEntryList::value_type(newEntry));
			++entry;
		}

		_talkData.push_back(TalkDataList::value_type(data));
		++tdHeader;
	}
	delete mb;

	// Load in the list of room exit coordinates
	mb = d.getEntry(EXIT_COORDINATES_RESOURCE_ID);
	RoomExitCoordinateEntryResource *coordRec = (RoomExitCoordinateEntryResource *) mb->data();
	while (READ_LE_UINT16(coordRec) != 0xffff) {
		RoomExitCoordinates *newEntry = new RoomExitCoordinates(coordRec);
		_coordinateList.push_back(RoomExitCoordinatesList::value_type(newEntry));
		++coordRec;
	}
	delete mb;

	// Load the list of room exit hotspot Ids
	mb = d.getEntry(EXIT_HOTSPOT_ID_LIST);
	RoomExitIndexedHotspotResource *indexedRec = (RoomExitIndexedHotspotResource *) mb->data();
	while (READ_LE_UINT16(indexedRec) != 0xffff) {
		_indexedRoomExitHospots.push_back(RoomExitIndexedHotspotList::value_type(new RoomExitIndexedHotspotData(indexedRec)));
		indexedRec++;
	}
	delete mb;

	// Initialize delay list
	_delayList.clear(true);

	// Load miscellaneous data
	_cursors = d.getEntry(CURSOR_RESOURCE_ID);
	_scriptData = d.getEntry(SCRIPT_DATA_RESOURCE_ID);
	_script2Data = d.getEntry(SCRIPT2_DATA_RESOURCE_ID);
	_messagesData = d.getEntry(MESSAGES_LIST_RESOURCE_ID);
	_talkDialogData = d.getEntry(TALK_DIALOG_RESOURCE_ID);

	_activeTalkData = nullptr;
	_currentAction = NONE;
	_talkState = TALK_NONE;
	_talkSelection = 0;
	_talkStartEntry = 0;
	_talkDetails.active = false;
	_talkingCharacter = 0;
}

RoomExitJoinData *Resources::getExitJoin(uint16 hotspotId) {
	RoomExitJoinList::iterator i;

	for (i = _exitJoins.begin(); i != _exitJoins.end(); ++i) {
		RoomExitJoinData *rec = (*i).get();
		if ((rec->hotspots[0].hotspotId == hotspotId) || (rec->hotspots[1].hotspotId == hotspotId))
			return rec;
	}

	return nullptr;
}

uint16 Resources::getHotspotScript(uint16 index) {
	return _hotspotScriptData[index];
}

RoomData *Resources::getRoom(uint16 roomNumber) {
	RoomDataList::iterator i;

	for (i = _roomData.begin(); i != _roomData.end(); ++i) {
		RoomData *rec = (*i).get();
		if (rec->roomNumber == roomNumber) return rec;
	}

	return nullptr;
}

bool Resources::checkHotspotExtent(HotspotData *hotspot) {
	uint16 roomNum = hotspot->roomNumber;
	RoomData *room = getRoom(roomNum);
	return (hotspot->startX >= room->clippingXStart) && ((room->clippingXEnd == 0) ||
			(hotspot->startX + 32 < room->clippingXEnd));
}

void Resources::insertPaletteSubset(Palette &p) {
	p.palette()->copyFrom(_paletteSubset->palette(), 0, 129*4, 60*4);
	p.palette()->copyFrom(_paletteSubset->palette(), 60*4, 220*4, 8*4);
}

byte *Resources::getCursor(uint8 cursorNum) {
	if (!LureEngine::getReference().isEGA())
		return _cursors->data() + (cursorNum * CURSOR_SIZE);

	Common::fill(&_cursor[0], &_cursor[0] + CURSOR_SIZE, 0);
	byte *pSrc = _cursors->data() + (cursorNum * 64);
	byte *pDest = &_cursor[0];

	for (int y = 0; y < 16; ++y) {
		for (int x = 0; x < 2; ++x) {
			for (int planeNum = 0; planeNum < 2; ++planeNum, ++pSrc) {

				byte v = *pSrc;
				for (int bitCtr = 0; bitCtr < 8; ++bitCtr, v <<= 1) {
					if ((v & 0x80) != 0)
						*(pDest + bitCtr) |= 1 << planeNum;
					else
						*(pDest + bitCtr) &= ~(1 << planeNum);
				}
			}

			pDest += 8;
		}
	}

	// Post-process the cells to adjust the color
	for (int index = 0; index < CURSOR_SIZE; ++index) {
		if (_cursor[index] == 3) _cursor[index] = 15;
	}

	return &_cursor[0];
}

HotspotData *Resources::getHotspot(uint16 hotspotId) {
	HotspotDataList::iterator i;

	for (i = _hotspotData.begin(); i != _hotspotData.end(); ++i) {
		HotspotData *rec = (*i).get();
		if (rec->hotspotId == hotspotId) return rec;
	}

	return nullptr;
}

Hotspot *Resources::getActiveHotspot(uint16 hotspotId) {
	HotspotList::iterator i;

	for (i = _activeHotspots.begin(); i != _activeHotspots.end(); ++i) {
		Hotspot *rec = (*i).get();
		if (rec->hotspotId() == hotspotId) return rec;
	}

	return nullptr;
}


HotspotOverrideData *Resources::getHotspotOverride(uint16 hotspotId) {
	HotspotOverrideList::iterator i;

	for (i = _hotspotOverrides.begin(); i != _hotspotOverrides.end(); ++i) {
		HotspotOverrideData *rec = (*i).get();
		if (rec->hotspotId == hotspotId) return rec;
	}

	return nullptr;
}

HotspotAnimData *Resources::getAnimation(uint16 animRecordId) {
	HotspotAnimList::iterator i;

	for (i = _animData.begin(); i != _animData.end(); ++i) {
		HotspotAnimData *rec = (*i).get();
		if (rec->animRecordId == animRecordId) return rec;
	}

	return nullptr;
}

int Resources::getAnimationIndex(HotspotAnimData *animData) {
	HotspotAnimList::iterator i;
	int index = 0;

	for (i = _animData.begin(); i != _animData.end(); ++i, ++index) {
		HotspotAnimData *rec = (*i).get();
		if (rec == animData)
			return index;
	}

	return -1;
}

uint16 Resources::getHotspotAction(uint16 actionsOffset, Action action) {
	HotspotActionList *list = _actionsList.getActions(actionsOffset);
	uint16 offset = (!list) ? 0 : list->getActionOffset(action);
	debugC(ERROR_DETAILED, kLureDebugHotspots,
		"Resources::getHotspotAction actionsOffset=%xh result=%xh", actionsOffset, offset);
	return offset;
}

TalkHeaderData *Resources::getTalkHeader(uint16 hotspotId) {
	TalkHeaderList::iterator i;
	for (i = _talkHeaders.begin(); i != _talkHeaders.end(); ++i) {
		TalkHeaderData *rec = (*i).get();
		if (rec->characterId == hotspotId) return rec;
	}
	return nullptr;
}

HotspotActionList *Resources::getHotspotActions(uint16 actionsOffset) {
	return _actionsList.getActions(actionsOffset);
}

void Resources::setTalkingCharacter(uint16 id) {
	Resources &res = Resources::getReference();

	if (_talkingCharacter != 0) {
		deactivateHotspot(_talkingCharacter, true);
		HotspotData *charHotspot = res.getHotspot(_talkingCharacter);
		assert(charHotspot);
		charHotspot->talkDestCharacterId = 0;

		if (_talkingCharacter != id)
			charHotspot->talkCountdown = 0;
	}

	_talkingCharacter = id;

	if (_talkingCharacter != 0) {
		Hotspot *character = getActiveHotspot(id);
		assert(character);

		// Add the special "voice" animation above the character
		Hotspot *hotspot = new Hotspot(character, VOICE_ANIM_IDX);
		addHotspot(hotspot);
	}
}
uint16 englishLoadOffsets[] = {0x3afe, 0x41BD, 0x7167, 0x7172, 0x8617, 0x88ac, 0};

Hotspot *Resources::activateHotspot(uint16 hotspotId) {
	Resources &resources = Resources::getReference();
	HotspotData *res = getHotspot(hotspotId);
	if (!res) return nullptr;
	res->roomNumber &= 0x7fff; // clear any suppression bit in room #

	// Make sure that the hotspot isn't already active
	Hotspot *h = getActiveHotspot(hotspotId);
	if (h != nullptr)
		return h;

	// If it's NPC with a schedule, then activate the schedule
	if ((res->npcScheduleId != 0) && (res->npcSchedule.isEmpty())) {
		CharacterScheduleEntry *entry = resources.charSchedules().getEntry(res->npcScheduleId);
		res->npcSchedule.addFront(DISPATCH_ACTION, entry, res->roomNumber);
	}

	// Check the script load flag
	if (res->scriptLoadFlag) {
		// Execute a script rather than doing a standard load
		Script::execute(res->loadOffset);
	} else {
		// Standard load
		bool loadFlag = true;
		uint16 talkIndex;

		switch (res->loadOffset) {
		case 1:
			// Copy protection check - since the game is freeware now, ignore it
			loadFlag = false;
			break;

		case 2:
			// Empty handler used to prevent loading hotspots that
			// are yet to be active (such as the straw fire)
			loadFlag = false;
			break;

		case 3:
		case 4:
			// Standard animation load
			break;

		case 5:
			// Custom loader used by the notice hotspot 42ah in room #20
			talkIndex = _fieldList.getField(TALK_INDEX);
			if ((talkIndex < 8) || (talkIndex >= 14))
				// Don't load hotspot
				loadFlag = false;
			else
				// Make the notice be on-screen
				res->startY = 85;
			break;

		case 6:
			// Torch in room #1
			loadFlag = _fieldList.getField(TORCH_HIDE) == 0;
			break;

		default:
			// All others simply activate the hotspot
			warning("Hotspot %d uses unknown load offset index %d",
				res->hotspotId, res->loadOffset);
		}

		if (loadFlag) {
			Hotspot *hotspot = addHotspot(hotspotId);
			assert(hotspot);

			// Special post-load handling
			if (res->loadOffset == 3) hotspot->setPersistent(true);
			if (res->loadOffset == 5) hotspot->handleTalkDialog();
			if (hotspotId == CASTLE_SKORL_ID) {
				// The Castle skorl has a default room #99, so it needs to be adjusted dynamically
				res->npcSchedule.clear();
				CharacterScheduleEntry *entry = resources.charSchedules().getEntry(res->npcScheduleId);
				res->npcSchedule.addFront(DISPATCH_ACTION, entry, res->roomNumber);
			}
			if ((hotspotId == GOEWIN_ID) && (hotspot->roomNumber() == 39)) {
				// WORKAROUND: When you re-join Goewin in the caves, clear her schedule. This may prevent a
				// situation where you could close the left door, and she'd be permanently stuck trying to go
				// the next room on the left, since her old schedule still had her following your old path
				hotspot->currentActions().clear();

				// Since she's no longer a follower, clear her start room field
				hotspot->setStartRoomNumber(0);
			}

			// TODO: Figure out why there's a room set in the animation decode for a range of characters,
			// particularly since it doesn't seem to match what happens in-game
			/*
			if ((hotspot->hotspotId() >= RATPOUCH_ID) &&
				(hotspot->hotspotId() < FIRST_NONCHARACTER_ID) &&
				(hotspot->roomNumber() < 42)) {
				// Start wandering characters off in room 24
				hotspot->setRoomNumber(24);
				hotspot->setPosition(64, 116);
				_fieldList.wanderingCharsLoaded() = true;
			}
			*/

			return hotspot;
		}
	}

	return nullptr;
}

Hotspot *Resources::addHotspot(uint16 hotspotId) {
	HotspotData *hData = getHotspot(hotspotId);
	assert(hData);
	Hotspot *hotspot = new Hotspot(hData);
	_activeHotspots.push_back(HotspotList::value_type(hotspot));

	if (hotspotId < FIRST_NONCHARACTER_ID) {
		// Default characters to facing upwards until they start moving
		hotspot->setDirection(UP);
		hotspot->setCharRectY(0);

		// When reactivating an NPC, ensure that their previous state wasn't PROCESSING_PATH, since
		// the pause has destroyed the previously decided destination position
		if (!hData->npcSchedule.isEmpty() && (hData->npcSchedule.top().action() == PROCESSING_PATH))
			hData->npcSchedule.top().setAction(DISPATCH_ACTION);
	}

	return hotspot;
}

void Resources::addHotspot(Hotspot *hotspot) {
	_activeHotspots.push_back(HotspotList::value_type(hotspot));
}

void Resources::deactivateHotspot(uint16 hotspotId, bool isDestId) {
	HotspotList::iterator i = _activeHotspots.begin();

	while (i != _activeHotspots.end()) {
		Hotspot const &h = **i;
		if ((!isDestId && (h.hotspotId() == hotspotId)) ||
			(isDestId && (h.destHotspotId() == hotspotId) && (h.hotspotId() == 0xffff))) {
			_activeHotspots.erase(i);
			break;
		}

		i++;
	}
}

void Resources::deactivateHotspot(Hotspot *hotspot) {
	HotspotList::iterator i = _activeHotspots.begin();

	while (i != _activeHotspots.end()) {
		Hotspot *h = (*i).get();
		if (h == hotspot) {
			_activeHotspots.erase(i);
			break;
		}

		i++;
	}
}

uint16 Resources::numInventoryItems() {
	uint16 numItems = 0;
	HotspotDataList &list = _hotspotData;
	HotspotDataList::iterator i;
	for (i = list.begin(); i != list.end(); ++i) {
		if ((*i)->roomNumber == PLAYER_ID) ++numItems;
	}

	return numItems;
}

void Resources::copyCursorTo(Surface *s, uint8 cursorNum, int16 x, int16 y) {
	byte *pSrc = getCursor(cursorNum);
	byte *pDest = s->data().data() + (y * FULL_SCREEN_WIDTH) + x;

	for (int yP = 0; yP < CURSOR_HEIGHT; ++yP) {
		for (int xP = 0; xP < CURSOR_WIDTH; ++xP) {
			if (*pSrc != 0) *pDest = *pSrc;
			++pSrc;
			++pDest;
		}
		pDest += FULL_SCREEN_WIDTH - CURSOR_WIDTH;
	}
}

void Resources::setTalkData(uint16 offset) {
	if (offset == 0) {
		_activeTalkData = nullptr;
		return;
	}

	TalkDataList::iterator i;
	for (i = _talkData.begin(); i != _talkData.end(); ++i) {
		TalkData *rec = (*i).get();
		if (rec->recordId == offset) {
			_activeTalkData = rec;
			return;
		}
	}

	error("Unknown talk entry offset %d requested", offset);
}

void Resources::saveToStream(Common::WriteStream *stream) {
	// Save basic fields
	stream->writeUint16LE(_talkingCharacter);

	// Save out the schedule for any non-active NPCs
	HotspotDataList::iterator i;
	for (i = _hotspotData.begin(); i != _hotspotData.end(); ++i) {
		HotspotData const &rec = **i;
		if (!rec.npcSchedule.isEmpty()) {
			Hotspot *h = getActiveHotspot(rec.hotspotId);
			if (h == nullptr) {
				stream->writeUint16LE(rec.hotspotId);
				rec.npcSchedule.saveToStream(stream);
			}
		}
	}
	stream->writeUint16LE(0xffff);

	// Save sublist data
	_hotspotData.saveToStream(stream);
	_activeHotspots.saveToStream(stream);
	_fieldList.saveToStream(stream);
	_randomActions.saveToStream(stream);
	_barmanLists.saveToStream(stream);
	_exitJoins.saveToStream(stream);
	_roomData.saveToStream(stream);
	_delayList.saveToStream(stream);
	_talkData.saveToStream(stream);
}

void Resources::loadFromStream(Common::ReadStream *stream) {
	uint8 saveVersion = LureEngine::getReference().saveVersion();

	if (saveVersion >= 26) {
		debugC(ERROR_DETAILED, kLureDebugScripts, "Loading resource data");
		_talkingCharacter = stream->readUint16LE();
	} else {
		_talkingCharacter = 0;
	}

	_talkState = TALK_NONE;
	_activeTalkData = nullptr;

	if (saveVersion >= 31) {
		// Load in any schedules for non-active NPCS
		debugC(ERROR_DETAILED, kLureDebugScripts, "Loading NPC schedules");
		uint16 hotspotId;
		while ((hotspotId = stream->readUint16LE()) != 0xffff) {
			HotspotData *hotspot = getHotspot(hotspotId);
			assert(hotspot);
			hotspot->npcSchedule.loadFromStream(stream);
		}
	}

	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading hotspot data");
	_hotspotData.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading active hotspots");
	_activeHotspots.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading fields");
	_fieldList.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading random actions");
	_randomActions.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading barman lists");
	_barmanLists.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading room exit joins");
	_exitJoins.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading walkable paths");
	_roomData.loadFromStream(stream);
	debugC(ERROR_DETAILED, kLureDebugScripts, "Loading delay list");
	_delayList.loadFromStream(stream);

	if (saveVersion >= 32) {
		debugC(ERROR_DETAILED, kLureDebugScripts, "Loading talk data");
		_talkData.loadFromStream(stream);
	}

	debugC(ERROR_DETAILED, kLureDebugScripts, "Finished loading");
}

} // End of namespace Lure
