/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "common/system.h"

#include "m4/dialogs.h"
#include "m4/globals.h"
#include "m4/scene.h"
#include "m4/events.h"
#include "m4/graphics.h"
#include "m4/rails.h"
#include "m4/font.h"
#include "m4/m4_views.h"
#include "m4/compression.h"
#include "m4/staticres.h"

namespace M4 {

static const int INV_ANIM_FRAME_SPEED = 8;
static const int INVENTORY_X = 160;
static const int INVENTORY_Y = 159;
static const int SCROLLER_DELAY = 200;


Scene::Scene(M4Engine *vm): View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) {
	_screenType = VIEWID_SCENE;

	_sceneResources.hotspots = new HotSpotList();
	_sceneResources.parallax = new HotSpotList();
	_sceneResources.props = new HotSpotList();
	_backgroundSurface = new M4Surface();
	_codeSurface = new M4Surface();
	_madsInterfaceSurface = new MadsInterfaceView(vm);
	_sceneSprites = NULL;
	_palData = NULL;
	_interfacePal = NULL;
	_inverseColorTable = NULL;
	strcpy(_statusText, "");
	_vm->_rails->setCodeSurface(_codeSurface);
	_currentAction = kVerbNone;
}

Scene::~Scene() {
	_sceneResources.hotspots->clear();
	_sceneResources.parallax->clear();
	_sceneResources.props->clear();

	delete _sceneResources.hotspots;
	delete _sceneResources.parallax;
	delete _sceneResources.props;

	delete _backgroundSurface;
	delete _codeSurface;

	if (_sceneSprites)
		delete _sceneSprites;

//	_vm->_palette->deleteAllRanges();

	if (_palData)
		delete _palData;

	if (_interfacePal)
		delete _interfacePal;

	if (_inverseColorTable)
		delete[] _inverseColorTable;

}

void Scene::loadScene(int sceneNumber) {
	_currentScene = sceneNumber;

	// Close the menu if it's active
	if (!_vm->isM4()) {
		View *mainMenu = _vm->_viewManager->getView(VIEWID_MAINMENU);
		if (mainMenu != NULL) {
			_vm->_viewManager->deleteView(mainMenu);
		}
	}

	// TODO: Check if we were loading a game


	// Load scene background and set palette
	if (_palData) {
		_vm->_palette->deleteRange(_palData);
		delete _palData;
	}

	if (_interfacePal) {
		_vm->_palette->deleteRange(_interfacePal);
		delete _interfacePal;
	}

	if (_vm->isM4()) {
		_backgroundSurface->loadBackground(sceneNumber);
		_palData = NULL;
	} else {
		// Set system palette entries
		_vm->_palette->blockRange(0, 7);
		RGB8 sysColors[3] = { {0x1f<<2, 0x2d<<2, 0x31<<2, 0}, {0x24<<2, 0x37<<2, 0x3a<<2, 0}, 
			{0x00<<2, 0x10<<2, 0x16<<2, 0}};
		_vm->_palette->setPalette(&sysColors[0], 4, 3);

		_backgroundSurface->loadBackground(sceneNumber, &_palData);
		_vm->_palette->addRange(_palData);
		_backgroundSurface->translate(_palData);

		if (sceneNumber < 900) {
			/*_backgroundSurface->fillRect(Common::Rect(0, MADS_SURFACE_HEIGHT,
				_backgroundSurface->width(), _backgroundSurface->height()),
				_vm->_palette->BLACK);*/
			// TODO: interface palette
			_madsInterfaceSurface->madsloadInterface(0, &_interfacePal);
			_vm->_palette->addRange(_interfacePal);
			_madsInterfaceSurface->translate(_interfacePal);
			_backgroundSurface->copyFrom(_madsInterfaceSurface, Common::Rect(0, 0, 320, 44), 0, 200 - 44);

			_madsInterfaceSurface->initialise();
		}
	}

	if (_vm->getGameType() == GType_Burger &&
		sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER)
		_vm->_interfaceView->setStatusText("");

	// Load scene def file (*.CHK)
	if (_vm->isM4()) {
		loadSceneResources(sceneNumber);
		loadSceneInverseColorTable(sceneNumber);
	} else {
		// Don't load other screen resources for system screens
		if (sceneNumber >= 900)
			return;

		loadSceneHotSpotsMads(sceneNumber);
	}

	// TODO: set walker scaling
	// TODO: destroy woodscript buffer

	// Load scene walk path file (*.COD/*.WW?)
	loadSceneCodes(sceneNumber);

	// Load inverse color table file (*.IPL)
	loadSceneInverseColorTable(sceneNumber);

	if (_vm->isM4()) {

		if (_vm->getGameType() != GType_Burger) {
			// Load scene sprites file (*.SSB)
			loadSceneSprites(sceneNumber);

			// Load scene sprite codes file (*.SSC)
			loadSceneSpriteCodes(sceneNumber);
		}


		if (sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER) {
			_vm->_interfaceView->show();
			showSprites();
		}
	}

	// Purge resources
	_vm->res()->purge();
}

