/* 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 "engines/kyra/screen_lok.h"
#include "engines/kyra/kyra_lok.h"

#include "common/system.h"

#include "graphics/palette.h"

namespace Kyra {

Screen_LoK::Screen_LoK(KyraEngine_LoK *vm, OSystem *system)
	: Screen(vm, system, _screenDimTable, _screenDimTableCount) {
	_vm = vm;
	_unkPtr1 = _unkPtr2 = 0;
	_bitBlitNum = 0;
}

Screen_LoK::~Screen_LoK() {
	for (int i = 0; i < ARRAYSIZE(_saveLoadPage); ++i) {
		delete[] _saveLoadPage[i];
		_saveLoadPage[i] = 0;
	}

	for (int i = 0; i < ARRAYSIZE(_saveLoadPageOvl); ++i) {
		delete[] _saveLoadPageOvl[i];
		_saveLoadPageOvl[i] = 0;
	}

	delete[] _unkPtr1;
	delete[] _unkPtr2;
}

bool Screen_LoK::init() {
	if (!Screen::init())
		return false;

	memset(_bitBlitRects, 0, sizeof(_bitBlitRects));
	_bitBlitNum = 0;
	memset(_saveLoadPage, 0, sizeof(_saveLoadPage));
	memset(_saveLoadPageOvl, 0, sizeof(_saveLoadPageOvl));

	_unkPtr1 = new uint8[getRectSize(1, 144)];
	assert(_unkPtr1);
	memset(_unkPtr1, 0, getRectSize(1, 144));
	_unkPtr2 = new uint8[getRectSize(1, 144)];
	assert(_unkPtr2);
	memset(_unkPtr2, 0, getRectSize(1, 144));

	return true;
}

void Screen_LoK::fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime) {
	if (_vm->gameFlags().platform == Common::kPlatformAmiga)
		return;

	assert(_vm->palTable1()[palIndex]);

	Palette tempPal(getPalette(0).getNumColors());
	tempPal.copy(getPalette(0));
	tempPal.copy(_vm->palTable1()[palIndex], 0, size, startIndex);

	fadePalette(tempPal, fadeTime * 18);

	getPalette(0).copy(tempPal, startIndex, size);
	setScreenPalette(getPalette(0));
	_system->updateScreen();
}

void Screen_LoK::addBitBlitRect(int x, int y, int w, int h) {
	if (_bitBlitNum >= kNumBitBlitRects)
		error("too many bit blit rects");

	_bitBlitRects[_bitBlitNum].left = x;
	_bitBlitRects[_bitBlitNum].top = y;
	_bitBlitRects[_bitBlitNum].right = x + w;
	_bitBlitRects[_bitBlitNum].bottom = y + h;
	++_bitBlitNum;
}

void Screen_LoK::bitBlitRects() {
	Common::Rect *cur = _bitBlitRects;
	while (_bitBlitNum) {
		_bitBlitNum--;
		copyRegion(cur->left, cur->top, cur->left, cur->top, cur->width(), cur->height(), 2, 0);
		++cur;
	}
}

void Screen_LoK::savePageToDisk(const char *file, int page) {
	if (!_saveLoadPage[page / 2]) {
		_saveLoadPage[page / 2] = new uint8[SCREEN_W * SCREEN_H];
		assert(_saveLoadPage[page / 2]);
	}
	memcpy(_saveLoadPage[page / 2], getPagePtr(page), SCREEN_W * SCREEN_H);

	if (_useOverlays) {
		if (!_saveLoadPageOvl[page / 2]) {
			_saveLoadPageOvl[page / 2] = new uint8[SCREEN_OVL_SJIS_SIZE];
			assert(_saveLoadPageOvl[page / 2]);
		}

		uint8 *srcPage = getOverlayPtr(page);
		if (!srcPage) {
			warning("trying to save unsupported overlay page %d", page);
			return;
		}

		memcpy(_saveLoadPageOvl[page / 2], srcPage, SCREEN_OVL_SJIS_SIZE);
	}
}

void Screen_LoK::loadPageFromDisk(const char *file, int page) {
	if (!_saveLoadPage[page / 2]) {
		warning("trying to restore page %d, but no backup found", page);
		return;
	}

	copyBlockToPage(page, 0, 0, SCREEN_W, SCREEN_H, _saveLoadPage[page / 2]);
	delete[] _saveLoadPage[page / 2];
	_saveLoadPage[page / 2] = 0;

	if (_saveLoadPageOvl[page / 2]) {
		uint8 *dstPage = getOverlayPtr(page);
		if (!dstPage) {
			warning("trying to restore unsupported overlay page %d", page);
			return;
		}

		memcpy(dstPage, _saveLoadPageOvl[page / 2], SCREEN_OVL_SJIS_SIZE);
		delete[] _saveLoadPageOvl[page / 2];
		_saveLoadPageOvl[page / 2] = 0;
	}
}

void Screen_LoK::queryPageFromDisk(const char *file, int page, uint8 *buffer) {
	if (!_saveLoadPage[page / 2]) {
		warning("trying to query page %d, but no backup found", page);
		return;
	}

	memcpy(buffer, _saveLoadPage[page / 2], SCREEN_W * SCREEN_H);
}

void Screen_LoK::deletePageFromDisk(int page) {
	delete[] _saveLoadPage[page / 2];
	_saveLoadPage[page / 2] = 0;

	if (_saveLoadPageOvl[page / 2]) {
		delete[] _saveLoadPageOvl[page / 2];
		_saveLoadPageOvl[page / 2] = 0;
	}
}

void Screen_LoK::copyBackgroundBlock(int x, int page, int flag) {
	if (x < 1)
		return;

	int height = 128;
	if (flag)
		height += 8;
	if (!(x & 1))
		++x;
	if (x == 19)
		x = 17;

	uint8 *ptr1 = _unkPtr1;
	uint8 *ptr2 = _unkPtr2;
	int oldVideoPage = _curPage;
	_curPage = page;

	int curX = x;
	copyRegionToBuffer(_curPage, 8, 8, 8, height, ptr2);
	for (int i = 0; i < 19; ++i) {
		int tempX = curX + 1;
		copyRegionToBuffer(_curPage, tempX << 3, 8, 8, height, ptr1);
		copyBlockToPage(_curPage, tempX << 3, 8, 8, height, ptr2);
		int newXPos = curX + x;
		if (newXPos > 37)
			newXPos = newXPos % 38;

		tempX = newXPos + 1;
		copyRegionToBuffer(_curPage, tempX << 3, 8, 8, height, ptr2);
		copyBlockToPage(_curPage, tempX << 3, 8, 8, height, ptr1);
		curX += x * 2;
		if (curX > 37) {
			curX = curX % 38;
		}
	}
	_curPage = oldVideoPage;
}

void Screen_LoK::copyBackgroundBlock2(int x) {
	copyBackgroundBlock(x, 4, 1);
}

void Screen_LoK::setTextColorMap(const uint8 *cmap) {
	setTextColor(cmap, 0, 11);
}

int Screen_LoK::getRectSize(int x, int y) {
	if (x < 1)
		x = 1;
	else if (x > 40)
		x = 40;

	if (y < 1)
		y = 1;
	else if (y > 200)
		y = 200;

	return ((x * y) << 3);
}

void Screen_LoK::postProcessCursor(uint8 *data, int width, int height, int pitch) {
	if (_vm->gameFlags().platform == Common::kPlatformAmiga && _interfacePaletteEnabled) {
		pitch -= width;

		for (int y = 0; y < height; ++y) {
			for (int x = 0; x < width; ++x) {
				if (*data != _cursorColorKey)
					*data += 32;
				++data;
			}

			data += pitch;
		}
	}
}

#pragma mark -

Screen_LoK_16::Screen_LoK_16(KyraEngine_LoK *vm, OSystem *system) : Screen_LoK(vm, system) {
	memset(_paletteDither, 0, sizeof(_paletteDither));
}

void Screen_LoK_16::setScreenPalette(const Palette &pal) {
	_screenPalette->copy(pal);

	for (int i = 0; i < 256; ++i)
		paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2);

	set16ColorPalette(_palette16);
	_forceFullUpdate = true;
}

void Screen_LoK_16::fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc) {
	uint8 notBlackFlag = 0;
	for (int i = 0; i < 768; ++i) {
		if ((*_screenPalette)[i])
			notBlackFlag |= 1;
		if (pal[i])
			notBlackFlag |= 2;
	}

	if (notBlackFlag == 1 || notBlackFlag == 2) {
		bool upFade = false;

		for (int i = 0; i < 768; ++i) {
			if ((*_screenPalette)[i] < pal[i]) {
				upFade = true;
				break;
			}
		}

		if (upFade) {
			for (int i = 0; i < 256; ++i)
				paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2);
			_forceFullUpdate = true;
		}

		uint8 color16Palette[16 * 3];

		if (upFade)
			memset(color16Palette, 0, sizeof(color16Palette));
		else
			memcpy(color16Palette, _palette16, sizeof(color16Palette));

		set16ColorPalette(color16Palette);
		updateScreen();

		for (int i = 0; i < 16; ++i) {
			set16ColorPalette(color16Palette);

			for (int k = 0; k < 48; ++k) {
				if (upFade) {
					if (color16Palette[k] < _palette16[k])
						++color16Palette[k];
				} else {
					if (color16Palette[k] > 0)
						--color16Palette[k];
				}
			}

			if (upFunc && upFunc->isValid())
				(*upFunc)();
			else
				_system->updateScreen();

			_vm->delay((delay >> 5) * _vm->tickLength());
		}
	}

	setScreenPalette(pal);
}

void Screen_LoK_16::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) {
	error("Screen_LoK_16::getFadeParams called");
}

int Screen_LoK_16::fadePalStep(const Palette &pal, int diff) {
	error("Screen_LoK_16::fadePalStep called");
	return 0;   // for compilers that don't support NORETURN
}

void Screen_LoK_16::paletteMap(uint8 idx, int r, int g, int b) {
	const int red = r;
	const int green = g;
	const int blue = b;

	uint16 rgbDiff = 1000;
	int rDiff = 0, gDiff = 0, bDiff = 0;

	int index1 = -1;

	for (int i = 0; i < 16; ++i) {
		const int realR = _palette16[i * 3 + 0] << 4;
		const int realG = _palette16[i * 3 + 1] << 4;
		const int realB = _palette16[i * 3 + 2] << 4;

		uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB);

		if (diff < rgbDiff) {
			rgbDiff = diff;
			index1 = i;

			rDiff = r - realR;
			gDiff = g - realG;
			bDiff = b - realB;
		}
	}

	r = rDiff / 4 + red;
	g = gDiff / 4 + green;
	b = bDiff / 4 + blue;

	rgbDiff = 1000;
	int index2 = -1;

	for (int i = 0; i < 16; ++i) {
		const int realR = _palette16[i * 3 + 0] << 4;
		const int realG = _palette16[i * 3 + 1] << 4;
		const int realB = _palette16[i * 3 + 2] << 4;

		uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB);

		if (diff < rgbDiff) {
			rgbDiff = diff;
			index2 = i;
		}
	}

	_paletteDither[idx].bestMatch = index1;
	_paletteDither[idx].invertMatch = index2;
}

void Screen_LoK_16::convertTo16Colors(uint8 *page, int w, int h, int pitch, int keyColor) {
	const int rowAdd = pitch * 2 - w;

	uint8 *row1 = page;
	uint8 *row2 = page + pitch;

	for (int i = 0; i < h; i += 2) {
		for (int k = 0; k < w; k += 2) {
			if (keyColor == -1 || keyColor != *row1) {
				const PaletteDither &dither = _paletteDither[*row1];

				*row1++ = dither.bestMatch;
				*row1++ = dither.invertMatch;
				*row2++ = dither.invertMatch;
				*row2++ = dither.bestMatch;
			} else {
				row1 += 2;
				row2 += 2;
			}
		}

		row1 += rowAdd;
		row2 += rowAdd;
	}
}

void Screen_LoK_16::mergeOverlay(int x, int y, int w, int h) {
	byte *dst = _sjisOverlayPtrs[0] + y * 640 + x;

	// We do a game screen rect to 16 color dithering here. It is
	// important that we do not dither the overlay, since else the
	// japanese fonts will look wrong.
	convertTo16Colors(dst, w, h, 640);

	const byte *src = _sjisOverlayPtrs[1] + y * 640 + x;

	int add = 640 - w;

	while (h--) {
		for (x = 0; x < w; ++x, ++dst) {
			byte col = *src++;
			if (col != _sjisInvisibleColor)
				*dst = _paletteDither[col].bestMatch;
		}
		dst += add;
		src += add;
	}
}

void Screen_LoK_16::set16ColorPalette(const uint8 *pal) {
	uint8 palette[16 * 3];
	for (int i = 0; i < 16; ++i) {
		palette[i * 3 + 0] = (pal[i * 3 + 0] * 0xFF) / 0x0F;
		palette[i * 3 + 1] = (pal[i * 3 + 1] * 0xFF) / 0x0F;
		palette[i * 3 + 2] = (pal[i * 3 + 2] * 0xFF) / 0x0F;
	}

	_system->getPaletteManager()->setPalette(palette, 0, 16);
}

} // End of namespace Kyra
