/* 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 "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/text/text.h"

#include "zvision/file/lzss_read_stream.h"

#include "common/file.h"
#include "common/system.h"
#include "common/stream.h"

#include "engines/util.h"

#include "image/tga.h"

namespace ZVision {

RenderManager::RenderManager(ZVision *engine, uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow, const Graphics::PixelFormat pixelFormat, bool doubleFPS)
	: _engine(engine),
	  _system(engine->_system),
	  _screenCenterX(_workingWindow.width() / 2),
	  _screenCenterY(_workingWindow.height() / 2),
	  _workingWindow(workingWindow),
	  _pixelFormat(pixelFormat),
	  _backgroundWidth(0),
	  _backgroundHeight(0),
	  _backgroundOffset(0),
	  _renderTable(_workingWindow.width(), _workingWindow.height()),
	  _doubleFPS(doubleFPS),
	  _subid(0) {

	_backgroundSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
	_effectSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
	_warpedSceneSurface.create(_workingWindow.width(), _workingWindow.height(), _pixelFormat);
	_menuSurface.create(windowWidth, workingWindow.top, _pixelFormat);

	_menuArea = Common::Rect(0, 0, windowWidth, workingWindow.top);

	initSubArea(windowWidth, windowHeight, workingWindow);
}

RenderManager::~RenderManager() {
	_currentBackgroundImage.free();
	_backgroundSurface.free();
	_effectSurface.free();
	_warpedSceneSurface.free();
	_menuSurface.free();
	_subtitleSurface.free();
}

void RenderManager::renderSceneToScreen() {
	Graphics::Surface *out = &_warpedSceneSurface;
	Graphics::Surface *in = &_backgroundSurface;
	Common::Rect outWndDirtyRect;

	// If we have graphical effects, we apply them using a temporary buffer
	if (!_effects.empty()) {
		bool copied = false;
		Common::Rect windowRect(_workingWindow.width(), _workingWindow.height());

		for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) {
			Common::Rect rect = (*it)->getRegion();
			Common::Rect screenSpaceLocation = rect;

			if ((*it)->isPort()) {
				screenSpaceLocation = transformBackgroundSpaceRectToScreenSpace(screenSpaceLocation);
			}

			if (windowRect.intersects(screenSpaceLocation)) {
				if (!copied) {
					copied = true;
					_effectSurface.copyFrom(_backgroundSurface);
					in = &_effectSurface;
				}
				const Graphics::Surface *post;
				if ((*it)->isPort())
					post = (*it)->draw(_currentBackgroundImage.getSubArea(rect));
				else
					post = (*it)->draw(_effectSurface.getSubArea(rect));
				Common::Rect empty;
				blitSurfaceToSurface(*post, empty, _effectSurface, screenSpaceLocation.left, screenSpaceLocation.top);
				screenSpaceLocation.clip(windowRect);
				if (_backgroundSurfaceDirtyRect .isEmpty()) {
					_backgroundSurfaceDirtyRect = screenSpaceLocation;
				} else {
					_backgroundSurfaceDirtyRect.extend(screenSpaceLocation);
				}
			}
		}
	}

	RenderTable::RenderState state = _renderTable.getRenderState();
	if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
		if (!_backgroundSurfaceDirtyRect.isEmpty()) {
			_renderTable.mutateImage(&_warpedSceneSurface, in);
			out = &_warpedSceneSurface;
			outWndDirtyRect = Common::Rect(_workingWindow.width(), _workingWindow.height());
		}
	} else {
		out = in;
		outWndDirtyRect = _backgroundSurfaceDirtyRect;
	}

	if (!outWndDirtyRect.isEmpty()) {
		Common::Rect rect(
			outWndDirtyRect.left + _workingWindow.left,
			outWndDirtyRect.top + _workingWindow.top,
			outWndDirtyRect.left + _workingWindow.left + outWndDirtyRect.width(),
			outWndDirtyRect.top + _workingWindow.top + outWndDirtyRect.height()
		);
		copyToScreen(*out, rect, outWndDirtyRect.left, outWndDirtyRect.top);
	}
}

void RenderManager::copyToScreen(const Graphics::Surface &surface, Common::Rect &rect, int16 srcLeft, int16 srcTop) {
	// Convert the surface to RGB565, if needed
	Graphics::Surface *outSurface = surface.convertTo(_engine->_screenPixelFormat);
	_system->copyRectToScreen(outSurface->getBasePtr(srcLeft, srcTop),
		                        outSurface->pitch,
		                        rect.left,
		                        rect.top,
		                        rect.width(),
		                        rect.height());
	outSurface->free();
	delete outSurface;
}

void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY) {
	Graphics::Surface surface;
	readImageToSurface(fileName, surface);

	blitSurfaceToBkg(surface, destX, destY);
	surface.free();
}

void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, uint32 keycolor) {
	Graphics::Surface surface;
	readImageToSurface(fileName, surface);

	blitSurfaceToBkg(surface, destX, destY, keycolor);
	surface.free();
}

void RenderManager::renderImageToBackground(const Common::String &fileName, int16 destX, int16 destY, int16  keyX, int16 keyY) {
	Graphics::Surface surface;
	readImageToSurface(fileName, surface);

	uint16 keycolor = *(uint16 *)surface.getBasePtr(keyX, keyY);

	blitSurfaceToBkg(surface, destX, destY, keycolor);
	surface.free();
}

void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination) {
	bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA;
	readImageToSurface(fileName, destination, isTransposed);
}

void RenderManager::readImageToSurface(const Common::String &fileName, Graphics::Surface &destination, bool transposed) {
	Common::File file;

	if (!_engine->getSearchManager()->openFile(file, fileName)) {
		warning("Could not open file %s", fileName.c_str());
		return;
	}

	// Read the magic number
	// Some files are true TGA, while others are TGZ
	uint32 fileType = file.readUint32BE();

	uint32 imageWidth;
	uint32 imageHeight;
	Image::TGADecoder tga;
	uint16 *buffer;
	// All Z-Vision images are in RGB 555
	destination.format = _engine->_resourcePixelFormat;

	bool isTGZ;

	// Check for TGZ files
	if (fileType == MKTAG('T', 'G', 'Z', '\0')) {
		isTGZ = true;

		// TGZ files have a header and then Bitmap data that is compressed with LZSS
		uint32 decompressedSize = file.readSint32LE() / 2;
		imageWidth = file.readSint32LE();
		imageHeight = file.readSint32LE();

		LzssReadStream lzssStream(&file);
		buffer = (uint16 *)(new uint16[decompressedSize]);
		lzssStream.read(buffer, 2 * decompressedSize);
#ifndef SCUMM_LITTLE_ENDIAN
		for (uint32 i = 0; i < decompressedSize; ++i)
			buffer[i] = FROM_LE_16(buffer[i]);
#endif
	} else {
		isTGZ = false;

		// Reset the cursor
		file.seek(0);

		// Decode
		if (!tga.loadStream(file)) {
			warning("Error while reading TGA image");
			return;
		}

		Graphics::Surface tgaSurface = *(tga.getSurface());
		imageWidth = tgaSurface.w;
		imageHeight = tgaSurface.h;

		buffer = (uint16 *)tgaSurface.getPixels();
	}

	// Flip the width and height if transposed
	if (transposed) {
		uint16 temp = imageHeight;
		imageHeight = imageWidth;
		imageWidth = temp;
	}

	// If the destination internal buffer is the same size as what we're copying into it,
	// there is no need to free() and re-create
	if (imageWidth != destination.w || imageHeight != destination.h) {
		destination.create(imageWidth, imageHeight, _engine->_resourcePixelFormat);
	}

	// If transposed, 'un-transpose' the data while copying it to the destination
	// Otherwise, just do a simple copy
	if (transposed) {
		uint16 *dest = (uint16 *)destination.getPixels();

		for (uint32 y = 0; y < imageHeight; ++y) {
			uint32 columnIndex = y * imageWidth;

			for (uint32 x = 0; x < imageWidth; ++x) {
				dest[columnIndex + x] = buffer[x * imageHeight + y];
			}
		}
	} else {
		memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * destination.format.bytesPerPixel);
	}

	// Cleanup
	if (isTGZ) {
		delete[] buffer;
	} else {
		tga.destroy();
	}
}

const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) {
	if (_workingWindow.contains(point)) {
		// Convert from screen space to working window space
		Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top));

		RenderTable::RenderState state = _renderTable.getRenderState();
		if (state == RenderTable::PANORAMA || state == RenderTable::TILT) {
			newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint);
		}

		if (state == RenderTable::PANORAMA) {
			newPoint += (Common::Point(_backgroundOffset - _screenCenterX, 0));
		} else if (state == RenderTable::TILT) {
			newPoint += (Common::Point(0, _backgroundOffset - _screenCenterY));
		}

		if (_backgroundWidth)
			newPoint.x %= _backgroundWidth;
		if (_backgroundHeight)
			newPoint.y %= _backgroundHeight;

		if (newPoint.x < 0)
			newPoint.x += _backgroundWidth;
		if (newPoint.y < 0)
			newPoint.y += _backgroundHeight;

		return newPoint;
	} else {
		return Common::Point(0, 0);
	}
}

RenderTable *RenderManager::getRenderTable() {
	return &_renderTable;
}

void RenderManager::setBackgroundImage(const Common::String &fileName) {
	readImageToSurface(fileName, _currentBackgroundImage);
	_backgroundWidth = _currentBackgroundImage.w;
	_backgroundHeight = _currentBackgroundImage.h;
	_backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight);
}

void RenderManager::setBackgroundPosition(int offset) {
	RenderTable::RenderState state = _renderTable.getRenderState();
	if (state == RenderTable::TILT || state == RenderTable::PANORAMA)
		if (_backgroundOffset != offset)
			_backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight);
	_backgroundOffset = offset;

	_engine->getScriptManager()->setStateValue(StateKey_ViewPos, offset);
}

uint32 RenderManager::getCurrentBackgroundOffset() {
	RenderTable::RenderState state = _renderTable.getRenderState();

	if (state == RenderTable::PANORAMA) {
		return _backgroundOffset;
	} else if (state == RenderTable::TILT) {
		return _backgroundOffset;
	} else {
		return 0;
	}
}

Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) {
	Graphics::Surface *tranposedSurface = new Graphics::Surface();
	tranposedSurface->create(surface->h, surface->w, surface->format);

	const uint16 *source = (const uint16 *)surface->getPixels();
	uint16 *dest = (uint16 *)tranposedSurface->getPixels();

	for (uint32 y = 0; y < tranposedSurface->h; ++y) {
		uint32 columnIndex = y * tranposedSurface->w;

		for (uint32 x = 0; x < tranposedSurface->w; ++x) {
			dest[columnIndex + x] = source[x * surface->w + y];
		}
	}

	return tranposedSurface;
}

void RenderManager::scaleBuffer(const void *src, void *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint32 dstWidth, uint32 dstHeight) {
	assert(bytesPerPixel == 1 || bytesPerPixel == 2);

	const float  xscale = (float)srcWidth / (float)dstWidth;
	const float  yscale = (float)srcHeight / (float)dstHeight;

	if (bytesPerPixel == 1) {
		const byte *srcPtr = (const byte *)src;
		byte *dstPtr = (byte *)dst;
		for (uint32 y = 0; y < dstHeight; ++y) {
			for (uint32 x = 0; x < dstWidth; ++x) {
				*dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth];
				dstPtr++;
			}
		}
	} else if (bytesPerPixel == 2) {
		const uint16 *srcPtr = (const uint16 *)src;
		uint16 *dstPtr = (uint16 *)dst;
		for (uint32 y = 0; y < dstHeight; ++y) {
			for (uint32 x = 0; x < dstWidth; ++x) {
				*dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth];
				dstPtr++;
			}
		}
	}
}

void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int _x, int _y) {
	Common::Rect srcRect = _srcRect;
	if (srcRect.isEmpty())
		srcRect = Common::Rect(src.w, src.h);
	srcRect.clip(src.w, src.h);
	Common::Rect dstRect = Common::Rect(-_x + srcRect.left , -_y + srcRect.top, -_x + srcRect.left + dst.w, -_y + srcRect.top + dst.h);
	srcRect.clip(dstRect);

	if (srcRect.isEmpty() || !srcRect.isValidRect())
		return;

	Graphics::Surface *srcAdapted = src.convertTo(dst.format);

	// Copy srcRect from src surface to dst surface
	const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top);

	int xx = _x;
	int yy = _y;

	if (xx < 0)
		xx = 0;
	if (yy < 0)
		yy = 0;

	if (_x >= dst.w || _y >= dst.h) {
		srcAdapted->free();
		delete srcAdapted;
		return;
	}

	byte *dstBuffer = (byte *)dst.getBasePtr(xx, yy);

	int32 w = srcRect.width();
	int32 h = srcRect.height();

	for (int32 y = 0; y < h; y++) {
		memcpy(dstBuffer, srcBuffer, w * srcAdapted->format.bytesPerPixel);
		srcBuffer += srcAdapted->pitch;
		dstBuffer += dst.pitch;
	}

	srcAdapted->free();
	delete srcAdapted;
}

void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, const Common::Rect &_srcRect , Graphics::Surface &dst, int _x, int _y, uint32 colorkey) {
	Common::Rect srcRect = _srcRect;
	if (srcRect.isEmpty())
		srcRect = Common::Rect(src.w, src.h);
	srcRect.clip(src.w, src.h);
	Common::Rect dstRect = Common::Rect(-_x + srcRect.left , -_y + srcRect.top, -_x + srcRect.left + dst.w, -_y + srcRect.top + dst.h);
	srcRect.clip(dstRect);

	if (srcRect.isEmpty() || !srcRect.isValidRect())
		return;

	Graphics::Surface *srcAdapted = src.convertTo(dst.format);
	uint32 keycolor = colorkey & ((1 << (src.format.bytesPerPixel << 3)) - 1);

	// Copy srcRect from src surface to dst surface
	const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top);

	int xx = _x;
	int yy = _y;

	if (xx < 0)
		xx = 0;
	if (yy < 0)
		yy = 0;

	if (_x >= dst.w || _y >= dst.h) {
		srcAdapted->free();
		delete srcAdapted;
		return;
	}

	byte *dstBuffer = (byte *)dst.getBasePtr(xx, yy);

	int32 w = srcRect.width();
	int32 h = srcRect.height();

	for (int32 y = 0; y < h; y++) {
		switch (srcAdapted->format.bytesPerPixel) {
		case 1: {
			const uint *srcTemp = (const uint *)srcBuffer;
			uint *dstTemp = (uint *)dstBuffer;
			for (int32 x = 0; x < w; x++) {
				if (*srcTemp != keycolor)
					*dstTemp = *srcTemp;
				srcTemp++;
				dstTemp++;
			}
		}
		break;

		case 2: {
			const uint16 *srcTemp = (const uint16 *)srcBuffer;
			uint16 *dstTemp = (uint16 *)dstBuffer;
			for (int32 x = 0; x < w; x++) {
				if (*srcTemp != keycolor)
					*dstTemp = *srcTemp;
				srcTemp++;
				dstTemp++;
			}
		}
		break;

		case 4: {
			const uint32 *srcTemp = (const uint32 *)srcBuffer;
			uint32 *dstTemp = (uint32 *)dstBuffer;
			for (int32 x = 0; x < w; x++) {
				if (*srcTemp != keycolor)
					*dstTemp = *srcTemp;
				srcTemp++;
				dstTemp++;
			}
		}
		break;

		default:
			break;
		}
		srcBuffer += srcAdapted->pitch;
		dstBuffer += dst.pitch;
	}

	srcAdapted->free();
	delete srcAdapted;
}

void RenderManager::blitSurfaceToBkg(const Graphics::Surface &src, int x, int y, int32 colorkey) {
	Common::Rect empt;
	if (colorkey >= 0)
		blitSurfaceToSurface(src, empt, _currentBackgroundImage, x, y, colorkey);
	else
		blitSurfaceToSurface(src, empt, _currentBackgroundImage, x, y);
	Common::Rect dirty(src.w, src.h);
	dirty.translate(x, y);
	if (_backgroundDirtyRect.isEmpty())
		_backgroundDirtyRect = dirty;
	else
		_backgroundDirtyRect.extend(dirty);
}

void RenderManager::blitSurfaceToBkgScaled(const Graphics::Surface &src, const Common::Rect &_dstRect, int32 colorkey) {
	if (src.w == _dstRect.width() && src.h == _dstRect.height()) {
		blitSurfaceToBkg(src, _dstRect.left, _dstRect.top, colorkey);
	} else {
		Graphics::Surface *tmp = new Graphics::Surface;
		tmp->create(_dstRect.width(), _dstRect.height(), src.format);
		scaleBuffer(src.getPixels(), tmp->getPixels(), src.w, src.h, src.format.bytesPerPixel, _dstRect.width(), _dstRect.height());
		blitSurfaceToBkg(*tmp, _dstRect.left, _dstRect.top, colorkey);
		tmp->free();
		delete tmp;
	}
}

void RenderManager::blitSurfaceToMenu(const Graphics::Surface &src, int x, int y, int32 colorkey) {
	Common::Rect empt;
	if (colorkey >= 0)
		blitSurfaceToSurface(src, empt, _menuSurface, x, y, colorkey);
	else
		blitSurfaceToSurface(src, empt, _menuSurface, x, y);
	Common::Rect dirty(src.w, src.h);
	dirty.translate(x, y);
	if (_menuSurfaceDirtyRect.isEmpty())
		_menuSurfaceDirtyRect = dirty;
	else
		_menuSurfaceDirtyRect.extend(dirty);
}

Graphics::Surface *RenderManager::getBkgRect(Common::Rect &rect) {
	Common::Rect dst = rect;
	dst.clip(_backgroundWidth, _backgroundHeight);

	if (dst.isEmpty() || !dst.isValidRect())
		return NULL;

	Graphics::Surface *srf = new Graphics::Surface;
	srf->create(dst.width(), dst.height(), _currentBackgroundImage.format);

	srf->copyRectToSurface(_currentBackgroundImage, 0, 0, Common::Rect(dst));

	return srf;
}

Graphics::Surface *RenderManager::loadImage(Common::String file) {
	Graphics::Surface *tmp = new Graphics::Surface;
	readImageToSurface(file, *tmp);
	return tmp;
}

Graphics::Surface *RenderManager::loadImage(Common::String file, bool transposed) {
	Graphics::Surface *tmp = new Graphics::Surface;
	readImageToSurface(file, *tmp, transposed);
	return tmp;
}

void RenderManager::prepareBackground() {
	_backgroundDirtyRect.clip(_backgroundWidth, _backgroundHeight);
	RenderTable::RenderState state = _renderTable.getRenderState();

	if (state == RenderTable::PANORAMA) {
		// Calculate the visible portion of the background
		Common::Rect viewPort(_workingWindow.width(), _workingWindow.height());
		viewPort.translate(-(_screenCenterX - _backgroundOffset), 0);
		Common::Rect drawRect = _backgroundDirtyRect;
		drawRect.clip(viewPort);

		// Render the visible portion
		if (!drawRect.isEmpty()) {
			blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX - _backgroundOffset + drawRect.left, drawRect.top);
		}

		// Mark the dirty portion of the surface
		_backgroundSurfaceDirtyRect = _backgroundDirtyRect;
		_backgroundSurfaceDirtyRect.translate(_screenCenterX - _backgroundOffset, 0);

		// Panorama mode allows the user to spin in circles. Therefore, we need to render
		// the portion of the image that wrapped to the other side of the screen
		if (_backgroundOffset < _screenCenterX) {
			viewPort.moveTo(-(_screenCenterX - (_backgroundOffset + _backgroundWidth)), 0);
			drawRect = _backgroundDirtyRect;
			drawRect.clip(viewPort);

			if (!drawRect.isEmpty())
				blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX - (_backgroundOffset + _backgroundWidth) + drawRect.left, drawRect.top);

			Common::Rect tmp = _backgroundDirtyRect;
			tmp.translate(_screenCenterX - (_backgroundOffset + _backgroundWidth), 0);
			if (!tmp.isEmpty())
				_backgroundSurfaceDirtyRect.extend(tmp);

		} else if (_backgroundWidth - _backgroundOffset < _screenCenterX) {
			viewPort.moveTo(-(_screenCenterX + _backgroundWidth - _backgroundOffset), 0);
			drawRect = _backgroundDirtyRect;
			drawRect.clip(viewPort);

			if (!drawRect.isEmpty())
				blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _screenCenterX + _backgroundWidth - _backgroundOffset + drawRect.left, drawRect.top);

			Common::Rect tmp = _backgroundDirtyRect;
			tmp.translate(_screenCenterX + _backgroundWidth - _backgroundOffset, 0);
			if (!tmp.isEmpty())
				_backgroundSurfaceDirtyRect.extend(tmp);

		}
	} else if (state == RenderTable::TILT) {
		// Tilt doesn't allow wrapping, so we just do a simple clip
		Common::Rect viewPort(_workingWindow.width(), _workingWindow.height());
		viewPort.translate(0, -(_screenCenterY - _backgroundOffset));
		Common::Rect drawRect = _backgroundDirtyRect;
		drawRect.clip(viewPort);
		if (!drawRect.isEmpty())
			blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, drawRect.left, _screenCenterY - _backgroundOffset + drawRect.top);

		// Mark the dirty portion of the surface
		_backgroundSurfaceDirtyRect = _backgroundDirtyRect;
		_backgroundSurfaceDirtyRect.translate(0, _screenCenterY - _backgroundOffset);

	} else {
		if (!_backgroundDirtyRect.isEmpty())
			blitSurfaceToSurface(_currentBackgroundImage, _backgroundDirtyRect, _backgroundSurface, _backgroundDirtyRect.left, _backgroundDirtyRect.top);
		_backgroundSurfaceDirtyRect = _backgroundDirtyRect;
	}

	// Clear the dirty rect since everything is clean now
	_backgroundDirtyRect = Common::Rect();

	_backgroundSurfaceDirtyRect.clip(_workingWindow.width(), _workingWindow.height());
}

void RenderManager::clearMenuSurface() {
	_menuSurfaceDirtyRect = Common::Rect(0, 0, _menuSurface.w, _menuSurface.h);
	_menuSurface.fillRect(_menuSurfaceDirtyRect, 0);
}

void RenderManager::clearMenuSurface(const Common::Rect &r) {
	if (_menuSurfaceDirtyRect.isEmpty())
		_menuSurfaceDirtyRect = r;
	else
		_menuSurfaceDirtyRect.extend(r);
	_menuSurface.fillRect(r, 0);
}

void RenderManager::renderMenuToScreen() {
	if (!_menuSurfaceDirtyRect.isEmpty()) {
		_menuSurfaceDirtyRect.clip(Common::Rect(_menuSurface.w, _menuSurface.h));
		if (!_menuSurfaceDirtyRect.isEmpty()) {
			Common::Rect rect(
				_menuSurfaceDirtyRect.left + _menuArea.left,
				_menuSurfaceDirtyRect.top + _menuArea.top,
				_menuSurfaceDirtyRect.left + _menuArea.left + _menuSurfaceDirtyRect.width(),
				_menuSurfaceDirtyRect.top + _menuArea.top + _menuSurfaceDirtyRect.height()
			);
			copyToScreen(_menuSurface, rect, _menuSurfaceDirtyRect.left, _menuSurfaceDirtyRect.top);
		}
		_menuSurfaceDirtyRect = Common::Rect();
	}
}

void RenderManager::initSubArea(uint32 windowWidth, uint32 windowHeight, const Common::Rect workingWindow) {
	_workingWindow = workingWindow;

	_subtitleSurface.free();

	_subtitleSurface.create(windowWidth, windowHeight - workingWindow.bottom, _pixelFormat);
	_subtitleArea = Common::Rect(0, workingWindow.bottom, windowWidth, windowHeight);
}

uint16 RenderManager::createSubArea(const Common::Rect &area) {
	_subid++;

	OneSubtitle sub;
	sub.redraw = false;
	sub.timer = -1;
	sub.todelete = false;
	sub.r = area;

	_subsList[_subid] = sub;

	return _subid;
}

uint16 RenderManager::createSubArea() {
	Common::Rect r(_subtitleArea.left, _subtitleArea.top, _subtitleArea.right, _subtitleArea.bottom);
	r.translate(-_workingWindow.left, -_workingWindow.top);
	return createSubArea(r);
}

void RenderManager::deleteSubArea(uint16 id) {
	if (_subsList.contains(id))
		_subsList[id].todelete = true;
}

void RenderManager::deleteSubArea(uint16 id, int16 delay) {
	if (_subsList.contains(id))
		_subsList[id].timer = delay;
}

void RenderManager::updateSubArea(uint16 id, const Common::String &txt) {
	if (_subsList.contains(id)) {
		OneSubtitle *sub = &_subsList[id];
		sub->txt = txt;
		sub->redraw = true;
	}
}

void RenderManager::processSubs(uint16 deltatime) {
	bool redraw = false;
	for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) {
		if (it->_value.timer != -1) {
			it->_value.timer -= deltatime;
			if (it->_value.timer <= 0)
				it->_value.todelete = true;
		}
		if (it->_value.todelete) {
			_subsList.erase(it);
			redraw = true;
		} else if (it->_value.redraw) {
			redraw = true;
		}
	}

	if (redraw) {
		_subtitleSurface.fillRect(Common::Rect(_subtitleSurface.w, _subtitleSurface.h), 0);

		for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) {
			OneSubtitle *sub = &it->_value;
			if (sub->txt.size()) {
				Graphics::Surface subtitleSurface;
				subtitleSurface.create(sub->r.width(), sub->r.height(), _engine->_resourcePixelFormat);
				_engine->getTextRenderer()->drawTextWithWordWrapping(sub->txt, subtitleSurface);
				Common::Rect empty;
				blitSurfaceToSurface(subtitleSurface, empty, _subtitleSurface, sub->r.left - _subtitleArea.left + _workingWindow.left, sub->r.top - _subtitleArea.top + _workingWindow.top);
				subtitleSurface.free();
			}
			sub->redraw = false;
		}

		Common::Rect rect(
			_subtitleArea.left,
			_subtitleArea.top,
			_subtitleArea.left + _subtitleSurface.w,
			_subtitleArea.top + _subtitleSurface.h
		);
		copyToScreen(_subtitleSurface, rect, 0, 0);
	}
}

Common::Point RenderManager::getBkgSize() {
	return Common::Point(_backgroundWidth, _backgroundHeight);
}

void RenderManager::addEffect(GraphicsEffect *_effect) {
	_effects.push_back(_effect);
}

void RenderManager::deleteEffect(uint32 ID) {
	for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) {
		if ((*it)->getKey() == ID) {
			delete *it;
			it = _effects.erase(it);
		}
	}
}

Common::Rect RenderManager::transformBackgroundSpaceRectToScreenSpace(const Common::Rect &src) {
	Common::Rect tmp = src;
	RenderTable::RenderState state = _renderTable.getRenderState();

	if (state == RenderTable::PANORAMA) {
		if (_backgroundOffset < _screenCenterX) {
			Common::Rect rScreen(_screenCenterX + _backgroundOffset, _workingWindow.height());
			Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height());
			lScreen.translate(_backgroundWidth - lScreen.width(), 0);
			lScreen.clip(src);
			rScreen.clip(src);
			if (rScreen.width() < lScreen.width()) {
				tmp.translate(_screenCenterX - _backgroundOffset - _backgroundWidth, 0);
			} else {
				tmp.translate(_screenCenterX - _backgroundOffset, 0);
			}
		} else if (_backgroundWidth - _backgroundOffset < _screenCenterX) {
			Common::Rect rScreen(_screenCenterX - (_backgroundWidth - _backgroundOffset), _workingWindow.height());
			Common::Rect lScreen(_workingWindow.width() - rScreen.width(), _workingWindow.height());
			lScreen.translate(_backgroundWidth - lScreen.width(), 0);
			lScreen.clip(src);
			rScreen.clip(src);
			if (lScreen.width() < rScreen.width()) {
				tmp.translate(_screenCenterX + (_backgroundWidth - _backgroundOffset), 0);
			} else {
				tmp.translate(_screenCenterX - _backgroundOffset, 0);
			}
		} else {
			tmp.translate(_screenCenterX - _backgroundOffset, 0);
		}
	} else if (state == RenderTable::TILT) {
		tmp.translate(0, (_screenCenterY - _backgroundOffset));
	}

	return tmp;
}

EffectMap *RenderManager::makeEffectMap(const Common::Point &xy, int16 depth, const Common::Rect &rect, int8 *_minComp, int8 *_maxComp) {
	Common::Rect bkgRect(_backgroundWidth, _backgroundHeight);
	if (!bkgRect.contains(xy))
		return NULL;

	if (!bkgRect.intersects(rect))
		return NULL;

	uint16 color = *(uint16 *)_currentBackgroundImage.getBasePtr(xy.x, xy.y);
	uint8 stC1, stC2, stC3;
	_currentBackgroundImage.format.colorToRGB(color, stC1, stC2, stC3);
	EffectMap *newMap = new EffectMap;

	EffectMapUnit unit;
	unit.count = 0;
	unit.inEffect = false;

	int16 w = rect.width();
	int16 h = rect.height();

	bool first = true;

	uint8 minComp = MIN(MIN(stC1, stC2), stC3);
	uint8 maxComp = MAX(MAX(stC1, stC2), stC3);

	uint8 depth8 = depth << 3;

	for (int16 j = 0; j < h; j++) {
		uint16 *pix = (uint16 *)_currentBackgroundImage.getBasePtr(rect.left, rect.top + j);
		for (int16 i = 0; i < w; i++) {
			uint16 curClr = pix[i];
			uint8 cC1, cC2, cC3;
			_currentBackgroundImage.format.colorToRGB(curClr, cC1, cC2, cC3);

			bool use = false;

			if (curClr == color)
				use = true;
			else if (curClr > color) {
				if ((cC1 - stC1 < depth8) &&
				        (cC2 - stC2 < depth8) &&
				        (cC3 - stC3 < depth8))
					use = true;
			} else { /* if (curClr < color) */
				if ((stC1 - cC1 < depth8) &&
				        (stC2 - cC2 < depth8) &&
				        (stC3 - cC3 < depth8))
					use = true;
			}

			if (first) {
				unit.inEffect = use;
				first = false;
			}

			if (use) {
				uint8 cMinComp = MIN(MIN(cC1, cC2), cC3);
				uint8 cMaxComp = MAX(MAX(cC1, cC2), cC3);
				if (cMinComp < minComp)
					minComp = cMinComp;
				if (cMaxComp > maxComp)
					maxComp = cMaxComp;
			}

			if (unit.inEffect == use)
				unit.count++;
			else {
				newMap->push_back(unit);
				unit.count = 1;
				unit.inEffect = use;
			}
		}
	}
	newMap->push_back(unit);

	if (_minComp) {
		if (minComp - depth8 < 0)
			*_minComp = -(minComp >> 3);
		else
			*_minComp = -depth;
	}
	if (_maxComp) {
		if ((int16)maxComp + (int16)depth8 > 255)
			*_maxComp = (255 - maxComp) >> 3;
		else
			*_maxComp = depth;
	}

	return newMap;
}