void Scene::loadSceneResources(int sceneNumber) {
	char filename[kM4MaxFilenameSize];
	int i = 0, x = 0, y = 0;
	sprintf(filename, "%i.chk", sceneNumber);

	Common::SeekableReadStream *sceneS = _vm->res()->get(filename);

	if (sceneS != NULL) {
		sceneS->read(_sceneResources.artBase, MAX_CHK_FILENAME_SIZE);
		sceneS->read(_sceneResources.pictureBase, MAX_CHK_FILENAME_SIZE);
		_sceneResources.hotspotCount = sceneS->readUint32LE();
		_sceneResources.parallaxCount = sceneS->readUint32LE();
		_sceneResources.propsCount = sceneS->readUint32LE();
		_sceneResources.frontY = sceneS->readUint32LE();
		_sceneResources.backY = sceneS->readUint32LE();
		_sceneResources.frontScale = sceneS->readUint32LE();
		_sceneResources.backScale = sceneS->readUint32LE();
		for (i = 0; i < 16; i++)
			_sceneResources.depthTable[i] = sceneS->readUint16LE();
		_sceneResources.railNodeCount = sceneS->readUint32LE();

		// Clear rails from previous scene
		_vm->_rails->clearRails();

		for (i = 0; i < _sceneResources.railNodeCount; i++) {
			x = sceneS->readUint32LE();
			y = sceneS->readUint32LE();
			if (_vm->_rails->addRailNode(x, y, true) < 0) {
				warning("Too many rail nodes defined for scene");
			}
		}

		// Clear current hotspot lists
		_sceneResources.hotspots->clear();
		_sceneResources.parallax->clear();
		_sceneResources.props->clear();

		_sceneResources.hotspots->loadHotSpots(sceneS, _sceneResources.hotspotCount);
		_sceneResources.parallax->loadHotSpots(sceneS, _sceneResources.parallaxCount);
		_sceneResources.props->loadHotSpots(sceneS, _sceneResources.propsCount);

		// Note that toss() deletes the MemoryReadStream
		_vm->res()->toss(filename);
	}
}

void Scene::loadSceneHotSpotsMads(int sceneNumber) {
	char filename[kM4MaxFilenameSize];
	sprintf(filename, "rm%i.hh", sceneNumber);
	MadsPack hotSpotData(filename, _vm);
	Common::SeekableReadStream *hotspotStream = hotSpotData.getItemStream(0);

	int hotspotCount = hotspotStream->readUint16LE();
	delete hotspotStream;

	_sceneResources.hotspotCount = hotspotCount;

	hotspotStream = hotSpotData.getItemStream(1);

	// Clear current hotspot lists
	_sceneResources.hotspots->clear();

	_sceneResources.hotspots->loadHotSpots(hotspotStream, _sceneResources.hotspotCount);

	delete hotspotStream;
}

void Scene::loadSceneCodes(int sceneNumber, int index) {
	char filename[kM4MaxFilenameSize];
	Common::SeekableReadStream *sceneS;

	if (_vm->isM4()) {
		sprintf(filename, "%i.cod", sceneNumber);
		sceneS = _vm->res()->openFile(filename);
		_codeSurface->loadCodesM4(sceneS);
		_vm->res()->toss(filename);
	} else if (_vm->getGameType() == GType_Phantom || _vm->getGameType() == GType_DragonSphere) {
		sprintf(filename, "rm%i.ww%i", sceneNumber, index);
		MadsPack walkData(filename, _vm);
		sceneS = walkData.getItemStream(0);
		_codeSurface->loadCodesMads(sceneS);
		_vm->res()->toss(filename);
	} else if (_vm->getGameType() == GType_RexNebular) {
		// TODO
		return;
	}
}

void Scene::loadSceneInverseColorTable(int sceneNumber) {
	char filename[kM4MaxFilenameSize];
	Common::SeekableReadStream *iplS;

	if (_vm->isM4()) {
		sprintf(filename, "%i.ipl", sceneNumber);
		iplS = _vm->res()->openFile(filename);
		if (_inverseColorTable)
			delete[] _inverseColorTable;
		_inverseColorTable = new byte[iplS->size()];
		iplS->read(_inverseColorTable, iplS->size());
		_vm->res()->toss(filename);
	} else {
		// TODO?
		return;
	}

}


void Scene::loadSceneSprites(int sceneNumber) {
	char filename[kM4MaxFilenameSize];
	sprintf(filename, "%i.ssb", sceneNumber);

	Common::SeekableReadStream *sceneS = _vm->res()->get(filename);
	_sceneSprites = new SpriteAsset(_vm, sceneS, sceneS->size(), filename);
	_vm->res()->toss(filename);

	printf("Scene has %d sprites, each one having %d colors\n", _sceneSprites->getCount(), _sceneSprites->getColorCount());
}

