/* 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/scummsys.h"
#include "mads/mads.h"
#include "mads/compression.h"
#include "mads/user_interface.h"
#include "mads/nebular/game_nebular.h"

namespace MADS {

UISlot::UISlot() {
	_flags = IMG_STATIC;
	_segmentId = 0;
	_spritesIndex = 0;
	_frameNumber = 0;
	_width = _height = 0;
}

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

void UISlots::fullRefresh() {
	UISlot slot;
	slot._flags = IMG_REFRESH;
	slot._segmentId = -1;

	push_back(slot);
}

void UISlots::add(const Common::Rect &bounds) {
	assert(size() < 50);

	UISlot ie;
	ie._flags = IMG_OVERPRINT;
	ie._segmentId = IMG_TEXT_UPDATE;
	ie._position = Common::Point(bounds.left, bounds.top);
	ie._width = bounds.width();
	ie._height = bounds.height();

	push_back(ie);
}

void UISlots::add(const AnimFrameEntry &frameEntry) {
	assert(size() < 50);

	UISlot ie;
	ie._flags = IMG_UPDATE;
	ie._segmentId = frameEntry._seqIndex;
	ie._spritesIndex = frameEntry._spriteSlot._spritesIndex;
	ie._frameNumber = frameEntry._spriteSlot._frameNumber;
	ie._position = frameEntry._spriteSlot._position;

	push_back(ie);
}

void UISlots::draw(bool updateFlag, bool delFlag) {
	Scene &scene = _vm->_game->_scene;
	UserInterface &userInterface = scene._userInterface;
	DirtyArea *dirtyAreaPtr = nullptr;

	// Loop through setting up the dirty areas
	for (uint idx = 0; idx < size(); ++idx) {
		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
		UISlot &slot = (*this)[idx];

		if (slot._flags >= IMG_STATIC) {
			dirtyArea._active = false;
		} else {
			dirtyArea.setUISlot(&slot);
			dirtyArea._textActive = true;
			if (slot._segmentId == IMG_SPINNING_OBJECT && slot._flags == IMG_FULL_UPDATE) {
				dirtyArea._active = false;
				dirtyAreaPtr = &dirtyArea;
			}
		}
	}

	userInterface._dirtyAreas.merge(1, userInterface._uiSlots.size());
	if (dirtyAreaPtr)
		dirtyAreaPtr->_active = true;

	// Copy parts of the user interface background that need to be erased
	for (uint idx = 0; idx < size(); ++idx) {
		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
		UISlot &slot = (*this)[idx];

		if (dirtyArea._active && dirtyArea._bounds.width() > 0
				&& dirtyArea._bounds.height() > 0 && slot._flags > -20) {

			if (slot._flags >= IMG_ERASE) {
				// Merge area
				userInterface.mergeFrom(&userInterface._surface, dirtyArea._bounds,
					Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top));
			} else {
				// Copy area
				userInterface._surface.copyTo(&userInterface, dirtyArea._bounds,
					Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top));
			}
		}
	}

	for (uint idx = 0; idx < size(); ++idx) {
		DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];
		UISlot &slot = (*this)[idx];

		int slotType = slot._flags;
		if (slotType >= IMG_STATIC) {
			dirtyArea.setUISlot(&slot);
			if (!updateFlag)
				slotType &= ~0x40;

			dirtyArea._textActive = slotType > 0;
			slot._flags &= 0x40;
		}
	}

	userInterface._dirtyAreas.merge(1, userInterface._uiSlots.size());

	for (uint idx = 0; idx < size(); ++idx) {
		DirtyArea *dirtyArea = &userInterface._dirtyAreas[idx];
		UISlot &slot = (*this)[idx];

		if (slot._flags >= IMG_STATIC && !(slot._flags & 0x40)) {
			if (!dirtyArea->_active) {
				do {
					dirtyArea = dirtyArea->_mergedArea;
				} while (!dirtyArea->_active);
			}

			if (dirtyArea->_textActive) {
				SpriteAsset *asset = scene._sprites[slot._spritesIndex];

				// Get the frame details
				int frameNumber = ABS(slot._frameNumber);
				bool flipped = slot._frameNumber < 0;

				if (slot._segmentId == IMG_SPINNING_OBJECT) {
					MSprite *sprite = asset->getFrame(frameNumber - 1);
					sprite->copyTo(&userInterface, slot._position,
						sprite->getTransparencyIndex());
				} else {
					MSprite *sprite = asset->getFrame(frameNumber - 1);

					if (flipped) {
						MSurface *spr = sprite->flipHorizontal();
						userInterface.mergeFrom(spr, spr->getBounds(), slot._position,
							sprite->getTransparencyIndex());
						delete spr;
					} else {
						userInterface.mergeFrom(sprite, sprite->getBounds(), slot._position,
							sprite->getTransparencyIndex());
					}
				}
			}
		}
	}

	// Mark areas of the screen surface for updating
	if (updateFlag) {
		for (uint idx = 0; idx < size(); ++idx) {
			DirtyArea &dirtyArea = userInterface._dirtyAreas[idx];

			if (dirtyArea._active && dirtyArea._textActive &&
				dirtyArea._bounds.width() > 0 && dirtyArea._bounds.height() > 0) {
				// Flag area of screen as needing update
				Common::Rect r = dirtyArea._bounds;
				r.translate(0, scene._interfaceY);
				_vm->_screen.copyRectToScreen(r);
			}
		}
	}

	// Post-processing to remove slots no longer needed
	for (int idx = (int)size() - 1; idx >= 0; --idx) {
		UISlot &slot = (*this)[idx];

		if (slot._flags < IMG_STATIC) {
			if (delFlag || updateFlag)
				remove_at(idx);
			else if (slot._flags > -20)
				slot._flags -= 20;
		} else {
			if (updateFlag)
				slot._flags &= ~0x40;
			else
				slot._flags |= 0x40;
		}
	}
}

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

MADSEngine *Conversation::_vm;

void Conversation::init(MADSEngine *vm) {
	_vm = vm;
}

void Conversation::setup(int globalId, ...) {
	va_list va;
	va_start(va, globalId);

	// Load the list of conversation quotes
	_quotes.clear();
	int quoteId = va_arg(va, int);
	while (quoteId > 0) {
		_quotes.push_back(quoteId);
		quoteId = va_arg(va, int);
	}
	va_end(va);

	if (quoteId < 0) {
		// For an ending value of -1, also initial the bitflags for the global
		// associated with the conversation entry, which enables all the quote Ids
		_vm->_game->globals()[globalId] = (int16)0xffff;
	}

	_globalId = globalId;
}

void Conversation::set(int quoteId, ...) {
	_vm->_game->globals()[_globalId] = 0;

	va_list va;
	va_start(va, quoteId);

	// Loop through handling each quote
	while (quoteId > 0) {
		for (uint idx = 0; idx < _quotes.size(); ++idx) {
			if (_quotes[idx] == quoteId) {
				// Found index, so set that bit in the global keeping track of conversation state
				_vm->_game->globals()[_globalId] |= 1 << idx;
				break;
			}
		}

		quoteId = va_arg(va, int);
	}
	va_end(va);
}

int Conversation::read(int quoteId) {
	uint16 flags = _vm->_game->globals()[_globalId];
	int count = 0;

	for (uint idx = 0; idx < _quotes.size(); ++idx) {
		if (flags & (1 << idx))
			++count;

		if (_quotes[idx] == quoteId)
			return flags & (1 << idx);
	}

	// Could not find it, simply return number of active quotes
	return count;
}

void Conversation::write(int quoteId, bool flag) {
	for (uint idx = 0; idx < _quotes.size(); ++idx) {
		if (_quotes[idx] == quoteId) {
			// Found index, so set or clear the flag
			if (flag) {
				// Set bit
				_vm->_game->globals()[_globalId] |= 1 << idx;
			} else {
				// Clear bit
				_vm->_game->globals()[_globalId] &= ~(1 << idx);
			}
			return;
		}
	}
}

void Conversation::start() {
	UserInterface &userInterface = _vm->_game->_scene._userInterface;
	userInterface.emptyConversationList();

	// Loop through each of the quotes loaded into the conversation
	for (uint idx = 0; idx < _quotes.size(); ++idx) {
		// Check whether the given quote is enabled or not
		if (_vm->_game->globals()[_globalId] & (1 << idx)) {
			// Quote enabled, so add it to the list of talk selections
			Common::String msg = _vm->_game->getQuote(_quotes[idx]);
			userInterface.addConversationMessage(_quotes[idx], msg);
		}
	}

	userInterface.setup(kInputConversation);
}

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

UserInterface::UserInterface(MADSEngine *vm) : _vm(vm), _dirtyAreas(vm),
		_uiSlots(vm) {
	_invSpritesIndex = -1;
	_invFrameNumber = 1;
	_scrollMilli = 0;
	_scrollFlag = false;
	_category = CAT_NONE;
	_inventoryTopIndex = 0;
	_selectedInvIndex = -1;
	_selectedActionIndex = 0;
	_selectedItemVocabIdx = -1;
	_scrollbarActive = SCROLLBAR_NONE;
	_scrollbarOldActive = SCROLLBAR_NONE;
	_scrollbarStrokeType = SCROLLBAR_NONE;
	_scrollbarQuickly = false;
	_scrollbarMilliTime = 0;
	_scrollbarElevator = _scrollbarOldElevator = 0;
	_highlightedCommandIndex = -1;
	_highlightedInvIndex = -1;
	_highlightedItemVocabIndex = -1;
	_dirtyAreas.resize(50);
	_inventoryChanged = false;
	_noSegmentsActive = 0;
	_someSegmentsActive = 0;
	_rectP = nullptr;

	Common::fill(&_categoryIndexes[0], &_categoryIndexes[7], 0);

	// Map the user interface to the bottom of the game's screen surface
	byte *pData = _vm->_screen.getBasePtr(0, MADS_SCENE_HEIGHT);
	setPixels(pData, MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT);

	_surface.setSize(MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT);
}

void UserInterface::load(const Common::String &resName) {
	File f(resName);
	MadsPack madsPack(&f);

	// Load in the palette
	Common::SeekableReadStream *palStream = madsPack.getItemStream(0);

	uint32 *gamePalP = &_vm->_palette->_palFlags[0];
	byte *palP = &_vm->_palette->_mainPalette[0];

	for (int i = 0; i < 16; ++i, gamePalP++, palP += 3) {
		RGB6 rgb;
		rgb.load(palStream);
		palP[0] = rgb.r;
		palP[1] = rgb.g;
		palP[2] = rgb.b;
		*gamePalP |= 1;
	}
	delete palStream;

	// Read in the surface data
	Common::SeekableReadStream *pixelsStream = madsPack.getItemStream(1);
	pixelsStream->read(_surface.getData(), MADS_SCREEN_WIDTH * MADS_INTERFACE_HEIGHT);
	delete pixelsStream;
}

void UserInterface::setup(InputMode inputMode) {
	Scene &scene = _vm->_game->_scene;

	if (_vm->_game->_screenObjects._inputMode != inputMode) {
		Common::String resName = _vm->_game->_aaName;

		// Strip off any extension
		const char *p = strchr(resName.c_str(), '.');
		if (p) {
			resName = Common::String(resName.c_str(), p);
		}

		// Add on suffix if necessary
		if (inputMode != kInputBuildingSentences)
			resName += "A";

		resName += ".INT";

		load(resName);
		_surface.copyTo(this);
	}
	_vm->_game->_screenObjects._inputMode = inputMode;

	scene._userInterface._uiSlots.clear();
	scene._userInterface._uiSlots.fullRefresh();
	_vm->_game->_screenObjects._baseTime = _vm->_events->getFrameCounter();
	_highlightedCommandIndex = -1;
	_highlightedItemVocabIndex = -1;
	_highlightedInvIndex = -1;

	if (_vm->_game->_kernelMode == KERNEL_ACTIVE_CODE)
		scene._userInterface._uiSlots.draw(false, false);

	scene._action.clear();
	drawTextElements();
	loadElements();
	scene._dynamicHotspots.refresh();
}

void UserInterface::drawTextElements() {
	switch (_vm->_game->_screenObjects._inputMode) {
	case kInputBuildingSentences:
		// Draw the actions
		drawActions();
		drawInventoryList();
		drawItemVocabList();
		break;

	case kInputConversation:
		drawConversationList();
		break;

	case kInputLimitedSentences:
	default:
		break;
	}
}

void UserInterface::mergeFrom(MSurface *src, const Common::Rect &srcBounds,
	const Common::Point &destPos, int transparencyIndex) {
	// Validation of the rectangle and position
	int destX = destPos.x, destY = destPos.y;
	if ((destX >= w) || (destY >= h))
		return;

	Common::Rect copyRect = srcBounds;
	if (destX < 0) {
		copyRect.left += -destX;
		destX = 0;
	} else if (destX + copyRect.width() > w) {
		copyRect.right -= destX + copyRect.width() - w;
	}
	if (destY < 0) {
		copyRect.top += -destY;
		destY = 0;
	} else if (destY + copyRect.height() > h) {
		copyRect.bottom -= destY + copyRect.height() - h;
	}

	if (!copyRect.isValidRect())
		return;

	// Copy the specified area

	byte *data = src->getData();
	byte *srcPtr = data + (src->getWidth() * copyRect.top + copyRect.left);
	byte *destPtr = (byte *)this->pixels + (destY * getWidth()) + destX;

	for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) {
		// Process each line of the area
		for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) {
			// Check for the range used for the user interface background,
			// which are the only pixels that can be replaced
			if ((destPtr[xCtr] >= 8 && destPtr[xCtr] <= 15) && (int)srcPtr[xCtr] != transparencyIndex)
				destPtr[xCtr] = srcPtr[xCtr];
		}

		srcPtr += src->getWidth();
		destPtr += getWidth();
	}
}

void UserInterface::drawActions() {
	for (int idx = 0; idx < 10; ++idx) {
		writeVocab(CAT_COMMAND, idx);
	}
}

void UserInterface::drawInventoryList() {
	int endIndex = MIN((int)_vm->_game->_objects._inventoryList.size(), _inventoryTopIndex + 5);
	for (int idx = _inventoryTopIndex; idx < endIndex; ++idx) {
		writeVocab(CAT_INV_LIST, idx);
	}
}

void UserInterface::drawItemVocabList() {
	if (_selectedInvIndex >= 0) {
		InventoryObject &io = _vm->_game->_objects[
			_vm->_game->_objects._inventoryList[_selectedInvIndex]];
		for (int idx = 0; idx < io._vocabCount; ++idx) {
			writeVocab(CAT_INV_VOCAB, idx);
		}
	}
}

void UserInterface::drawScroller() {
	if (_scrollbarActive)
		writeVocab(CAT_INV_SCROLLER, _scrollbarActive);
	writeVocab(CAT_INV_SCROLLER, 4);
}

void UserInterface::updateInventoryScroller() {
	ScreenObjects &screenObjects = _vm->_game->_screenObjects;

	if (screenObjects._inputMode != kInputBuildingSentences)
		return;

	_scrollbarActive = SCROLLBAR_NONE;

	if ((screenObjects._category == CAT_INV_SCROLLER) || (screenObjects._category != CAT_INV_SCROLLER
			&& _scrollbarOldActive == SCROLLBAR_ELEVATOR && _vm->_events->_mouseStatusCopy)) {
		if (_vm->_events->_mouseStatusCopy || _vm->_easyMouse) {
			if ((_vm->_events->_mouseClicked || (_vm->_easyMouse && !_vm->_events->_mouseStatusCopy))
					&& (screenObjects._category == CAT_INV_SCROLLER))
				_scrollbarStrokeType = (ScrollbarActive)screenObjects._spotId;

			if (screenObjects._spotId == _scrollbarStrokeType || _scrollbarOldActive == SCROLLBAR_ELEVATOR) {
				_scrollbarActive = _scrollbarStrokeType;
				uint32 currentMilli = g_system->getMillis();
				uint32 timeInc = _scrollbarQuickly ? 100 : 380;

				if (_vm->_events->_mouseStatus && (_scrollbarMilliTime + timeInc) <= currentMilli) {
					_scrollbarQuickly = _vm->_events->_strokeGoing < 1;
					_scrollbarMilliTime = currentMilli;

					// Change the scrollbar and visible inventory list
					changeScrollBar();
				}
			}
		}
	}

	if (_scrollbarActive != _scrollbarOldActive || _scrollbarElevator != _scrollbarOldElevator)
		scrollbarChanged();

	_scrollbarOldActive = _scrollbarActive;
	_scrollbarOldElevator = _scrollbarElevator;
}

void UserInterface::changeScrollBar() {
	Common::Array<int> &inventoryList = _vm->_game->_objects._inventoryList;
	ScreenObjects &screenObjects = _vm->_game->_screenObjects;

	if (screenObjects._inputMode != kInputBuildingSentences)
		return;

	switch (_scrollbarStrokeType) {
	case SCROLLBAR_UP:
		// Scroll up
		if (_inventoryTopIndex > 0 && inventoryList.size() > 0) {
			--_inventoryTopIndex;
			_inventoryChanged = true;
		}
		break;

	case SCROLLBAR_DOWN:
		// Scroll down
		if (_inventoryTopIndex < ((int)inventoryList.size() - 1) && inventoryList.size() > 1) {
			++_inventoryTopIndex;
			_inventoryChanged = true;
		}
		break;

	case SCROLLBAR_ELEVATOR: {
		// Inventory slider
		int newIndex = CLIP((int)_vm->_events->currentPos().y - 170, 0, 17)
			* inventoryList.size() / 10;
		if (newIndex >= (int)inventoryList.size())
			newIndex = inventoryList.size() - 1;

		if (inventoryList.size() > 0) {
			_inventoryChanged = newIndex != _inventoryTopIndex;
			_inventoryTopIndex = newIndex;
		}
		break;
	}

	default:
		break;
	}

	if (_inventoryChanged) {
		int dummy;
		updateSelection(CAT_INV_LIST, 0, &dummy);
	}
}

void UserInterface::scrollbarChanged() {
	Common::Rect r(73, 4, 73 + 9, 4 + 38);
	_uiSlots.add(r);
	_uiSlots.draw(false, false);
	drawScroller();
	updateRect(r);
}

void UserInterface::writeVocab(ScrCategory category, int id) {
	Common::Rect bounds;
	if (!getBounds(category, id, bounds))
		return;

	Scene &scene = _vm->_game->_scene;
	Font *font = nullptr;

	int vocabId;
	Common::String vocabStr;
	switch (category) {
	case CAT_COMMAND:
		font = _vm->_font->getFont(FONT_INTERFACE);
		vocabId = scene._verbList[id]._id;
		if (id == _highlightedCommandIndex) {
			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
		} else {
			_vm->_font->setColorMode(id == _selectedActionIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
		}
		vocabStr = scene.getVocab(vocabId);
		vocabStr.setChar(toupper(vocabStr[0]), 0);
		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
		break;

	case CAT_INV_LIST:
		font = _vm->_font->getFont(FONT_INTERFACE);
		vocabId = _vm->_game->_objects.getItem(id)._descId;
		if (id == _highlightedInvIndex) {
			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
		} else {
			_vm->_font->setColorMode(id == _selectedInvIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
		}

		vocabStr = scene.getVocab(vocabId);
		vocabStr.setChar(toupper(vocabStr[0]), 0);
		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
		break;

	case CAT_TALK_ENTRY:
		font = _vm->_font->getFont(FONT_INTERFACE);
		font->setColorMode(id == _highlightedCommandIndex ? SELMODE_HIGHLIGHTED : SELMODE_UNSELECTED);
		font->writeString(this, _talkStrings[id], Common::Point(bounds.left, bounds.top));
		break;

	case CAT_INV_SCROLLER:
		font = _vm->_font->getFont(FONT_MISC);

		switch (id) {
		case 1:
			vocabStr = "a";
			break;
		case 2:
			vocabStr = "b";
			break;
		case 3:
			vocabStr = "d";
			break;
		case 4:
			vocabStr = "c";
			break;
		default:
			break;
		}

		font->setColorMode((id == 4) || (_scrollbarActive == SCROLLBAR_ELEVATOR) ?
			SELMODE_HIGHLIGHTED : SELMODE_UNSELECTED);
		font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
		break;
	default:
		// Item specific verbs
		font = _vm->_font->getFont(FONT_INTERFACE);
		vocabId = _vm->_game->_objects.getItem(_selectedInvIndex)._vocabList[id]._vocabId;
		if (id == _highlightedItemVocabIndex) {
			_vm->_font->setColorMode(SELMODE_HIGHLIGHTED);
		} else {
			_vm->_font->setColorMode(id == _selectedInvIndex ? SELMODE_SELECTED : SELMODE_UNSELECTED);
			vocabStr = scene.getVocab(vocabId);
			vocabStr.setChar(toupper(vocabStr[0]), 0);
			font->writeString(this, vocabStr, Common::Point(bounds.left, bounds.top));
			break;
		}
		break;
	}
}

void UserInterface::loadElements() {
	Scene &scene = _vm->_game->_scene;
	Common::Rect bounds;
	_vm->_game->_screenObjects.clear();

	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences) {
		// Set up screen objects for the inventory scroller
		for (int idx = 1; idx <= 3; ++idx) {
			getBounds(CAT_INV_SCROLLER, idx, bounds);
			moveRect(bounds);

			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_SCROLLER, idx);
		}

		// Set up actions
		_categoryIndexes[CAT_COMMAND - 1] = _vm->_game->_screenObjects.size() + 1;
		for (int idx = 0; idx < 10; ++idx) {
			getBounds(CAT_COMMAND, idx, bounds);
			moveRect(bounds);

			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_COMMAND, idx);
		}

		// Set up inventory list
		_categoryIndexes[CAT_INV_LIST - 1] = _vm->_game->_screenObjects.size() + 1;
		for (int idx = 0; idx < 5; ++idx) {
			getBounds(CAT_INV_LIST, _inventoryTopIndex + idx, bounds);
			moveRect(bounds);

			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_LIST, idx);
		}

		// Set up the inventory vocab list
		_categoryIndexes[CAT_INV_VOCAB - 1] = _vm->_game->_screenObjects.size() + 1;
		for (int idx = 0; idx < 5; ++idx) {
			getBounds(CAT_INV_VOCAB, idx, bounds);
			moveRect(bounds);

			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_INV_VOCAB, idx);
		}

		// Set up the inventory item picture
		_categoryIndexes[CAT_INV_ANIM - 1] = _vm->_game->_screenObjects.size() + 1;
		_vm->_game->_screenObjects.add(Common::Rect(160, 159, 231, 194), SCREENMODE_VGA,
			CAT_INV_ANIM, 0);
	}

	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences ||
			_vm->_game->_screenObjects._inputMode == kInputLimitedSentences) {
		_categoryIndexes[CAT_HOTSPOT - 1] = _vm->_game->_screenObjects.size() + 1;
		for (int hotspotIdx = scene._hotspots.size() - 1; hotspotIdx >= 0; --hotspotIdx) {
			Hotspot &hs = scene._hotspots[hotspotIdx];
			ScreenObject *so = _vm->_game->_screenObjects.add(hs._bounds, SCREENMODE_VGA, 
				CAT_HOTSPOT, hotspotIdx);
			so->_active = hs._active;
		}
	}

	if (_vm->_game->_screenObjects._inputMode == kInputConversation) {
		// setup areas for talk entries
		_categoryIndexes[CAT_TALK_ENTRY - 1] = _vm->_game->_screenObjects.size() + 1;
		for (int idx = 0; idx < 5; ++idx) {
			getBounds(CAT_TALK_ENTRY, idx, bounds);
			moveRect(bounds);

			_vm->_game->_screenObjects.add(bounds, SCREENMODE_VGA, CAT_TALK_ENTRY, idx);
		}
	}

	// Store the number of UI elements loaded for easy nuking/refreshing hotspots added later
	_vm->_game->_screenObjects._uiCount = _vm->_game->_screenObjects.size();
}

bool UserInterface::getBounds(ScrCategory category, int v, Common::Rect &bounds) {
	int heightMultiplier, widthMultiplier;
	int leftStart, yOffset, widthAmt;

	switch (category) {
	case CAT_COMMAND:
		heightMultiplier = v % 5;
		widthMultiplier = v / 5;
		leftStart = 2;
		yOffset = 3;
		widthAmt = 32;
		break;

	case CAT_INV_LIST:
		if (v < _inventoryTopIndex || v >= (_inventoryTopIndex + 5))
			return false;

		heightMultiplier = v - _inventoryTopIndex;
		widthMultiplier = 0;
		leftStart = 90;
		yOffset = 3;
		widthAmt = 69;
		break;

	case CAT_TALK_ENTRY:
		heightMultiplier = v;
		widthMultiplier = 0;
		leftStart = 2;
		yOffset = 3;
		widthAmt = 310;
		break;

	case CAT_INV_SCROLLER:
		heightMultiplier = 0;
		widthMultiplier = 0;
		yOffset = 0;
		widthAmt = 9;
		leftStart = (v != 73) ? 73 : 75;
		break;

	default:
		heightMultiplier = v;
		widthMultiplier = 0;
		leftStart = 240;
		yOffset = 3;
		widthAmt = 80;
		break;
	}

	bounds.left = (widthMultiplier > 0) ? widthMultiplier * widthAmt + leftStart : leftStart;
	bounds.setWidth(widthAmt);
	bounds.top = heightMultiplier * 8 + yOffset;
	bounds.setHeight(8);

	if (category == CAT_INV_SCROLLER) {
		switch (v) {
		case SCROLLBAR_UP:
			// Arrow up
			bounds.top = 4;
			bounds.setHeight(7);
			break;
		case SCROLLBAR_DOWN:
			// Arrow down
			bounds.top = 35;
			bounds.setHeight(7);
			break;
		case SCROLLBAR_ELEVATOR:
			// Scroller
			bounds.top = 12;
			bounds.setHeight(22);
			break;
		case SCROLLBAR_THUMB:
			// Thumb
			bounds.top = _scrollbarElevator + 14;
			bounds.setHeight(1);
			break;
		default:
			break;
		}
	}

	return true;
}

void UserInterface::moveRect(Common::Rect &bounds) {
	bounds.translate(0, MADS_SCENE_HEIGHT);
}

void UserInterface::drawConversationList() {
	for (uint idx = 0; idx < _talkStrings.size(); ++idx) {
		writeVocab(CAT_TALK_ENTRY, idx);
	}
}

void UserInterface::emptyConversationList() {
	_talkStrings.clear();
	_talkIds.clear();
}

void UserInterface::addConversationMessage(int vocabId, const Common::String &msg) {
	// Only allow a maximum of 5 talk entries to be displayed
	if (_talkStrings.size() < 5) {
		_talkStrings.push_back(msg);
		_talkIds.push_back(vocabId);
	}
}

void UserInterface::loadInventoryAnim(int objectId) {
	Scene &scene = _vm->_game->_scene;
	noInventoryAnim();

	// WORKAROUND: Even in still mode, we now load the animation frames for the
	// object, so we can show the first frame as a 'still'
	Common::String resName = Common::String::format("*OB%.3dI", objectId);
	SpriteAsset *asset = new SpriteAsset(_vm, resName, ASSET_SPINNING_OBJECT);
	_invSpritesIndex = scene._sprites.add(asset, 1);
	if (_invSpritesIndex >= 0) {
		_invFrameNumber = 1;
	}
}

void UserInterface::noInventoryAnim() {
	Scene &scene = _vm->_game->_scene;

	if (_invSpritesIndex >= 0) {
		scene._sprites.remove(_invSpritesIndex);
		_vm->_game->_screenObjects._baseTime = _vm->_events->getFrameCounter();
		_invSpritesIndex = -1;
	}

	if (_vm->_game->_screenObjects._inputMode == kInputBuildingSentences)
		refresh();
}

void UserInterface::refresh() {
	_uiSlots.clear();
	_uiSlots.fullRefresh();
	_uiSlots.draw(false, false);

	drawTextElements();
}

void UserInterface::inventoryAnim() {
	Scene &scene = _vm->_game->_scene;
	if (_vm->_game->_screenObjects._inputMode == kInputConversation ||
			_vm->_game->_screenObjects._inputMode == kInputLimitedSentences ||
			_invSpritesIndex < 0)
		return;

	// WORKAROUND: Fix still inventory display, which was broken in the original
	if (_vm->_invObjectsAnimated) {
		// Move to the next frame number in the sequence, resetting if at the end
		SpriteAsset *asset = scene._sprites[_invSpritesIndex];
		if (++_invFrameNumber > asset->getCount())
			_invFrameNumber = 1;
	}

	// Loop through the slots list for inventory animation entry
	for (uint i = 0; i < _uiSlots.size(); ++i) {
		if (_uiSlots[i]._segmentId == IMG_SPINNING_OBJECT)
			_uiSlots[i]._flags = IMG_FULL_UPDATE;
	}

	// Add a new slot entry for the inventory animation
	UISlot slot;
	slot._flags = IMG_UPDATE;
	slot._segmentId = IMG_SPINNING_OBJECT;
	slot._frameNumber = _invFrameNumber;
	slot._spritesIndex = _invSpritesIndex;
	slot._position = Common::Point(160, 3);

	_uiSlots.push_back(slot);
}

void UserInterface::doBackgroundAnimation() {
	Scene &scene = _vm->_game->_scene;
	Common::Array<AnimUIEntry> &uiEntries = scene._animationData->_uiEntries;
	Common::Array<AnimFrameEntry> &frameEntries = scene._animationData->_frameEntries;

	_noSegmentsActive = !_someSegmentsActive;
	_someSegmentsActive = false;

	for (int idx = 0; idx < (int)uiEntries.size(); ++idx) {
		AnimUIEntry &uiEntry = uiEntries[idx];

		if (uiEntry._counter < 0) {
			if (uiEntry._counter == -1) {
				int probabilityRandom = _vm->getRandomNumber(1, 30000);
				int probability = uiEntry._probability;
				if (uiEntry._probability > 30000) {
					if (_noSegmentsActive) {
						probability -= 30000;
					} else {
						probability = -1;
					}
				}
				if (probabilityRandom <= probability) {
					uiEntry._counter = uiEntry._firstImage;
					_someSegmentsActive = true;
				}
			} else {
				uiEntry._counter = uiEntry._firstImage;
				_someSegmentsActive = true;
			}
		} else {
			for (int idx2 = 0; idx2 < ANIM_SPAWN_COUNT; idx2++) {
				if (uiEntry._spawnFrame[idx2] == (uiEntry._counter - uiEntry._firstImage)) {
					int tempIndex = uiEntry._spawn[idx2];
					if (idx >= tempIndex) {
						uiEntries[tempIndex]._counter = uiEntries[tempIndex]._firstImage;
					} else {
						uiEntries[tempIndex]._counter = -2;
					}
					_someSegmentsActive = true;
				}
			}

			++uiEntry._counter;
			if (uiEntry._counter > uiEntry._lastImage) {
				uiEntry._counter = -1;
			} else {
				_someSegmentsActive = true;
			}
		}
	}

	for (uint idx = 0; idx < uiEntries.size(); ++idx) {
		int imgScan = uiEntries[idx]._counter;
		if (imgScan >= 0) {
			_uiSlots.add(frameEntries[imgScan]);
		}
	}
}

void UserInterface::categoryChanged() {
	_highlightedInvIndex = -1;
	_vm->_events->initVars();
	_category = CAT_NONE;
}

void UserInterface::selectObject(int invIndex) {
	if (_selectedInvIndex != invIndex || _inventoryChanged) {
		int oldVocabCount = _selectedInvIndex < 0 ? 0 : _vm->_game->_objects.getItem(_selectedInvIndex)._vocabCount;
		int newVocabCount = invIndex < 0 ? 0 : _vm->_game->_objects.getItem(invIndex)._vocabCount;
		int maxVocab = MAX(oldVocabCount, newVocabCount);

		updateSelection(CAT_INV_LIST, invIndex, &_selectedInvIndex);
		_highlightedItemVocabIndex = -1;
		_selectedItemVocabIdx = -1;

		if (maxVocab) {
			assert(_uiSlots.size() < 50);
			int vocabHeight = maxVocab * 8;

			Common::Rect bounds(240, 3, 240 + 80, 3 + vocabHeight);
			_uiSlots.add(bounds);
			_uiSlots.draw(false, false);
			drawItemVocabList();
			updateRect(bounds);
		}
	}

	if (invIndex == -1) {
		noInventoryAnim();
	} else {
		loadInventoryAnim(_vm->_game->_objects._inventoryList[invIndex]);
		_vm->_palette->setPalette(&_vm->_palette->_mainPalette[7 * 3], 7, 1);
		_vm->_palette->setPalette(&_vm->_palette->_mainPalette[246 * 3], 246, 2);
	}
}

void UserInterface::updateSelection(ScrCategory category, int newIndex, int *idx) {
	Game &game = *_vm->_game;
	Common::Array<int> &invList = game._objects._inventoryList;
	Common::Rect bounds;

	if (category == CAT_INV_LIST && _inventoryChanged) {
		*idx = newIndex;
		bounds = Common::Rect(90, 3, 90 + 69, 3 + 40);
		_uiSlots.add(bounds);
		_uiSlots.draw(false, false);
		drawInventoryList();
		updateRect(bounds);
		_inventoryChanged = false;

		if (invList.size() < 2) {
			_scrollbarElevator = 0;
		} else {
			int v = _inventoryTopIndex * 18 / (invList.size() - 1);
			_scrollbarElevator = MIN(v, 17);
		}
	} else {
		int oldIndex = *idx;
		*idx = newIndex;

		if (oldIndex >= 0) {
			writeVocab(category, oldIndex);

			if (getBounds(category, oldIndex, bounds))
				updateRect(bounds);
		}

		if (newIndex >= 0) {
			writeVocab(category, newIndex);

			if (getBounds(category, newIndex, bounds))
				updateRect(bounds);
		}
	}
}

void UserInterface::updateRect(const Common::Rect &bounds) {
	Common::Rect r = bounds;
	r.translate(0, MADS_SCENE_HEIGHT);
	_vm->_screen.copyRectToScreen(r);
}

void UserInterface::scrollerChanged() {
	warning("TODO: scrollerChanged");
}

void UserInterface::scrollInventory() {
	Common::Array<int> &invList = _vm->_game->_objects._inventoryList;

	if (_vm->_events->_mouseButtons) {
		int yp = _vm->_events->currentPos().y;
		if (yp < MADS_SCENE_HEIGHT || yp == (MADS_SCREEN_HEIGHT - 1)) {
			uint32 timeDiff = _scrollFlag ? 100 : 380;
			uint32 currentMilli = g_system->getMillis();
			_vm->_game->_screenObjects._v8332A = -1;

			if (currentMilli >= (_scrollMilli + timeDiff)) {
				_scrollMilli = currentMilli;
				_scrollFlag = true;

				if (yp == (MADS_SCREEN_HEIGHT - 1)) {
					if (_inventoryTopIndex < ((int)invList.size() - 1)) {
						++_inventoryTopIndex;
						_inventoryChanged = true;
					}
				} else {
					if (_inventoryTopIndex > 0) {
						--_inventoryTopIndex;
						_inventoryChanged = true;
					}
				}
			}
		}
	}

	_vm->_game->_screenObjects._v8332A = 0;
}

void UserInterface::synchronize(Common::Serializer &s) {
	InventoryObjects &invObjects = _vm->_game->_objects;

	if (s.isLoading()) {
		_selectedInvIndex = invObjects._inventoryList.empty() ? -1 : 0;
	}

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

} // End of namespace MADS