EffectMap *RenderManager::makeEffectMap(const Graphics::Surface &surf, uint16 transp) {
	EffectMapUnit unit;
	unit.count = 0;
	unit.inEffect = false;

	int16 w = surf.w;
	int16 h = surf.h;

	EffectMap *newMap = new EffectMap;

	bool first = true;

	for (int16 j = 0; j < h; j++) {
		const uint16 *pix = (const uint16 *)surf.getBasePtr(0, j);
		for (int16 i = 0; i < w; i++) {
			bool use = false;
			if (pix[i] != transp)
				use = true;

			if (first) {
				unit.inEffect = use;
				first = false;
			}

			if (unit.inEffect == use)
				unit.count++;
			else {
				newMap->push_back(unit);
				unit.count = 1;
				unit.inEffect = use;
			}
		}
	}
	newMap->push_back(unit);

	return newMap;
}

void RenderManager::markDirty() {
	_backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight);
}

#if 0
void RenderManager::bkgFill(uint8 r, uint8 g, uint8 b) {
	_currentBackgroundImage.fillRect(Common::Rect(_currentBackgroundImage.w, _currentBackgroundImage.h), _currentBackgroundImage.format.RGBToColor(r, g, b));
	markDirty();
}
#endif

void RenderManager::timedMessage(const Common::String &str, uint16 milsecs) {
	uint16 msgid = createSubArea();
	updateSubArea(msgid, str);
	deleteSubArea(msgid, milsecs);
}