void Scene::loadSceneSpriteCodes(int sceneNumber) {
	char filename[kM4MaxFilenameSize];
	sprintf(filename, "%i.ssc", sceneNumber);

	Common::SeekableReadStream *sceneS = _vm->res()->get(filename);

	// TODO

	if (sceneS != NULL) {
		SpriteAsset* _sceneSpriteCodes = new SpriteAsset(_vm, sceneS, sceneS->size(), filename);
		int colorCount = _sceneSpriteCodes->getColorCount();
//			RGB8* spritePalette = _sceneSpriteCodes->getPalette();
		//_vm->_palette->setPalette(spritePalette, 0, colorCount);

		printf("Scene has %d sprite codes, each one having %d colors\n", _sceneSpriteCodes->getCount(), colorCount);

		// Note that toss() deletes the MemoryReadStream
		_vm->res()->toss(filename);
	}
}

void Scene::showSprites() {
	// TODO: This is all experimental code, it needs heavy restructuring
	// and cleanup

	// taken from set_walker_scaling() in adv_walk.cpp. A proper implementation will need
	// to store these in global variables
	int minScaling = FixedDiv(_sceneResources.backScale << 16, 100 << 16);
	int maxScaling = FixedDiv(_sceneResources.frontScale << 16, 100 << 16);
	int scaler;

	_vm->_actor->setWalkerDirection(kFacingSouthEast);
	//_vm->_actor->setWalkerPalette();

	// taken from set_walker_scaling() in adv_walk.cpp
	if (_sceneResources.frontY == _sceneResources.backY)
		scaler = 0;
	else
		scaler = FixedDiv(maxScaling - minScaling,
				 (_sceneResources.frontY << 16) - (_sceneResources.backY << 16));

	// FIXME: For now, we (incorrectly) scale the walker to 50% of the scene's max scaling
	_vm->_actor->setWalkerScaling(scaler / 2);
	// Test code to display the protagonist
	_vm->_actor->placeWalkerSpriteAt(0, 320, 200);

	// Test code to display scene sprites
	// TODO
}

void Scene::checkHotspotAtMousePos(int x, int y) {
	if (_vm->getGameType() == GType_Riddle)
		return;

	// TODO: loads of things to do here, only the mouse cursor and the status
	// text is changed for now

	// Only scene hotspots are checked for now, not parallax/props, as the
	// latter ones are not used by Orion Burger
	HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
	if (currentHotSpot != NULL && currentHotSpot->getActive()) {
		if (_vm->_mouse->getCursorNum() != CURSOR_LOOK &&
			_vm->_mouse->getCursorNum() != CURSOR_TAKE &&
			_vm->_mouse->getCursorNum() != CURSOR_USE &&
			_vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
			_vm->_mouse->setCursorNum(currentHotSpot->getCursor());
		}
		_vm->_interfaceView->setStatusText(currentHotSpot->getPrep());
	} else {
		if (_vm->_mouse->getCursorNum() != CURSOR_LOOK &&
			_vm->_mouse->getCursorNum() != CURSOR_TAKE &&
			_vm->_mouse->getCursorNum() != CURSOR_USE &&
			_vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
			_vm->_mouse->setCursorNum(0);
		} else {

		}
	}
}

void Scene::checkHotspotAtMousePosMads(int x, int y) {
	HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
	if (currentHotSpot != NULL) {
		_vm->_mouse->setCursorNum(currentHotSpot->getCursor());

		// This is the "easy" interface, which updates the status text when the mouse is moved
		// TODO: toggle this code for easy/normal interface mode
		char statusText[50];
		int verbId = _currentAction;
		if (verbId == kVerbNone)
			verbId = currentHotSpot->getVerbID();
		if (verbId == kVerbNone)
			verbId = kVerbWalkTo;

		sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(verbId), currentHotSpot->getVocab());

		statusText[0] = toupper(statusText[0]);	// capitalize first letter
		setMADSStatusText(statusText);
	} else {
		_vm->_mouse->setCursorNum(0);
		setMADSStatusText("");
	}
}

// Test function, shows all scene hotspots
void Scene::showHotSpots() {
	int i = 0;
	HotSpot *currentHotSpot;
	// hotspots (green)
	for (i = 0; i < _sceneResources.hotspotCount; i++) {
		currentHotSpot = _sceneResources.hotspots->get(i);
		_backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->GREEN);
	}
	if (_vm->isM4()) {
		// parallax (yellow)
		for (i = 0; i < _sceneResources.parallaxCount; i++) {
			currentHotSpot = _sceneResources.parallax->get(i);
			_backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->YELLOW);
		}
		// props (red)
		for (i = 0; i < _sceneResources.propsCount; i++) {
			currentHotSpot = _sceneResources.props->get(i);
			_backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->RED);
		}
	}
}

