/* 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.
 *
 */

/*
 * This code is based on Labyrinth of Time code with assistance of
 *
 * Copyright (c) 1993 Terra Nova Development
 * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
 *
 */

#include "common/file.h"
#include "graphics/palette.h"

#include "lab/lab.h"

#include "lab/anim.h"
#include "lab/dispman.h"
#include "lab/eventman.h"
#include "lab/music.h"
#include "lab/image.h"
#include "lab/interface.h"
#include "lab/resource.h"
#include "lab/utils.h"

namespace Lab {

DisplayMan::DisplayMan(LabEngine *vm) : _vm(vm) {
	_longWinInFront = false;
	_lastMessageLong = false;
	_actionMessageShown = false;

	_screenBytesPerPage = 0;
	_curBitmap = nullptr;
	_displayBuffer = nullptr;
	_currentDisplayBuffer = nullptr;
	_fadePalette = nullptr;

	_screenWidth = 0;
	_screenHeight = 0;

	for (int i = 0; i < 256 * 3; i++)
		_curVgaPal[i] = 0;
}

DisplayMan::~DisplayMan() {
	freePict();
	delete[] _displayBuffer;
}

void DisplayMan::loadPict(const Common::String filename) {
	freePict();
	_curBitmap = _vm->_resource->openDataFile(filename, MKTAG('D', 'I', 'F', 'F'));
}

void DisplayMan::loadBackPict(const Common::String fileName, uint16 *highPal) {
	_fadePalette = highPal;
	_vm->_anim->_noPalChange = true;
	readPict(fileName);

	for (int i = 0; i < 16; i++) {
		highPal[i] = ((_vm->_anim->_diffPalette[i * 3] >> 2) << 8) +
			((_vm->_anim->_diffPalette[i * 3 + 1] >> 2) << 4) +
			((_vm->_anim->_diffPalette[i * 3 + 2] >> 2));
	}

	_vm->_anim->_noPalChange = false;
}

void DisplayMan::readPict(const Common::String filename, bool playOnce, bool onlyDiffData, byte *memoryBuffer) {
	_vm->_anim->stopDiff();
	loadPict(filename);
	_vm->_anim->setOutputBuffer(memoryBuffer);
	_vm->_anim->readDiff(_curBitmap, playOnce, onlyDiffData);
}

void DisplayMan::freePict() {
	delete _curBitmap;
	_curBitmap = nullptr;
}

Common::String DisplayMan::getWord(const char *mainBuffer) {
	Common::String result;

	for (int i = 0; mainBuffer[i] && (mainBuffer[i] != ' ') && (mainBuffer[i] != '\n'); i++)
		result += mainBuffer[i];

	return result;
}

Common::String DisplayMan::getLine(TextFont *tf, const char **mainBuffer, uint16 lineWidth) {
	uint16 curWidth = 0;
	Common::String result;

	while ((*mainBuffer)[0]) {
		Common::String wordBuffer = getWord(*mainBuffer);

		if ((curWidth + textLength(tf, wordBuffer)) <= lineWidth) {
			result += wordBuffer;
			(*mainBuffer) += wordBuffer.size();

			// end of line
			if ((*mainBuffer)[0] == '\n') {
				(*mainBuffer)++;
				break;
			}

			// append any space after the word
			if ((*mainBuffer)[0]) {
				result += (*mainBuffer)[0];
				(*mainBuffer)++;
			}

			curWidth = textLength(tf, result);
		} else
			break;
	}

	return result;
}

int DisplayMan::flowText(TextFont *font, int16 spacing, byte penColor, byte backPen,
			bool fillBack, bool centerh, bool centerv, bool output, Common::Rect textRect, const char *str, Image *targetImage) {

	byte *saveDisplayBuffer = _currentDisplayBuffer;

	if (targetImage) {
		_currentDisplayBuffer = targetImage->_imageData;
		assert(_screenBytesPerPage == (uint32)(targetImage->_width * targetImage->_height));
	}

	if (fillBack)
		rectFill(textRect, backPen);

	if (!str)
		return 0;

	const char *orig = str;

	TextFont *msgFont = font;
	uint16 fontHeight = textHeight(msgFont) + spacing;
	uint16 numLines   = (textRect.height() + 1) / fontHeight;
	uint16 width      = textRect.width() + 1;
	uint16 y          = textRect.top;

	if (centerv && output) {
		const char *temp = str;
		uint16 actlines = 0;

		while (temp[0]) {
			getLine(msgFont, &temp, width);
			actlines++;
		}

		if (actlines <= numLines)
			y += ((textRect.height() + 1) - (actlines * fontHeight)) / 2;
	}

	while (numLines && str[0]) {
		Common::String lineBuffer;
		lineBuffer = getLine(msgFont, &str, width);

		uint16 x = textRect.left;

		if (centerh)
			x += (width - textLength(msgFont, lineBuffer)) / 2;

		if (output)
			drawText(msgFont, x, y, penColor, lineBuffer);

		numLines--;
		y += fontHeight;
	}

	_currentDisplayBuffer = saveDisplayBuffer;

	return (str - orig);
}

void DisplayMan::createBox(uint16 y2) {
	// Message box area
	rectFillScaled(4, 154, 315, y2 - 2, 7);

	// Box around message area
	drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleX(317), 0);
	drawVLine(_vm->_utils->vgaScaleX(317), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2), 0);
	drawHLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(y2), _vm->_utils->vgaScaleX(317), 0);
	drawVLine(_vm->_utils->vgaScaleX(2), _vm->_utils->vgaScaleY(152), _vm->_utils->vgaScaleY(y2), 0);
}