bool RenderManager::askQuestion(const Common::String &str) {
	Graphics::Surface textSurface;
	textSurface.create(_subtitleArea.width(), _subtitleArea.height(), _engine->_resourcePixelFormat);
	_engine->getTextRenderer()->drawTextWithWordWrapping(str, textSurface);
	copyToScreen(textSurface, _subtitleArea, 0, 0);

	_engine->stopClock();

	int result = 0;

	while (result == 0) {
		Common::Event evnt;
		while (_engine->getEventManager()->pollEvent(evnt)) {
			if (evnt.type == Common::EVENT_KEYDOWN) {
				// English: yes/no
				// German: ja/nein
				// Spanish: si/no
				// French Nemesis: F4/any other key
				// French ZGI: oui/non
				switch (evnt.kbd.keycode) {
				case Common::KEYCODE_y:
					if (_engine->getLanguage() == Common::EN_ANY)
						result = 2;
					break;
				case Common::KEYCODE_j:
					if (_engine->getLanguage() == Common::DE_DEU)
						result = 2;
					break;
				case Common::KEYCODE_s:
					if (_engine->getLanguage() == Common::ES_ESP)
						result = 2;
					break;
				case Common::KEYCODE_o:
					if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_GRANDINQUISITOR)
						result = 2;
					break;
				case Common::KEYCODE_F4:
					if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
						result = 2;
					break;
				case Common::KEYCODE_n:
					result = 1;
					break;
				default:
					if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
						result = 1;
					break;
				}
			}
		}
		_system->updateScreen();
		if (_doubleFPS)
			_system->delayMillis(33);
		else
			_system->delayMillis(66);
	}

	// Draw over the text in order to clear it
	textSurface.fillRect(Common::Rect(_subtitleArea.width(), _subtitleArea.height()), 0);
	copyToScreen(textSurface, _subtitleArea, 0, 0);

	// Free the surface
	textSurface.free();

	_engine->startClock();
	return result == 2;
}