// Test function, shows all scene codes
void Scene::showCodes() {
	uint8 *pixelData = _codeSurface->getBasePtr(0, 0);
	for (int i = 0; i < _codeSurface->width() * _codeSurface->height(); i++)
		if (pixelData[i] & 0x10)
			pixelData[i] = 0xFF;
		else
			pixelData[i] = 0;

	byte colors[256 * 4];
	memset(colors, 0, sizeof(colors));
	colors[255 * 4 + 0] = 255;
	colors[255 * 4 + 1] = 255;
	colors[255 * 4 + 2] = 255;
	_vm->_palette->setPalette(colors, 0, 256);

	_backgroundSurface->copyFrom(_codeSurface, Common::Rect(0, 0, 640, 480), 0, 0);
	//_system->copyRectToScreen(codes->getBasePtr(0, 0), codes->w, 0, 0, codes->w, codes->h);
}

void Scene::playIntro() {

}

void Scene::show() {
	_vm->_viewManager->addView(this);
	_vm->_viewManager->addView(_madsInterfaceSurface);
}

void Scene::update() {
	// TODO: Needs a proper implementation
	// NOTE: Don't copy the background when in M4 mode or WoodScript anims won't be shown
	if (!_vm->isM4()) {
		_backgroundSurface->copyTo(this);

		// Handle display of any status text
		if (_statusText[0]) {
			// Text colors are inverted in Dragonsphere
			if (_vm->getGameType() == GType_DragonSphere)
				_vm->_font->setColors(_vm->_palette->BLACK, _vm->_palette->WHITE, _vm->_palette->BLACK);
			else
				_vm->_font->setColors(_vm->_palette->WHITE, _vm->_palette->BLACK, _vm->_palette->BLACK);

			_vm->_font->setFont(FONT_MAIN_MADS);
			_vm->_font->writeString(this, _statusText, (width() - _vm->_font->getWidth(_statusText)) / 2, 142, 0);
		}

		_madsInterfaceSurface->copyTo(this, 0, this->height() - _madsInterfaceSurface->height());
	}
}

void Scene::onRefresh(RectList *rects, M4Surface *destSurface) {
	update();
	View::onRefresh(rects, destSurface);
}

bool Scene::onEvent(M4EventType eventType, int32 param1, int x, int y, bool &captureEvents) {
	//if (_vm->getGameType() != GType_Burger)
	//	return false;

	// If the game is currently paused, don't do any scene processing
	if (_vm->_kernel->paused)
		return false;

	switch (eventType) {
	case MEVENT_LEFT_CLICK:
		{
			if (_vm->getGameType() == GType_Burger) {
				// Place a Wilbur sprite with the correct facing
				HotSpot	*currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
				if (currentHotSpot != NULL && currentHotSpot->getActive()) {
					update();
					_vm->_actor->setWalkerDirection(currentHotSpot->getFacing());
					/*
					int posX = currentHotSpot->getFeetX();
					int posY = currentHotSpot->getFeetY() -
							   scaleValue(_vm->_actor->getWalkerHeight(), _vm->_actor->getWalkerScaling(), 0);
					//_vm->_actor->placeWalkerSpriteAt(0, posX, posY);
					*/

					// Player said.... (for scene scripts)
					printf("Player said: %s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab());

					// FIXME: This should be moved somewhere else, and is incomplete
					if (_vm->_interfaceView->_inventory.getSelectedIndex() == -1) {
						if (_vm->_mouse->getVerb() == NULL) {
							strcpy(_vm->_player->verb, currentHotSpot->getVerb());
						} else {
							strcpy(_vm->_player->verb, _vm->_mouse->getVerb());
						}
					} else {
						strcpy(_vm->_player->verb, _vm->_interfaceView->_inventory.getSelectedObjectName());
					}
					strcpy(_vm->_player->noun, currentHotSpot->getVocab());
					strcpy(_vm->_player->object, "");
					_vm->_player->commandReady = true;

					printf("## Player said: %s %s\n", _vm->_player->verb, _vm->_player->noun);

				}
			}

			if (!_vm->isM4()) {
				HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y);
				if (currentHotSpot != NULL) {
					char statusText[50];
					if (currentHotSpot->getVerbID() != 0) {
						sprintf(statusText, "%s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab());
					} else {
						sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(kVerbWalkTo), currentHotSpot->getVocab());
					}

					statusText[0] = toupper(statusText[0]);	// capitalize first letter
					setMADSStatusText(statusText);
				}
			}
		}
		break;
	case MEVENT_RIGHT_CLICK:
		if (_vm->getGameType() == GType_Burger) {
			nextCommonCursor();
			_vm->_interfaceView->_inventory.clearSelected();
		} else {
			// ***DEBUG*** - sample dialog display
			int idx = 3; //_vm->_globals->messageIndexOf(0x277a);
			const char *msg = _vm->_globals->loadMessage(idx);
			Dialog *dlg = new Dialog(_vm, msg, "TEST DIALOG");
			_vm->_viewManager->addView(dlg);
			_vm->_viewManager->moveToFront(dlg);
		}
		break;
	case MEVENT_MOVE:
		if (_vm->isM4())
			checkHotspotAtMousePos(x, y);
		else
			checkHotspotAtMousePosMads(x, y);
		break;
	default:
		return false;
	}

	return true;
}