int DisplayMan::longDrawMessage(Common::String str, bool isActionMessage) {
	if (isActionMessage) {
		_actionMessageShown = true;
	} else if (_actionMessageShown) {
		_actionMessageShown = false;
		return 0;
	}

	if (str.empty())
		return 0;

	_vm->_interface->attachButtonList(nullptr);

	if (!_longWinInFront) {
		_longWinInFront = true;
		// Clear Area
		rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199), 3);
	}

	createBox(198);

	return flowText(_vm->_msgFont, 0, 1, 7, false, true, true, true, _vm->_utils->vgaRectScale(6, 155, 313, 195), str.c_str());
}

void DisplayMan::drawMessage(Common::String str, bool isActionMessage) {
	if (isActionMessage) {
		_actionMessageShown = true;
	} else if (_actionMessageShown) {
		_actionMessageShown = false;
		return;
	}

	if (str.empty())
		return;

	if ((textLength(_vm->_msgFont, str) > _vm->_utils->vgaScaleX(306))) {
		longDrawMessage(str, isActionMessage);
		_lastMessageLong = true;
	} else {
		if (_longWinInFront) {
			_longWinInFront = false;
			drawPanel();
		}

		createBox(168);
		drawText(_vm->_msgFont, _vm->_utils->vgaScaleX(7), _vm->_utils->vgaScaleY(155) + _vm->_utils->svgaCord(2), 1, str);
		_lastMessageLong = false;
	}
}