void RenderManager::delayedMessage(const Common::String &str, uint16 milsecs) {
	uint16 msgid = createSubArea();
	updateSubArea(msgid, str);
	processSubs(0);
	renderSceneToScreen();
	_engine->stopClock();

	uint32 stopTime = _system->getMillis() + milsecs;
	while (_system->getMillis() < stopTime) {
		Common::Event evnt;
		while (_engine->getEventManager()->pollEvent(evnt)) {
			if (evnt.type == Common::EVENT_KEYDOWN &&
			        (evnt.kbd.keycode == Common::KEYCODE_SPACE ||
			         evnt.kbd.keycode == Common::KEYCODE_RETURN ||
			         evnt.kbd.keycode == Common::KEYCODE_ESCAPE))
				break;
		}
		_system->updateScreen();
		if (_doubleFPS)
			_system->delayMillis(33);
		else
			_system->delayMillis(66);
	}
	deleteSubArea(msgid);
	_engine->startClock();
}

void RenderManager::showDebugMsg(const Common::String &msg, int16 delay) {
	uint16 msgid = createSubArea();
	updateSubArea(msgid, msg);
	deleteSubArea(msgid, delay);
}

void RenderManager::updateRotation() {
	int16 _velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity();
	ScriptManager *scriptManager = _engine->getScriptManager();

	if (_doubleFPS)
		_velocity /= 2;

	if (_velocity) {
		RenderTable::RenderState renderState = _renderTable.getRenderState();
		if (renderState == RenderTable::PANORAMA) {
			int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos);

			int16 newPosition = startPosition + (_renderTable.getPanoramaReverse() ? -_velocity : _velocity);

			int16 zeroPoint = _renderTable.getPanoramaZeroPoint();
			if (startPosition >= zeroPoint && newPosition < zeroPoint)
				scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) - 1);
			if (startPosition <= zeroPoint && newPosition > zeroPoint)
				scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) + 1);

			int16 screenWidth = getBkgSize().x;
			if (screenWidth)
				newPosition %= screenWidth;

			if (newPosition < 0)
				newPosition += screenWidth;

			setBackgroundPosition(newPosition);
		} else if (renderState == RenderTable::TILT) {
			int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos);

			int16 newPosition = startPosition + _velocity;

			int16 screenHeight = getBkgSize().y;
			int16 tiltGap = (int16)_renderTable.getTiltGap();

			if (newPosition >= (screenHeight - tiltGap))
				newPosition = screenHeight - tiltGap;
			if (newPosition <= tiltGap)
				newPosition = tiltGap;

			setBackgroundPosition(newPosition);
		}
	}
}