void Scene::nextCommonCursor() {
	int cursorIndex = _vm->_mouse->getCursorNum();

	switch (cursorIndex) {
	case CURSOR_ARROW:
		cursorIndex = CURSOR_LOOK;
		break;
	case CURSOR_LOOK:
		cursorIndex = CURSOR_TAKE;
		break;
	case CURSOR_TAKE:
		cursorIndex = CURSOR_USE;
		break;
	case CURSOR_USE:
		cursorIndex = CURSOR_ARROW;
		break;
	default:
		cursorIndex = CURSOR_ARROW;
	}

	_vm->_mouse->setCursorNum(cursorIndex);
}

enum boxSprites {
	topLeft = 0,
	topRight = 1,
	bottomLeft = 2,
	bottomRight = 3,
	left = 4,
	right = 5,
	top = 6,
	bottom = 7,
	topMiddle = 8,
	filler1 = 9,
	filler2 = 10
	// TODO: finish this
};

// TODO: calculate width and height, show text, show face if it exists
// TODO: this has been tested with Dragonsphere only, there are some differences
// in the sprites used in Phantom
void Scene::showMADSV2TextBox(char *text, int x, int y, char *faceName) {
	int repeatX = 40;	// FIXME: this is hardcoded
	int repeatY = 30;	// FIXME: this is hardcoded
	int curX = x, curY = y;
	int topRightX = x;	// TODO: this is probably not needed
	Common::SeekableReadStream *data = _vm->res()->get("box.ss");
	SpriteAsset *boxSprites = new SpriteAsset(_vm, data, data->size(), "box.ss");
	_vm->res()->toss("box.ss");

	RGBList *palData = new RGBList(boxSprites->getColorCount(), boxSprites->getPalette(), true);
	_vm->_palette->addRange(palData);

	for (int i = 0; i < boxSprites->getCount(); i++)
		boxSprites->getFrame(i)->translate(palData);		// sprite pixel translation

	// Top left corner
	boxSprites->getFrame(topLeft)->copyTo(_backgroundSurface, x, curY);
	curX += boxSprites->getFrame(topLeft)->width();

	// Top line
	for (int i = 0; i < repeatX; i++) {
		boxSprites->getFrame(top)->copyTo(_backgroundSurface, curX, curY + 3);
		curX += boxSprites->getFrame(top)->width();
	}

	// Top right corner
	boxSprites->getFrame(topRight)->copyTo(_backgroundSurface, curX, curY);
	topRightX = curX;

	// Top middle
	// FIXME: the transparent color for this is also the black border color
	boxSprites->getFrame(topMiddle)->copyTo(_backgroundSurface,
											x + (curX - x) / 2 - boxSprites->getFrame(topMiddle)->width() / 2,
											curY - 5, 167);
	curX = x;
	curY += boxSprites->getFrame(topLeft)->height();

	// -----------------------------------------------------------------------------------------------

	// Draw contents
	for (int i = 0; i < repeatY; i++) {
		for (int j = 0; j < repeatX; j++) {
			if (j == 0) {
				boxSprites->getFrame(left)->copyTo(_backgroundSurface, curX + 3, curY);
				curX += boxSprites->getFrame(left)->width();
			} else if (j == repeatX - 1) {
				curX = topRightX - 2;
				boxSprites->getFrame(right)->copyTo(_backgroundSurface, curX + 3, curY + 1);
			} else {
				// TODO: the background of the contents follows a pattern which is not understood yet
				if (j % 2 == 0) {
					boxSprites->getFrame(filler1)->copyTo(_backgroundSurface, curX + 3, curY);
					curX += boxSprites->getFrame(filler1)->width();
				} else {
					boxSprites->getFrame(filler2)->copyTo(_backgroundSurface, curX + 3, curY);
					curX += boxSprites->getFrame(filler2)->width();
				}
			}
		}	// for j
		curX = x;
		curY += boxSprites->getFrame(left)->height();
	}	// for i

	// -----------------------------------------------------------------------------------------------
	curX = x;

	// Bottom left corner
	boxSprites->getFrame(bottomLeft)->copyTo(_backgroundSurface, curX, curY);
	curX += boxSprites->getFrame(bottomLeft)->width();

	// Bottom line
	for (int i = 0; i < repeatX; i++) {
		boxSprites->getFrame(bottom)->copyTo(_backgroundSurface, curX, curY + 1);
		curX += boxSprites->getFrame(bottom)->width();
	}

	// Bottom right corner
	boxSprites->getFrame(bottomRight)->copyTo(_backgroundSurface, curX, curY + 1);
}