void DisplayMan::drawPanel() {
	// Clear Area
	rectFill(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), _vm->_utils->vgaScaleY(199), 3);

	// First Line
	drawHLine(0, _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), 0);
	// Second Line
	drawHLine(0, _vm->_utils->vgaScaleY(149) + 1 + _vm->_utils->svgaCord(2), _vm->_utils->vgaScaleX(319), 5);
	// Button Separators
	drawHLine(0, _vm->_utils->vgaScaleY(170), _vm->_utils->vgaScaleX(319), 0);

	if (!_vm->_alternate) {
		// The horizontal lines under the black one
		drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319), 4);
		_vm->_interface->drawButtonList(&_vm->_moveButtonList);
	} else {
		if (_vm->getPlatform() != Common::kPlatformWindows) {
			// Vertical Black lines
			drawVLine(_vm->_utils->vgaScaleX(124), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
			drawVLine(_vm->_utils->vgaScaleX(194), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
		} else {
			// Vertical Black lines
			drawVLine(_vm->_utils->vgaScaleX(90), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
			drawVLine(_vm->_utils->vgaScaleX(160), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
			drawVLine(_vm->_utils->vgaScaleX(230), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleY(199), 0);
		}

		// The horizontal lines under the black one
		drawHLine(0, _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(122), 4);
		drawHLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(192), 4);
		drawHLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 1, _vm->_utils->vgaScaleX(319), 4);
		// The vertical high light lines
		drawVLine(_vm->_utils->vgaScaleX(1), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);

		if (_vm->getPlatform() != Common::kPlatformWindows) {
			drawVLine(_vm->_utils->vgaScaleX(126), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
			drawVLine(_vm->_utils->vgaScaleX(196), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
		} else {
			drawVLine(_vm->_utils->vgaScaleX(92), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
			drawVLine(_vm->_utils->vgaScaleX(162), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
			drawVLine(_vm->_utils->vgaScaleX(232), _vm->_utils->vgaScaleY(170) + 2, _vm->_utils->vgaScaleY(198), 4);
		}

		_vm->_interface->drawButtonList(&_vm->_invButtonList);
	}
}

void DisplayMan::setUpScreens() {
	Interface *i = _vm->_interface;
	ButtonList *moveButtonList = &_vm->_moveButtonList;
	ButtonList *invButtonList = &_vm->_invButtonList;
	Image **moveImages = _vm->_moveImages;
	Image **invImages = _vm->_invImages;

	createScreen(_vm->_isHiRes);

	// TODO: The CONTROL file is not present in the Amiga version
	Common::File *controlFile = _vm->_resource->openDataFile("P:Control");
	for (int j = 0; j < 20; j++)
		_vm->_moveImages[j] = new Image(controlFile, _vm);
	delete controlFile;

	// Creates the buttons for the movement control panel
	// The key mapping was only set for the Windows version.
	// It's very convenient to have those shortcut, so I added them
	// for all versions. (Strangerke)
	uint16 y = _vm->_utils->vgaScaleY(173) - _vm->_utils->svgaCord(2);
	moveButtonList->push_back(i->createButton(  1, y, 0, Common::KEYCODE_t,     moveImages[0],  moveImages[1]));
	moveButtonList->push_back(i->createButton( 33, y, 1, Common::KEYCODE_m,     moveImages[2],  moveImages[3]));
	moveButtonList->push_back(i->createButton( 65, y, 2, Common::KEYCODE_o,     moveImages[4],  moveImages[5]));
	moveButtonList->push_back(i->createButton( 97, y, 3, Common::KEYCODE_c,     moveImages[6],  moveImages[7]));
	moveButtonList->push_back(i->createButton(129, y, 4, Common::KEYCODE_l,     moveImages[8],  moveImages[9]));
	moveButtonList->push_back(i->createButton(161, y, 5, Common::KEYCODE_i,     moveImages[12], moveImages[13]));
	moveButtonList->push_back(i->createButton(193, y, 6, Common::KEYCODE_LEFT,  moveImages[14], moveImages[15]));
	moveButtonList->push_back(i->createButton(225, y, 7, Common::KEYCODE_UP,    moveImages[16], moveImages[17]));
	moveButtonList->push_back(i->createButton(257, y, 8, Common::KEYCODE_RIGHT, moveImages[18], moveImages[19]));
	moveButtonList->push_back(i->createButton(289, y, 9, Common::KEYCODE_p,     moveImages[10], moveImages[11]));

	// TODO: The INV file is not present in the Amiga version
	Common::File *invFile = _vm->_resource->openDataFile("P:Inv");
	if (_vm->getPlatform() == Common::kPlatformWindows) {
		for (int imgIdx = 0; imgIdx < 10; imgIdx++)
			_vm->_invImages[imgIdx] = new Image(invFile, _vm);
	} else {
		for (int imgIdx = 0; imgIdx < 6; imgIdx++)
			_vm->_invImages[imgIdx] = new Image(invFile, _vm);
	}

	if (_vm->getPlatform() == Common::kPlatformWindows) {
		invButtonList->push_back(i->createButton( 24, y, 0, Common::KEYCODE_ESCAPE, invImages[0],   invImages[1]));
		invButtonList->push_back(i->createButton( 56, y, 1, Common::KEYCODE_g,      invImages[2],   invImages[3]));
		invButtonList->push_back(i->createButton( 94, y, 2, Common::KEYCODE_u,      invImages[4],   invImages[5]));
		invButtonList->push_back(i->createButton(126, y, 3, Common::KEYCODE_l,      moveImages[8],  moveImages[9]));
		invButtonList->push_back(i->createButton(164, y, 4, Common::KEYCODE_LEFT,   moveImages[14], moveImages[15]));
		invButtonList->push_back(i->createButton(196, y, 5, Common::KEYCODE_RIGHT,  moveImages[18], moveImages[19]));
	// The windows version has 2 extra buttons for breadcrumb trail
	// CHECKME: the game is really hard to play without those, maybe we could add something to enable that.
		invButtonList->push_back(i->createButton(234, y, 6, Common::KEYCODE_b, invImages[6], invImages[7]));
		invButtonList->push_back(i->createButton(266, y, 7, Common::KEYCODE_f, invImages[8], invImages[9]));
	} else {
		invButtonList->push_back(i->createButton( 58, y, 0, Common::KEYCODE_ESCAPE, invImages[0],   invImages[1]));
		invButtonList->push_back(i->createButton( 90, y, 1, Common::KEYCODE_g,      invImages[2],   invImages[3]));
		invButtonList->push_back(i->createButton(128, y, 2, Common::KEYCODE_u,      invImages[4],   invImages[5]));
		invButtonList->push_back(i->createButton(160, y, 3, Common::KEYCODE_l,      moveImages[8],  moveImages[9]));
		invButtonList->push_back(i->createButton(198, y, 4, Common::KEYCODE_LEFT,   moveImages[14], moveImages[15]));
		invButtonList->push_back(i->createButton(230, y, 5, Common::KEYCODE_RIGHT,  moveImages[18], moveImages[19]));
	}

	delete invFile;
}

void DisplayMan::rectFill(Common::Rect fillRect, byte color) {
	int width = fillRect.width() + 1;
	int height = fillRect.height() + 1;

	if (fillRect.left + width > _screenWidth)
		width = _screenWidth - fillRect.left;

	if (fillRect.top + height > _screenHeight)
		height = _screenHeight - fillRect.top;

	if ((width > 0) && (height > 0)) {
		byte *d = getCurrentDrawingBuffer() + fillRect.top * _screenWidth + fillRect.left;

		while (height-- > 0) {
			byte *dd = d;
			int ww = width;

			while (ww-- > 0) {
				*dd++ = color;
			}

			d += _screenWidth;
		}
	}
}

void DisplayMan::rectFill(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color) {
	rectFill(Common::Rect(x1, y1, x2, y2), color);
}

void DisplayMan::rectFillScaled(uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte color) {
	rectFill(_vm->_utils->vgaRectScale(x1, y1, x2, y2), color);
}

void DisplayMan::drawVLine(uint16 x, uint16 y1, uint16 y2, byte color) {
	rectFill(x, y1, x, y2, color);
}

void DisplayMan::drawHLine(uint16 x1, uint16 y, uint16 x2, byte color) {
	rectFill(x1, y, x2, y, color);
}

void DisplayMan::screenUpdate() {
	_vm->_event->processInput();

	_vm->_system->copyRectToScreen(_displayBuffer, _screenWidth, 0, 0, _screenWidth, _screenHeight);
	_vm->_console->onFrame();
	_vm->_system->updateScreen();
}

void DisplayMan::createScreen(bool hiRes) {
	if (hiRes) {
		_screenWidth  = 640;
		_screenHeight = 480;
	} else {
		_screenWidth  = 320;
		_screenHeight = 200;
	}
	_screenBytesPerPage = _screenWidth * _screenHeight;

	if (_displayBuffer)
		delete[] _displayBuffer;
	_displayBuffer = new byte[_screenBytesPerPage];
	memset(_displayBuffer, 0, _screenBytesPerPage);
}

void DisplayMan::setAmigaPal(uint16 *pal) {
	byte vgaPal[16 * 3];
	uint16 vgaIdx = 0;

	for (int i = 0; i < 16; i++) {
		vgaPal[vgaIdx++] = (byte)(((pal[i] & 0xf00) >> 8) << 2);
		vgaPal[vgaIdx++] = (byte)(((pal[i] & 0x0f0) >> 4) << 2);
		vgaPal[vgaIdx++] = (byte)(((pal[i] & 0x00f)) << 2);
	}

	writeColorRegs(vgaPal, 0, 16);
}

void DisplayMan::writeColorRegs(byte *buf, uint16 first, uint16 numReg) {
	assert(first + numReg <= 256);
	byte tmp[256 * 3];

	for (int i = 0; i < numReg * 3; i++)
		tmp[i] = (buf[i] << 2) | (buf[i] >> 4);	// better results than buf[i] * 4

	_vm->_system->getPaletteManager()->setPalette(tmp, first, numReg);
	memcpy(&(_curVgaPal[first * 3]), buf, numReg * 3);
}

void DisplayMan::setPalette(void *newPal, uint16 numColors) {
	if (memcmp(newPal, _curVgaPal, numColors * 3) != 0)
		writeColorRegs((byte *)newPal, 0, numColors);
}

byte *DisplayMan::getCurrentDrawingBuffer() {
	if (_currentDisplayBuffer)
		return _currentDisplayBuffer;

	return _displayBuffer;
}

void DisplayMan::checkerBoardEffect(uint16 penColor, uint16 x1, uint16 y1, uint16 x2, uint16 y2) {
	int w = x2 - x1 + 1;
	int h = y2 - y1 + 1;

	if (x1 + w > _screenWidth)
		w = _screenWidth - x1;

	if (y1 + h > _screenHeight)
		h = _screenHeight - y1;

	if ((w > 0) && (h > 0)) {
		byte *d = getCurrentDrawingBuffer() + y1 * _screenWidth + x1;

		while (h-- > 0) {
			byte *dd = d;
			int ww = w;

			if (y1 & 1) {
				dd++;
				ww--;
			}

			while (ww > 0) {
				*dd = penColor;
				dd += 2;
				ww -= 2;
			}

			d += _screenWidth;
			y1++;
		}
	}
}

void DisplayMan::freeFont(TextFont **font) {
	if (*font) {
		if ((*font)->_data)
			delete[] (*font)->_data;

		delete *font;
		*font = nullptr;
	}
}

uint16 DisplayMan::textLength(TextFont *font, const Common::String text) {
	uint16 length = 0;

	if (font) {
		int numChars = text.size();
		for (int i = 0; i < numChars; i++) {
			length += font->_widths[(byte)text[i]];
		}
	}

	return length;
}

uint16 DisplayMan::textHeight(TextFont *tf) {
	return (tf) ? tf->_height : 0;
}

void DisplayMan::drawText(TextFont *tf, uint16 x, uint16 y, uint16 color, const Common::String text) {
	byte *vgaTop = getCurrentDrawingBuffer();
	int numChars = text.size();

	for (int i = 0; i < numChars; i++) {
		uint32 realOffset = (_screenWidth * y) + x;
		uint16 curPage    = realOffset / _screenBytesPerPage;
		uint32 segmentOffset = realOffset - (curPage * _screenBytesPerPage);
		int32 leftInSegment = _screenBytesPerPage - segmentOffset;
		byte *vgaCur = vgaTop + segmentOffset;

		if (tf->_widths[(byte)text[i]]) {
			byte *cdata = tf->_data + tf->_offsets[(byte)text[i]];
			uint16 bwidth = *cdata++;
			byte *vgaTemp = vgaCur;
			byte *vgaTempLine = vgaCur;

			for (int rows = 0; rows < tf->_height; rows++) {
				int32 templeft = leftInSegment;

				vgaTemp = vgaTempLine;

				for (int cols = 0; cols < bwidth; cols++) {
					uint16 data = *cdata++;

					if (data && (templeft >= 8)) {
						for (int j = 7; j >= 0; j--) {
							if ((1 << j) & data)
								*vgaTemp = color;
							vgaTemp++;
						}

						templeft -= 8;
					} else if (data) {
						uint16 mask = 0x80;
						templeft = leftInSegment;

						for (int counterb = 0; counterb < 8; counterb++) {
							if (templeft <= 0) {
								curPage++;
								vgaTemp = vgaTop - templeft;
								// Set up VGATempLine for next line
								vgaTempLine -= _screenBytesPerPage;
								// Set up LeftInSegment for next line
								leftInSegment += _screenBytesPerPage + templeft;
								templeft += _screenBytesPerPage;
							}

							if (mask & data)
								*vgaTemp = color;

							vgaTemp++;

							mask = mask >> 1;
							templeft--;
						}
					} else {
						templeft -= 8;
						vgaTemp += 8;
					}
				}

				vgaTempLine += _screenWidth;
				leftInSegment -= _screenWidth;

				if (leftInSegment <= 0) {
					curPage++;
					vgaTempLine -= _screenBytesPerPage;
					leftInSegment += _screenBytesPerPage;
				}
			}
		}

		x += tf->_widths[(byte)text[i]];
	}
}

void DisplayMan::doScrollBlack() {
	uint16 width = _vm->_utils->vgaScaleX(320);
	uint16 height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);

	_vm->_event->mouseHide();

	byte *mem = new byte[width * height];
	int16 by = _vm->_utils->vgaScaleX(4);
	int16 verticalScroll = height;

	while (verticalScroll > 0) {
		scrollDisplayY(-by, 0, 0, width - 1, height - 1, mem);
		verticalScroll -= by;

		_vm->updateEvents();
		_vm->waitTOF();
	}

	delete[] mem;

	_vm->_event->mouseShow();
}

void DisplayMan::copyPage(uint16 width, uint16 height, uint16 nheight, uint16 startLine, byte *mem) {
	byte *baseAddr = getCurrentDrawingBuffer();

	uint32 size = (int32)(height - nheight) * (int32)width;
	mem += startLine * width;
	uint16 curPage = ((int32)nheight * (int32)width) / _screenBytesPerPage;
	uint32 offSet = ((int32)nheight * (int32)width) - (curPage * _screenBytesPerPage);

	while (size) {
		uint32 copySize;
		if (size > (_screenBytesPerPage - offSet))
			copySize = _screenBytesPerPage - offSet;
		else
			copySize = size;

		size -= copySize;

		memcpy(baseAddr + (offSet >> 2), mem, copySize);
		mem += copySize;
		curPage++;
		offSet = 0;
	}
}

void DisplayMan::doScrollWipe(const Common::String filename) {
	_vm->_event->mouseHide();
	uint16 width = _vm->_utils->vgaScaleX(320);
	uint16 height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);

	while (_vm->_music->isSoundEffectActive()) {
		_vm->updateEvents();
		_vm->waitTOF();
	}

	readPict(filename, true, true);
	setPalette(_vm->_anim->_diffPalette, 256);
	byte *mem = _vm->_anim->_scrollScreenBuffer;

	_vm->updateEvents();
	uint16 by = _vm->_utils->vgaScaleX(3);
	uint16 nheight = height;
	uint16 startLine = 0, onRow = 0;

	while (onRow < _vm->_anim->getDIFFHeight()) {
		_vm->updateEvents();

		if ((by > nheight) && nheight)
			by = nheight;

		if ((startLine + by) > (_vm->_anim->getDIFFHeight() - height - 1))
			break;

		if (nheight)
			nheight -= by;

		copyPage(width, height, nheight, startLine, mem);
		screenUpdate();

		if (!nheight)
			startLine += by;

		onRow += by;

		if (nheight <= (height / 4))
			by = _vm->_utils->vgaScaleX(5);
		else if (nheight <= (height / 3))
			by = _vm->_utils->vgaScaleX(4);
		else if (nheight <= (height / 2))
			by = _vm->_utils->vgaScaleX(3);
	}

	_vm->_event->mouseShow();
}

void DisplayMan::doScrollBounce() {
	const uint16 offsets[8] = { 3, 3, 2, 2, 2, 1, 1, 1 };
	const int multiplier = (_vm->_isHiRes) ? 2 : 1;

	_vm->_event->mouseHide();
	int width = _vm->_utils->vgaScaleX(320);
	int height = _vm->_utils->vgaScaleY(149) + _vm->_utils->svgaCord(2);
	byte *mem = _vm->_anim->_scrollScreenBuffer;

	_vm->updateEvents();
	int startLine = _vm->_anim->getDIFFHeight() - height - 1;

	for (int i = 0; i < 5; i++) {
		_vm->updateEvents();
		startLine -= (5 - i) * multiplier;
		copyPage(width, height, 0, startLine, mem);
		_vm->waitTOF();
	}

	for (int i = 8; i > 0; i--) {
		_vm->updateEvents();
		startLine += offsets[i - 1] * multiplier;
		copyPage(width, height, 0, startLine, mem);
		_vm->waitTOF();
	}

	_vm->_event->mouseShow();
}

void DisplayMan::doTransWipe(const Common::String filename) {
	uint16 lastY, linesLast;

	if (_vm->_isHiRes) {
		linesLast = 3;
		lastY = 358;
	} else {
		linesLast = 1;
		lastY = 148;
	}

	uint16 linesDone = 0;

	for (int j = 0; j < 2; j++) {
		for (int i = 0; i < 2; i++) {
			uint16 curY = i * 2;

			while (curY < lastY) {
				if (linesDone >= linesLast) {
					_vm->updateEvents();
					_vm->waitTOF();
					linesDone = 0;
				}

				if (j == 0)
					checkerBoardEffect(0, 0, curY, _screenWidth - 1, curY + 1);
				else
					rectFill(0, curY, _screenWidth - 1, curY + 1, 0);
				curY += 4;
				linesDone++;
			}	// while
		}	// for i
	}	// for j

	if (filename.empty())
		_vm->_curFileName = _vm->getPictName(true);
	else if (filename[0] > ' ')
		_vm->_curFileName = filename;
	else
		_vm->_curFileName = _vm->getPictName(true);

	byte *bitMapBuffer = new byte[_screenWidth * (lastY + 5)];
	readPict(_vm->_curFileName, true, false, bitMapBuffer);

	setPalette(_vm->_anim->_diffPalette, 256);

	Image imgSource(_vm);
	imgSource._width = _screenWidth;
	imgSource._height = lastY;
	imgSource.setData(bitMapBuffer, true);

	Image imgDest(_vm);
	imgDest._width = _screenWidth;
	imgDest._height = _screenHeight;
	imgDest.setData(getCurrentDrawingBuffer(), false);

	for (int j = 0; j < 2; j++) {
		for (int i = 0; i < 2; i++) {
			uint16 curY = i * 2;

			while (curY < lastY) {
				if (linesDone >= linesLast) {
					_vm->updateEvents();
					_vm->waitTOF();
					linesDone = 0;
				}

				imgDest.setData(getCurrentDrawingBuffer(), false);

				if (j == 0) {
					imgSource.blitBitmap(0, curY, &imgDest, 0, curY, _screenWidth, 2, false);
					checkerBoardEffect(0, 0, curY, _screenWidth - 1, curY + 1);
				} else {
					uint16 bitmapHeight = (curY == lastY) ? 1 : 2;
					imgSource.blitBitmap(0, curY, &imgDest, 0, curY, _screenWidth, bitmapHeight, false);
				}
				curY += 4;
				linesDone++;
			}	// while
		}	// for i
	}	// for j

	// bitMapBuffer will be deleted by the Image destructor
}

void DisplayMan::doTransition(TransitionType transitionType, const Common::String filename) {
	switch (transitionType) {
	case kTransitionWipe:
	case kTransitionTransporter:
		doTransWipe(filename);
		break;
	case kTransitionScrollWipe:		// only used in scene 7 (street, when teleporting to the surreal maze)
		doScrollWipe(filename);
		break;
	case kTransitionScrollBlack:	// only used in scene 7 (street, when teleporting to the surreal maze)
		doScrollBlack();
		break;
	case kTransitionScrollBounce:	// only used in scene 7 (street, when teleporting to the surreal maze)
		doScrollBounce();
		break;
	case kTransitionReadFirstFrame:	// only used in scene 7 (street, when teleporting to the surreal maze)
		readPict(filename, false);
		break;
	case kTransitionReadNextFrame:	// only used in scene 7 (street, when teleporting to the surreal maze)
		_vm->_anim->diffNextFrame();
		break;
	case kTransitionNone:
	default:
		break;
	}
}

void DisplayMan::blackScreen() {
	byte pal[256 * 3];
	memset(pal, 0, 248 * 3);
	writeColorRegs(pal, 8, 248);

	_vm->_system->delayMillis(32);
}

void DisplayMan::whiteScreen() {
	byte pal[256 * 3];
	memset(pal, 255, 248 * 3);
	writeColorRegs(pal, 8, 248);
}

void DisplayMan::blackAllScreen() {
	byte pal[256 * 3];
	memset(pal, 0, 256 * 3);
	writeColorRegs(pal, 0, 256);

	_vm->_system->delayMillis(32);
}

void DisplayMan::scrollDisplayX(int16 dx, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) {
	Image img(_vm);
	img.setData(buffer, false);

	if (x1 > x2)
		SWAP<uint16>(x1, x2);

	if (y1 > y2)
		SWAP<uint16>(y1, y2);

	if (dx > 0) {
		img._width = x2 - x1 + 1 - dx;
		img._height = y2 - y1 + 1;

		img.readScreenImage(x1, y1);
		img.drawImage(x1 + dx, y1);

		rectFill(x1, y1, x1 + dx - 1, y2, 0);
	} else if (dx < 0) {
		img._width = x2 - x1 + 1 + dx;
		img._height = y2 - y1 + 1;

		img.readScreenImage(x1 - dx, y1);
		img.drawImage(x1, y1);

		rectFill(x2 + dx + 1, y1, x2, y2, 0);
	}
}

void DisplayMan::scrollDisplayY(int16 dy, uint16 x1, uint16 y1, uint16 x2, uint16 y2, byte *buffer) {
	Image img(_vm);
	img.setData(buffer, false);

	if (x1 > x2)
		SWAP<uint16>(x1, x2);

	if (y1 > y2)
		SWAP<uint16>(y1, y2);

	if (dy > 0) {
		img._width = x2 - x1 + 1;
		img._height = y2 - y1 + 1 - dy;

		img.readScreenImage(x1, y1);
		img.drawImage(x1, y1 + dy);

		rectFill(x1, y1, x2, y1 + dy - 1, 0);
	} else if (dy < 0) {
		img._width = x2 - x1 + 1;
		img._height = y2 - y1 + 1 + dy;

		img.readScreenImage(x1, y1 - dy);
		img.drawImage(x1, y1);

		rectFill(x1, y2 + dy + 1, x2, y2, 0);
	}
}

uint16 DisplayMan::fadeNumIn(uint16 num, uint16 res, uint16 counter) {
	return (num - ((((int32)(15 - counter)) * ((int32)(num - res))) / 15));
}

uint16 DisplayMan::fadeNumOut(uint16 num, uint16 res, uint16 counter) {
	return (num - ((((int32) counter) * ((int32)(num - res))) / 15));
}

void DisplayMan::fade(bool fadeIn) {
	uint16 newPal[16];

	for (int i = 0; i < 16; i++) {
		for (int palIdx = 0; palIdx < 16; palIdx++) {
			if (fadeIn)
				newPal[palIdx] =
					(0x00F & fadeNumIn(0x00F & _fadePalette[palIdx], 0, i)) +
					(0x0F0 & fadeNumIn(0x0F0 & _fadePalette[palIdx], 0, i)) +
					(0xF00 & fadeNumIn(0xF00 & _fadePalette[palIdx], 0, i));
			else
				newPal[palIdx] =
					(0x00F & fadeNumOut(0x00F & _fadePalette[palIdx], 0, i)) +
					(0x0F0 & fadeNumOut(0x0F0 & _fadePalette[palIdx], 0, i)) +
					(0xF00 & fadeNumOut(0xF00 & _fadePalette[palIdx], 0, i));
		}

		setAmigaPal(newPal);
		_vm->updateEvents();
		_vm->waitTOF();
		_vm->waitTOF();
	}
}

} // End of namespace Lab