void RenderManager::checkBorders() {
	RenderTable::RenderState renderState = _renderTable.getRenderState();
	if (renderState == RenderTable::PANORAMA) {
		int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos);

		int16 newPosition = startPosition;

		int16 screenWidth = getBkgSize().x;

		if (screenWidth)
			newPosition %= screenWidth;

		if (newPosition < 0)
			newPosition += screenWidth;

		if (startPosition != newPosition)
			setBackgroundPosition(newPosition);
	} else if (renderState == RenderTable::TILT) {
		int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos);

		int16 newPosition = startPosition;

		int16 screenHeight = getBkgSize().y;
		int16 tiltGap = (int16)_renderTable.getTiltGap();

		if (newPosition >= (screenHeight - tiltGap))
			newPosition = screenHeight - tiltGap;
		if (newPosition <= tiltGap)
			newPosition = tiltGap;

		if (startPosition != newPosition)
			setBackgroundPosition(newPosition);
	}
}

void RenderManager::rotateTo(int16 _toPos, int16 _time) {
	if (_renderTable.getRenderState() != RenderTable::PANORAMA)
		return;

	if (_time == 0)
		_time = 1;

	int32 maxX = getBkgSize().x;
	int32 curX = getCurrentBackgroundOffset();
	int32 dx = 0;

	if (curX == _toPos)
		return;

	if (curX > _toPos) {
		if (curX - _toPos > maxX / 2)
			dx = (_toPos + (maxX - curX)) / _time;
		else
			dx = -(curX - _toPos) / _time;
	} else {
		if (_toPos - curX > maxX / 2)
			dx = -((maxX - _toPos) + curX) / _time;
		else
			dx = (_toPos - curX) / _time;
	}

	_engine->stopClock();

	for (int16 i = 0; i <= _time; i++) {
		if (i == _time)
			curX = _toPos;
		else
			curX += dx;

		if (curX < 0)
			curX = maxX - curX;
		else if (curX >= maxX)
			curX %= maxX;

		setBackgroundPosition(curX);

		prepareBackground();
		renderSceneToScreen();

		_system->updateScreen();

		_system->delayMillis(500 / _time);
	}

	_engine->startClock();
}

void RenderManager::upscaleRect(Common::Rect &rect) {
	rect.top = rect.top * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT;
	rect.left = rect.left * HIRES_WINDOW_WIDTH / WINDOW_WIDTH;
	rect.bottom = rect.bottom * HIRES_WINDOW_HEIGHT / WINDOW_HEIGHT;
	rect.right = rect.right * HIRES_WINDOW_WIDTH / WINDOW_WIDTH;
}

} // End of namespace ZVision