void Scene::setAction(int action, int objectId) {
	VALIDATE_MADS;
	char statusText[50];

	// TODO: Actually executing actions directly for objects. Also, some object actions are special in that
	// a second object can be selected, as in 'use gun to shoot person', with requires a target

	// Set up the new action
	strcpy(statusText, _vm->_globals->getVocab(action));
	statusText[0] = toupper(statusText[0]);	// capitalize first letter
	
	if (objectId != -1) {
		MadsObject *obj = _vm->_globals->getObject(objectId);
		sprintf(statusText + strlen(statusText), " %s", _vm->_globals->getVocab(obj->descId));
	} else {
		_currentAction = action;
	}

	setMADSStatusText(statusText);
}


/*--------------------------------------------------------------------------
 * MadsInterfaceView handles the user interface section at the bottom of
 * game screens in MADS games
 *--------------------------------------------------------------------------
 */

MadsInterfaceView::MadsInterfaceView(M4Engine *vm): View(vm, Common::Rect(0, MADS_SURFACE_HEIGHT,
														 vm->_screen->width(), vm->_screen->height())) {
	_screenType = VIEWID_INTERFACE;
	_highlightedElement = -1;
	_topIndex = 0;
	_selectedObject = -1;
	_cheatKeyCtr = 0;

	_objectSprites = NULL;
	_objectPalData = NULL;

	/* Set up the rect list for screen elements */
	// Actions
	for (int i = 0; i < 10; ++i)
		_screenObjects.addRect((i / 5) * 32 + 1, (i % 5) * 8 + MADS_SURFACE_HEIGHT + 2,
			((i / 5) + 1) * 32 + 3, ((i % 5) + 1) * 8 + MADS_SURFACE_HEIGHT + 2);
	
	// Scroller elements (up arrow, scroller, down arrow)
	_screenObjects.addRect(73, 160, 82, 167);
	_screenObjects.addRect(73, 168, 82, 190);
	_screenObjects.addRect(73, 191, 82, 198);

	// Inventory object names
	for (int i = 0; i < 5; ++i)
		_screenObjects.addRect(89, 158 + i * 8, 160, 166 + i * 8);
	
	// Full rectangle area for all vocab actions
	for (int i = 0; i < 5; ++i)
		_screenObjects.addRect(239, 158 + i * 8, 320, 166 + i * 8);
}

MadsInterfaceView::~MadsInterfaceView() {
	delete _objectSprites;
}

void MadsInterfaceView::setFontMode(InterfaceFontMode newMode) {
	switch (newMode) {
	case ITEM_NORMAL:
		_vm->_font->setColors(4, 4, 0xff);
		break;
	case ITEM_HIGHLIGHTED:
		_vm->_font->setColors(5, 5, 0xff);
		break;
	case ITEM_SELECTED:
		_vm->_font->setColors(6, 6, 0xff);
		break;
	}
}

void MadsInterfaceView::initialise() {
	// Build up the inventory list
	_inventoryList.clear();

	for (uint i = 0; i < _vm->_globals->getObjectsSize(); ++i) {
		MadsObject *obj = _vm->_globals->getObject(i);
		if (obj->roomNumber == PLAYER_INVENTORY)
			_inventoryList.push_back(i);
	}

	// If the inventory has at least one object, select it
	if (_inventoryList.size() > 0)
		setSelectedObject(_inventoryList[0]);
}

void MadsInterfaceView::setSelectedObject(int objectNumber) {
	char resName[80];

	// Load inventory resource
	if (_objectSprites) {
		_vm->_palette->deleteRange(_objectPalData);
		delete _objectSprites;
	}

	// Check to make sure the object is in the inventory, and also visible on-screen
	int idx = _inventoryList.indexOf(objectNumber);
	if (idx == -1) {
		// Object wasn't found, so return
		_selectedObject = -1;
		return;
	}

	// Found the object
	if (idx < _topIndex)
		_topIndex = idx;
	else if (idx >= (_topIndex + 5))
		_topIndex = MAX(0, idx - 4);
	
	_selectedObject = objectNumber;
	sprintf(resName, "*OB%.3dI.SS", objectNumber);

	Common::SeekableReadStream *data = _vm->res()->get(resName);
	_objectSprites = new SpriteAsset(_vm, data, data->size(), resName);
	_vm->res()->toss(resName);

	// Slot it into available palette space
	_objectPalData = _objectSprites->getRgbList();
	_vm->_palette->addRange(_objectPalData);
	_objectSprites->translate(_objectPalData, true);

	_objectFrameNumber = 0;
}

