/* 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/endian.h"

#include "scumm/scumm.h"
#include "scumm/charset.h"
#include "scumm/util.h"
#include "scumm/resource.h"

#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE

namespace Scumm {

void ScummEngine::towns_drawStripToScreen(VirtScreen *vs, int dstX, int dstY, int srcX, int srcY, int width, int height) {
	if (width <= 0 || height <= 0)
		return;

	assert(_textSurface.getPixels());

	int m = _textSurfaceMultiplier;

	uint8 *src1 = vs->getPixels(srcX, srcY);
	uint8 *src2 = (uint8 *)_textSurface.getBasePtr(srcX * m, (srcY + vs->topline - _screenTop) * m);
	uint8 *dst1 = _townsScreen->getLayerPixels(0, dstX, dstY);
	uint8 *dst2 = _townsScreen->getLayerPixels(1, dstX * m, dstY * m);

	int dp1 = _townsScreen->getLayerPitch(0) - width * _townsScreen->getLayerBpp(0);
	int dp2 = _townsScreen->getLayerPitch(1) - width * m * _townsScreen->getLayerBpp(1);
	int sp1 = vs->pitch - (width * vs->format.bytesPerPixel);
	int sp2 = _textSurface.pitch - width * m;

	if (vs->number == kMainVirtScreen || _game.id == GID_INDY3 || _game.id == GID_ZAK) {
		for (int h = 0; h < height; ++h) {
			if (_outputPixelFormat.bytesPerPixel == 2) {
				for (int w = 0; w < width; ++w) {
					*(uint16 *)dst1 = _16BitPalette[*src1++];
					dst1 += _outputPixelFormat.bytesPerPixel;
				}

				src1 += sp1;
				dst1 += dp1;
			} else {
				memcpy(dst1, src1, width);
				src1 += vs->pitch;
				dst1 += _townsScreen->getLayerPitch(0);
			}

			for (int sH = 0; sH < m; ++sH) {
				memcpy(dst2, src2, width * m);
				src2 += _textSurface.pitch;
				dst2 += _townsScreen->getLayerPitch(1);
			}
		}
	} else {
		dst1 = dst2;
		for (int h = 0; h < height; ++h) {
			for (int w = 0; w < width; ++w) {
				uint8 t = (*src1++) & 0x0f;
				memset(dst1, (t << 4) | t, m);
				dst1 += m;
			}

			dst1 = dst2;
			uint8 *src3 = src2;

			if (m == 2) {
				dst2 += _townsScreen->getLayerPitch(1);
				src3 += _townsScreen->getLayerPitch(1);
			}

			for (int w = 0; w < width * m; ++w) {
				*dst2++ = (*src3 | (*dst1 & _townsLayer2Mask[*src3]));
				*dst1 = (*src2 | (*dst1 & _townsLayer2Mask[*src2]));
				src2++;
				src3++;
				dst1++;
			}

			src1 += sp1;
			src2 = src3 + sp2;
			dst1 = dst2 + dp2;
			dst2 += dp2;
		}
	}

	_townsScreen->addDirtyRect(dstX * m, dstY * m, width * m, height * m);
}

bool ScummEngine::towns_isRectInStringBox(int x1, int y1, int x2, int y2) {
	if (_game.platform == Common::kPlatformFMTowns && _charset->_hasMask && y1 <= _curStringRect.bottom && x1 <= _curStringRect.right && y2 >= _curStringRect.top && x2 >= _curStringRect.left)
		return true;
	return false;
}

void ScummEngine::towns_restoreCharsetBg() {
	if (_curStringRect.left != -1) {
		restoreBackground(_curStringRect, 0);
		_curStringRect.left = -1;
		_charset->_hasMask = false;
		_nextLeft = _string[0].xpos;
	}

	_nextLeft = _string[0].xpos;
	_nextTop = _string[0].ypos;
}

#ifdef USE_RGB_COLOR
void ScummEngine::towns_setPaletteFromPtr(const byte *ptr, int numcolor) {
	setPaletteFromPtr(ptr, numcolor);

	if (_game.version == 5)
		towns_setTextPaletteFromPtr(_currentPalette);

	_townsOverrideShadowColor = 1;
	int m = 48;
	for (int i = 1; i < 16; ++i) {
		int val = _currentPalette[i * 3] + _currentPalette[i * 3 + 1] + _currentPalette[i * 3 + 2];
		if (m > val) {
			_townsOverrideShadowColor = i;
			m = val;
		}
	}
}

void ScummEngine::towns_setTextPaletteFromPtr(const byte *ptr) {
	memcpy(_textPalette, ptr, 48);
}
#endif

void ScummEngine::towns_setupPalCycleField(int x1, int y1, int x2, int y2) {
	if (_numCyclRects >= 10)
		return;
	_cyclRects[_numCyclRects].left = x1;
	_cyclRects[_numCyclRects].top = y1;
	_cyclRects[_numCyclRects].right = x2;
	_cyclRects[_numCyclRects].bottom = y2;
	_numCyclRects++;
	_townsPaletteFlags |= 1;
}

void ScummEngine::towns_processPalCycleField() {
	for (int i = 0; i < _numCyclRects; i++) {
		int x1 = _cyclRects[i].left - _virtscr[kMainVirtScreen].xstart;
		int x2 = _cyclRects[i].right - _virtscr[kMainVirtScreen].xstart;
		if (x1 < 0)
			x1 = 0;
		if (x2 > 320)
			x2 = 320;
		if (x2 > 0)
			markRectAsDirty(kMainVirtScreen, x1, x2, _cyclRects[i].top, _cyclRects[i].bottom);
	}
}

void ScummEngine::towns_resetPalCycleFields() {
	_numCyclRects = 0;
	_townsPaletteFlags &= ~1;
}

const uint8 ScummEngine::_townsLayer2Mask[] = {
	0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#define DIRTY_RECTS_MAX 20
#define FULL_REDRAW (DIRTY_RECTS_MAX + 1)

TownsScreen::TownsScreen(OSystem *system, int width, int height, Graphics::PixelFormat &format) :
	_system(system), _width(width), _height(height), _pixelFormat(format), _pitch(width * format.bytesPerPixel) {
	memset(&_layers[0], 0, sizeof(TownsScreenLayer));
	memset(&_layers[1], 0, sizeof(TownsScreenLayer));
	_outBuffer = new byte[_pitch * _height];
	memset(_outBuffer, 0, _pitch * _height);

	setupLayer(0, width, height, 256);
}

TownsScreen::~TownsScreen() {
	delete[] _layers[0].pixels;
	delete[] _layers[1].pixels;
	delete[] _layers[0].bltInternX;
	delete[] _layers[1].bltInternX;
	delete[] _layers[0].bltInternY;
	delete[] _layers[1].bltInternY;
	delete[] _layers[0].bltTmpPal;
	delete[] _layers[1].bltTmpPal;
	delete[] _outBuffer;
	_dirtyRects.clear();
}

void TownsScreen::setupLayer(int layer, int width, int height, int numCol, void *pal) {
	if (layer < 0 || layer > 1)
		return;

	TownsScreenLayer *l = &_layers[layer];

	if (numCol >> 15)
		error("TownsScreen::setupLayer(): No more than 32767 colors supported.");

	if (width > _width || height > _height)
		error("TownsScreen::setupLayer(): Layer width/height must be equal or less than screen width/height");

	l->scaleW = _width / width;
	l->scaleH = _height / height;

	if ((float)l->scaleW !=	((float)_width / (float)width) || (float)l->scaleH != ((float)_height / (float)height))
		error("TownsScreen::setupLayer(): Layer width/height must be equal or an EXACT half, third, etc. of screen width/height.\n More complex aspect ratio scaling is not supported.");

	if (width <= 0 || height <= 0 || numCol < 16)
		error("TownsScreen::setupLayer(): Invalid width/height/number of colors setting.");

	l->height = height;
	l->numCol = numCol;
	l->bpp = ((numCol - 1) & 0xff00) ? 2 : 1;
	l->pitch = width * l->bpp;
	l->palette = (uint8 *)pal;

	if (l->palette && _pixelFormat.bytesPerPixel == 1)
		warning("TownsScreen::setupLayer(): Layer palette usage requires 16 bit graphics setting.\nLayer palette will be ignored.");

	delete[] l->pixels;
	l->pixels = new uint8[l->pitch * l->height];
	assert(l->pixels);
	memset(l->pixels, 0, l->pitch * l->height);

	// build offset tables to speed up merging/scaling layers
	delete[] l->bltInternX;
	l->bltInternX = new uint16[_width];
	for (int i = 0; i < _width; ++i)
		l->bltInternX[i] = (i / l->scaleW) * l->bpp;

	delete[] l->bltInternY;
	l->bltInternY = new uint8*[_height];
	for (int i = 0; i < _height; ++i)
		l->bltInternY[i] = l->pixels + (i / l->scaleH) * l->pitch;

	delete[] l->bltTmpPal;
	l->bltTmpPal = (l->bpp == 1 && _pixelFormat.bytesPerPixel == 2) ? new uint16[l->numCol] : 0;

	l->enabled = true;
	_layers[0].onBottom = true;
	_layers[1].onBottom = !_layers[0].enabled;
	l->ready = true;
}

void TownsScreen::clearLayer(int layer) {
	if (layer < 0 || layer > 1)
		return;

	TownsScreenLayer *l = &_layers[layer];
	if (!l->ready)
		return;

	memset(l->pixels, 0, l->pitch * l->height);
	_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
	_numDirtyRects = FULL_REDRAW;
}


void TownsScreen::fillLayerRect(int layer, int x, int y, int w, int h, int col) {
	if (layer < 0 || layer > 1 || w <= 0 || h <= 0)
		return;

	TownsScreenLayer *l = &_layers[layer];
	if (!l->ready)
		return;

	assert(x >= 0 && y >= 0 && ((x + w) * l->bpp) <= (l->pitch) && (y + h) <= (l->height));

	uint8 *pos = l->pixels + y * l->pitch + x * l->bpp;

	for (int i = 0; i < h; ++i) {
		if (l->bpp == 2) {
			for (int ii = 0; ii < w; ++ii) {
				*(uint16 *)pos = col;
				pos += 2;
			}
			pos += (l->pitch - w * 2);
		} else {
			memset(pos, col, w);
			pos += l->pitch;
		}
	}
	addDirtyRect(x * l->scaleW, y * l->scaleH, w * l->scaleW, h * l->scaleH);
}

uint8 *TownsScreen::getLayerPixels(int layer, int x, int y) {
	if (layer < 0 || layer > 1)
		return 0;

	TownsScreenLayer *l = &_layers[layer];
	if (!l->ready)
		return 0;

	return l->pixels + y * l->pitch + x * l->bpp;
}

int TownsScreen::getLayerPitch(int layer) {
	if (layer >= 0 && layer < 2)
		return _layers[layer].pitch;
	return 0;
}

int TownsScreen::getLayerHeight(int layer) {
	if (layer >= 0 && layer < 2)
		return _layers[layer].height;
	return 0;
}

int TownsScreen::getLayerBpp(int layer) {
	if (layer >= 0 && layer < 2)
		return _layers[layer].bpp;
	return 0;
}

int TownsScreen::getLayerScaleW(int layer) {
	if (layer >= 0 && layer < 2)
		return _layers[layer].scaleW;
	return 0;
}

int TownsScreen::getLayerScaleH(int layer) {
	if (layer >= 0 && layer < 2)
		return _layers[layer].scaleH;
	return 0;
}

void TownsScreen::addDirtyRect(int x, int y, int w, int h) {
	if (w <= 0 || h <= 0 || _numDirtyRects > DIRTY_RECTS_MAX)
		return;

	if (_numDirtyRects == DIRTY_RECTS_MAX) {
		// full redraw
		_dirtyRects.clear();
		_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
		_numDirtyRects++;
		return;
	}

	int x2 = x + w - 1;
	int y2 = y + h - 1;

	assert(x >= 0 && y >= 0 && x2 <= _width && y2 <= _height);

	bool skip = false;
	for (Common::List<Common::Rect>::iterator r = _dirtyRects.begin(); r != _dirtyRects.end(); ++r) {
		// Try to merge new rect with an existing rect (only once, since trying to merge
		// more than one overlapping rect would be causing more overhead than doing any good).
		if (x > r->left && x < r->right && y > r->top && y < r->bottom) {
			x = r->left;
			y = r->top;
			skip = true;
		}

		if (x2 > r->left && x2 < r->right && y > r->top && y < r->bottom) {
			x2 = r->right;
			y = r->top;
			skip = true;
		}

		if (x2 > r->left && x2 < r->right && y2 > r->top && y2 < r->bottom) {
			x2 = r->right;
			y2 = r->bottom;
			skip = true;
		}

		if (x > r->left && x < r->right && y2 > r->top && y2 < r->bottom) {
			x = r->left;
			y2 = r->bottom;
			skip = true;
		}

		if (skip) {
			r->left = x;
			r->top = y;
			r->right = x2;
			r->bottom = y2;
			break;
		}
	}

	if (!skip) {
		_dirtyRects.push_back(Common::Rect(x, y, x2, y2));
		_numDirtyRects++;
	}
}

void TownsScreen::toggleLayers(int flag) {
	if (flag < 0 || flag > 3)
		return;

	_layers[0].enabled = (flag & 1) ? true : false;
	_layers[0].onBottom = true;
	_layers[1].enabled = (flag & 2) ? true : false;
	_layers[1].onBottom = !_layers[0].enabled;

	_dirtyRects.clear();
	_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
	_numDirtyRects = FULL_REDRAW;

	memset(_outBuffer, 0, _pitch * _height);
	update();

	_system->updateScreen();
}

void TownsScreen::update() {
	updateOutputBuffer();
	outputToScreen();
}

void TownsScreen::updateOutputBuffer() {
	for (Common::List<Common::Rect>::iterator r = _dirtyRects.begin(); r != _dirtyRects.end(); ++r) {
		for (int i = 0; i < 2; i++) {

			TownsScreenLayer *l = &_layers[i];
			if (!l->enabled || !l->ready)
				continue;

			uint8 *dst = _outBuffer + r->top * _pitch + r->left * _pixelFormat.bytesPerPixel;
			int ptch = _pitch - (r->right - r->left + 1) * _pixelFormat.bytesPerPixel;

			if (_pixelFormat.bytesPerPixel == 2 && l->bpp == 1) {
				if (!l->palette)
					error("void TownsScreen::updateOutputBuffer(): No palette assigned to 8 bit layer %d", i);
				for (int ic = 0; ic < l->numCol; ic++)
					l->bltTmpPal[ic] = calc16BitColor(&l->palette[ic * 3]);
			}

			for (int y = r->top; y <= r->bottom; ++y) {
				if (l->bpp == _pixelFormat.bytesPerPixel && l->scaleW == 1 && l->onBottom && l->numCol & 0xff00) {
					memcpy(dst, &l->bltInternY[y][l->bltInternX[r->left]], (r->right + 1 - r->left) * _pixelFormat.bytesPerPixel);
					dst += _pitch;

				} else if (_pixelFormat.bytesPerPixel == 2) {
					for (int x = r->left; x <= r->right; ++x) {
						uint8 *src = &l->bltInternY[y][l->bltInternX[x]];
						if (l->bpp == 1) {
							uint8 col = *src;
							if (col || l->onBottom) {
								if (l->numCol == 16)
									col = (col >> 4) & (col & 0x0f);
								*(uint16 *)dst = l->bltTmpPal[col];
							}
						} else {
							*(uint16 *)dst = *(uint16 *)src;
						}
						dst += 2;
					}
					dst += ptch;

				} else {
					for (int x = r->left; x <= r->right; ++x) {
						uint8 col = l->bltInternY[y][l->bltInternX[x]];
						if (col || l->onBottom) {
							if (l->numCol == 16)
								col = (col >> 4) & (col & 0x0f);
							*dst = col;
						}
						dst++;
					}
					dst += ptch;
				}
			}
		}
	}
}

void TownsScreen::outputToScreen() {
	for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i)
		_system->copyRectToScreen(_outBuffer + i->top * _pitch + i->left * _pixelFormat.bytesPerPixel, _pitch, i->left, i->top, i->right - i->left + 1, i->bottom - i->top + 1);
	_dirtyRects.clear();
	_numDirtyRects = 0;
}

uint16 TownsScreen::calc16BitColor(const uint8 *palEntry) {
	return _pixelFormat.RGBToColor(palEntry[0], palEntry[1], palEntry[2]);
}

#undef DIRTY_RECTS_MAX
#undef FULL_REDRAW

} // End of namespace Scumm

#endif // DISABLE_TOWNS_DUAL_LAYER_MODE