void MadsInterfaceView::addObjectToInventory(int objectNumber) {
	if (_inventoryList.indexOf(objectNumber) == -1) {
		_vm->_globals->getObject(objectNumber)->roomNumber = PLAYER_INVENTORY;
		_inventoryList.push_back(objectNumber);
	}

	setSelectedObject(objectNumber);
}

void MadsInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) {
	_vm->_font->setFont(FONT_INTERFACE_MADS);
	char buffer[100];

	// Check to see if any dialog is currently active
	bool dialogVisible = _vm->_viewManager->getView(LAYER_DIALOG) != NULL;

	// Highlighting logic for action list
	int actionIndex = 0;
	for (int x = 0; x < 2; ++x) {
		for (int y = 0; y < 5; ++y, ++actionIndex) {
			// Determine the font colour depending on whether an item is selected. Note that the first action,
			// 'Look', is always 'selected', even when another action is clicked on
			setFontMode((_highlightedElement == actionIndex) ? ITEM_HIGHLIGHTED : 
				((actionIndex == 0) ? ITEM_SELECTED : ITEM_NORMAL));

			// Get the verb action and capitalise it
			const char *verbStr = _vm->_globals->getVocab(kVerbLook + actionIndex);
			strcpy(buffer, verbStr);
			if ((buffer[0] >= 'a') && (buffer[0] <= 'z')) buffer[0] -= 'a' - 'A';

			// Display the verb
			const Common::Rect r(_screenObjects[actionIndex]);
			_vm->_font->writeString(destSurface, buffer, r.left, r.top, r.width(), 0);
		}
	}

	// Check for highlighting of the scrollbar controls
	if ((_highlightedElement == SCROLL_UP) || (_highlightedElement == SCROLL_SCROLLER) || (_highlightedElement == SCROLL_DOWN)) {
		// Highlight the control's borders
		const Common::Rect r(_screenObjects[_highlightedElement]);
		destSurface->frameRect(r, 5);
	}

	// Draw the horizontal line in the scroller representing the current top selected 
	const Common::Rect scroller(_screenObjects[SCROLL_SCROLLER]);
	int yP = (_inventoryList.size() < 2) ? 0 : (scroller.height() - 5) * _topIndex / (_inventoryList.size() - 1);
	destSurface->setColor(4);
	destSurface->hLine(scroller.left + 2, scroller.right - 3, scroller.top + 2 + yP);

	// List inventory items
	for (uint i = 0; i < 5; ++i) {
		if ((_topIndex + i) >= _inventoryList.size())
			break;

		const char *descStr = _vm->_globals->getVocab(_vm->_globals->getObject(
			_inventoryList[_topIndex + i])->descId);
		strcpy(buffer, descStr);
		if ((buffer[0] >= 'a') && (buffer[0] <= 'z')) buffer[0] -= 'a' - 'A';

		const Common::Rect r(_screenObjects[INVLIST_START + i]);
	
		// Set the highlighting of the inventory item
		if (_highlightedElement == (int)(INVLIST_START + i)) setFontMode(ITEM_HIGHLIGHTED);
		else if (_selectedObject == _inventoryList[_topIndex + i]) setFontMode(ITEM_SELECTED);
		else setFontMode(ITEM_NORMAL);

		// Write out it's description
		_vm->_font->writeString(destSurface, buffer, r.left, r.top, r.width(), 0);
	}

	// Handle the display of any currently selected object
	if (_objectSprites) {
		// Display object sprite. Note that the frame number isn't used directly, because it would result
		// in too fast an animation
		M4Sprite *spr = _objectSprites->getFrame(_objectFrameNumber / INV_ANIM_FRAME_SPEED);
		spr->copyTo(destSurface, INVENTORY_X, INVENTORY_Y, 0);

		if (!_vm->_globals->invObjectsStill && !dialogVisible) {
			// If objetcs are to animated, move to the next frame
			if (++_objectFrameNumber >= (_objectSprites->getCount() * INV_ANIM_FRAME_SPEED))
				_objectFrameNumber = 0;
		}

		// List the vocab actions for the currently selected object
		MadsObject *obj = _vm->_globals->getObject(_selectedObject);
		int yIndex = MIN(_highlightedElement - VOCAB_START, obj->vocabCount - 1);

		for (int i = 0; i < obj->vocabCount; ++i) {
			const Common::Rect r(_screenObjects[VOCAB_START + i]);

			// Get the vocab description and capitalise it
			const char *descStr = _vm->_globals->getVocab(obj->vocabList[i].vocabId);
			strcpy(buffer, descStr);
			if ((buffer[0] >= 'a') && (buffer[0] <= 'z')) buffer[0] -= 'a' - 'A';
		
			// Set the highlighting and display the entry
			setFontMode((i == yIndex) ? ITEM_HIGHLIGHTED : ITEM_NORMAL);
			_vm->_font->writeString(destSurface, buffer, r.left, r.top, r.width(), 0);
		}
	}
}

bool MadsInterfaceView::onEvent(M4EventType eventType, int32 param1, int x, int y, bool &captureEvents) {
	// If the mouse isn't being held down, then reset the repeated scroll timer
	if (eventType != MEVENT_LEFT_HOLD)
		_nextScrollerTicks = 0;

	// Handle various event types
	switch (eventType) {
	case MEVENT_MOVE:
		// If the cursor isn't in "wait mode", don't do any processing
		if (_vm->_mouse->getCursorNum() == CURSOR_WAIT)
			return true;

		// Ensure the cursor is the standard arrow
		_vm->_mouse->setCursorNum(CURSOR_ARROW);

		// Check if any interface element is currently highlighted
		_highlightedElement = _screenObjects.find(Common::Point(x, y));

		return true;
	
	case MEVENT_LEFT_CLICK:
		// Left mouse click
		// Check if an inventory object was selected
		if ((_highlightedElement >= INVLIST_START) && (_highlightedElement < (INVLIST_START + 5))) {
			// Ensure there is an inventory item listed in that cell
			uint idx = _highlightedElement - INVLIST_START;
			if ((_topIndex + idx) < _inventoryList.size()) {
				// Set the selected object
				setSelectedObject(_inventoryList[_topIndex + idx]);
			}
		} else if ((_highlightedElement >= ACTIONS_START) && (_highlightedElement < (ACTIONS_START + 10))) {
			// A standard action was selected
			_vm->_scene->setAction(kVerbLook + (_highlightedElement - ACTIONS_START));
		} else if ((_highlightedElement >= VOCAB_START) && (_highlightedElement < (VOCAB_START + 5))) {
			// A vocab action was selected
			MadsObject *obj = _vm->_globals->getObject(_selectedObject);
			int vocabIndex = MIN(_highlightedElement - VOCAB_START, obj->vocabCount - 1);
			if (vocabIndex >= 0)
				_vm->_scene->setAction(obj->vocabList[vocabIndex].vocabId, _selectedObject);
		}

		return true;

	case MEVENT_LEFT_HOLD:
		// Left mouse hold
		// Handle the scroller - the up/down buttons allow for multiple actions whilst the mouse is held down
		if ((_highlightedElement == SCROLL_UP) || (_highlightedElement == SCROLL_DOWN)) {
			if ((_nextScrollerTicks == 0) || (g_system->getMillis() >= _nextScrollerTicks)) {
				// Handle scroll up/down action
				_nextScrollerTicks = g_system->getMillis() + SCROLLER_DELAY;

				if ((_highlightedElement == SCROLL_UP) && (_topIndex > 0))
					--_topIndex;
				if ((_highlightedElement == SCROLL_DOWN) && (_topIndex < (int)(_inventoryList.size() - 1)))
					++_topIndex;
			}
		}
		return true;

	case MEVENT_LEFT_DRAG:
		// Left mouse drag
		// Handle the the the scroller area that can be dragged to adjust the top displayed index
		if (_highlightedElement == SCROLL_SCROLLER) {
			// Calculate the new top index based on the Y position
			const Common::Rect r(_screenObjects[SCROLL_SCROLLER]);
			_topIndex = CLIP((int)(_inventoryList.size() - 1) * (y - r.top - 2) / (r.height() - 5),
				0, (int)_inventoryList.size() - 1);
		}
		return true;

	case KEVENT_KEY:
		if (_cheatKeyCtr == CHEAT_SEQUENCE_MAX)
			handleCheatKey(param1);
		handleKeypress(param1);
		return true;

	default:
		break;
	}

	return false;
}

bool MadsInterfaceView::handleCheatKey(int32 keycode) {
	switch (keycode) {
	case Common::KEYCODE_SPACE:
		// TODO: Move player to current destination
		return true;

	case Common::KEYCODE_t | (Common::KEYCODE_LALT):
	case Common::KEYCODE_t | (Common::KEYCODE_RALT):
	{
		// Teleport to room
		//Scene *sceneView = (Scene *)vm->_viewManager->getView(VIEWID_SCENE);
		

		return true;
	}

	default:
		break;
	}

	return false;
}

const char *CHEAT_SEQUENCE = "widepipe";

bool MadsInterfaceView::handleKeypress(int32 keycode) {
	int flags = keycode >> 24;
	int kc = keycode & 0xffff;

	// Capitalise the letter if necessary
	if (_cheatKeyCtr < CHEAT_SEQUENCE_MAX) {
		if ((flags == Common::KBD_CTRL) && (kc == CHEAT_SEQUENCE[_cheatKeyCtr])) {
			++_cheatKeyCtr;
			if (_cheatKeyCtr == CHEAT_SEQUENCE_MAX)
				Dialog::display(_vm, 22, cheatingEnabledDesc);
			return true;
		} else {
			_cheatKeyCtr = 0;
		}
	}

	return false;
}


} // End of namespace M4
