/* 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-12-0/engines/parallaction/graphics.cpp $
 * $Id: graphics.cpp 33072 2008-07-15 10:59:58Z peres001 $
 *
 */

#include "common/system.h"
#include "common/file.h"
#include "graphics/primitives.h"

#include "parallaction/input.h"
#include "parallaction/parallaction.h"


namespace Parallaction {

// this is the size of the receiving buffer for unpacked frames,
// since BRA uses some insanely big animations.
#define MAXIMUM_UNPACKED_BITMAP_SIZE	640*401


void Gfx::registerVar(const Common::String &name, int32 initialValue) {
	if (_vars.contains(name)) {
		warning("Variable '%s' already registered, ignoring initial value.\n", name.c_str());
	} else {
		_vars.setVal(name, initialValue);
	}
}

void Gfx::setVar(const Common::String &name, int32 value) {
	if (!_vars.contains(name)) {
		warning("Variable '%s' doesn't exist, skipping assignment.\n", name.c_str());
	} else {
		_vars.setVal(name, value);
	}
}

int32 Gfx::getVar(const Common::String &name) {
	int32 v = 0;

	if (!_vars.contains(name)) {
		warning("Variable '%s' doesn't exist, returning default value.\n", name.c_str());
	} else {
		v = _vars.getVal(name);
	}

	return v;
}



#define	LABEL_TRANSPARENT_COLOR 0xFF

void halfbritePixel(int x, int y, int color, void *data) {
	byte *buffer = (byte*)data;
	buffer[x + y * _vm->_screenWidth] &= ~0x20;
}

void drawCircleLine(int xCenter, int yCenter, int x, int y, int color, void (*plotProc)(int, int, int, void *), void *data){
	Graphics::drawLine(xCenter + x, yCenter + y, xCenter - x, yCenter + y, color, plotProc, data);
	Graphics::drawLine(xCenter + x, yCenter - y, xCenter - x, yCenter - y, color, plotProc, data);
	Graphics::drawLine(xCenter + y, yCenter + x, xCenter - y, yCenter + x, color, plotProc, data);
	Graphics::drawLine(xCenter + y, yCenter - x, xCenter - y, yCenter - x, color, plotProc, data);
}

void drawCircle(int xCenter, int yCenter, int radius, int color, void (*plotProc)(int, int, int, void *), void *data) {
	int x = 0;
	int y = radius;
	int p = 1 - radius;

	/* Plot first set of points */
	drawCircleLine(xCenter, yCenter, x, y, color, plotProc, data);

	while (x < y) {
		x++;
		if (p < 0)
			p += 2*x + 1;
		else {
			y--;
			p += 2 * (x-y) + 1;
		}
		drawCircleLine(xCenter, yCenter, x, y, color, plotProc, data);
	}
}




Palette::Palette() {

	int gameType = _vm->getGameType();

	if (gameType == GType_Nippon) {
		_colors = 32;
		_hb = (_vm->getPlatform() == Common::kPlatformAmiga);
	} else
	if (gameType == GType_BRA) {
		_colors = 256;
		_hb = false;
	} else
		error("can't create palette for id = '%i'", gameType);

	_size = _colors * 3;

	makeBlack();
}

Palette::Palette(const Palette &pal) {
	clone(pal);
}

void Palette::clone(const Palette &pal) {
	_colors = pal._colors;
	_hb = pal._hb;
	_size = pal._size;
	memcpy(_data, pal._data, _size);
}


void Palette::makeBlack() {
	memset(_data, 0, _size);
}

void Palette::setEntry(uint index, int red, int green, int blue) {
	assert(index < _colors);

	if (red >= 0)
		_data[index*3] = red & 0xFF;

	if (green >= 0)
		_data[index*3+1] = green & 0xFF;

	if (blue >= 0)
		_data[index*3+2] = blue & 0xFF;
}

void Palette::makeGrayscale() {
	byte v;
	for (uint16 i = 0; i < _colors; i++) {
		v = MAX(_data[i*3+1], _data[i*3+2]);
		v = MAX(v, _data[i*3]);
		setEntry(i, v, v, v);
	}
}

void Palette::fadeTo(const Palette& target, uint step) {

	if (step == 0)
		return;

	for (uint16 i = 0; i < _size; i++) {
		if (_data[i] == target._data[i]) continue;

		if (_data[i] < target._data[i])
			_data[i] = CLIP(_data[i] + step, (uint)0, (uint)target._data[i]);
		else
			_data[i] = CLIP(_data[i] - step, (uint)target._data[i], (uint)255);
	}

	return;
}

uint Palette::fillRGBA(byte *rgba) {

	byte r, g, b;
	byte *hbPal = rgba + _colors * 4;

	for (uint32 i = 0; i < _colors; i++) {
		r = (_data[i*3]   << 2) | (_data[i*3]   >> 4);
		g = (_data[i*3+1] << 2) | (_data[i*3+1] >> 4);
		b = (_data[i*3+2] << 2) | (_data[i*3+2] >> 4);

		rgba[i*4]   = r;
		rgba[i*4+1] = g;
		rgba[i*4+2] = b;
		rgba[i*4+3] = 0;

		if (_hb) {
			hbPal[i*4]   = r >> 1;
			hbPal[i*4+1] = g >> 1;
			hbPal[i*4+2] = b >> 1;
			hbPal[i*4+3] = 0;
		}

	}

	return ((_hb) ? 2 : 1) * _colors;
}

void Palette::rotate(uint first, uint last, bool forward) {

	byte tmp[3];

	if (forward) {					// forward

		tmp[0] = _data[first * 3];
		tmp[1] = _data[first * 3 + 1];
		tmp[2] = _data[first * 3 + 2];

		memmove(_data+first*3, _data+(first+1)*3, (last - first)*3);

		_data[last * 3]	 = tmp[0];
		_data[last * 3 + 1] = tmp[1];
		_data[last * 3 + 2] = tmp[2];

	} else {											// backward

		tmp[0] = _data[last * 3];
		tmp[1] = _data[last * 3 + 1];
		tmp[2] = _data[last * 3 + 2];

		memmove(_data+(first+1)*3, _data+first*3, (last - first)*3);

		_data[first * 3]	  = tmp[0];
		_data[first * 3 + 1] = tmp[1];
		_data[first * 3 + 2] = tmp[2];

	}

}



void Gfx::setPalette(Palette pal) {
	byte sysPal[256*4];

	uint n = pal.fillRGBA(sysPal);
	g_system->setPalette(sysPal, 0, n);
}

void Gfx::setBlackPalette() {
	Palette pal;
	setPalette(pal);
}




void Gfx::animatePalette() {

	// avoid forcing setPalette when not needed
	bool done = false;

	PaletteFxRange *range;
	for (uint16 i = 0; i < 4; i++) {
		range = &_backgroundInfo.ranges[i];

		if ((range->_flags & 1) == 0) continue;		// animated palette
		range->_timer += range->_step * 2;	// update timer

		if (range->_timer < 0x4000) continue;		// check timeout

		range->_timer = 0;							// reset timer

		_palette.rotate(range->_first, range->_last, (range->_flags & 2) != 0);

		done = true;
	}

	if (done) {
		setPalette(_palette);
	}

	return;
}





void Gfx::setHalfbriteMode(bool enable) {
	if (_vm->getPlatform() != Common::kPlatformAmiga) return;
	if (enable == _halfbrite) return;

	_halfbrite = !_halfbrite;
	_hbCircleRadius = 0;
}

#define HALFBRITE_CIRCLE_RADIUS		48
void Gfx::setProjectorPos(int x, int y) {
	_hbCircleRadius = HALFBRITE_CIRCLE_RADIUS;
	_hbCirclePos.x = x + _hbCircleRadius;
	_hbCirclePos.y = y + _hbCircleRadius;
}

void Gfx::setProjectorProgram(int16 *data) {
	_nextProjectorPos = data;
}

void Gfx::drawInventory() {

	if ((_engineFlags & kEngineInventory) == 0) {
		return;
	}

	Common::Rect r;
	_vm->_inventoryRenderer->getRect(r);
	byte *data = _vm->_inventoryRenderer->getData();

	g_system->copyRectToScreen(data, r.width(), r.left, r.top, r.width(), r.height());
}

void Gfx::drawItems() {
	if (_numItems == 0) {
		return;
	}

	Graphics::Surface *surf = g_system->lockScreen();
	for (uint i = 0; i < _numItems; i++) {
		drawGfxObject(_items[i].data, *surf, false);
	}
	g_system->unlockScreen();
}

void Gfx::drawBalloons() {
	if (_balloons.size() == 0) {
		return;
	}

	Graphics::Surface *surf = g_system->lockScreen();
	for (uint i = 0; i < _balloons.size(); i++) {
		drawGfxObject(_balloons[i], *surf, false);
	}
	g_system->unlockScreen();
}

void Gfx::clearScreen() {
	g_system->clearScreen();
}

void Gfx::beginFrame() {

	int32 oldBackgroundMode = _varBackgroundMode;
	_varBackgroundMode = getVar("background_mode");

	if (oldBackgroundMode != _varBackgroundMode) {
		switch (_varBackgroundMode) {
		case 1:
			_bitmapMask.free();
			break;
		case 2:
			_bitmapMask.create(_backgroundInfo.width, _backgroundInfo.height, 1);
			byte *data = (byte*)_bitmapMask.pixels;
			for (uint y = 0; y < _bitmapMask.h; y++) {
				for (uint x = 0; x < _bitmapMask.w; x++) {
					*data++ = _backgroundInfo.mask.getValue(x, y);
				}
			}
			break;
		}
	}


	if (_vm->_screenWidth >= _backgroundInfo.width) {
		_varScrollX = 0;
	} else {
		_varScrollX = getVar("scroll_x");
	}

	_varAnimRenderMode = getRenderMode("anim_render_mode");
	_varMiscRenderMode = getRenderMode("misc_render_mode");
}

int32 Gfx::getRenderMode(const char *type) {

	int32 mode = getVar(type);
	if (mode < 0 || mode > 2) {
		warning("new value for '%s' is wrong: resetting default", type);
		setVar(type, 1);
		mode = 1;
	}
	return mode;

}


void Gfx::updateScreen() {

	// background may not cover the whole screen, so adjust bulk update size
	uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo.width);
	uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo.height);

	byte *backgroundData = 0;
	uint16 backgroundPitch = 0;
	switch (_varBackgroundMode) {
	case 1:
		backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0);
		backgroundPitch = _backgroundInfo.bg.pitch;
		break;
	case 2:
		backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0);
		backgroundPitch = _bitmapMask.pitch;
		break;
	}
	g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h);


	_varRenderMode = _varAnimRenderMode;

	// TODO: transform objects coordinates to be drawn with scrolling
	Graphics::Surface *surf = g_system->lockScreen();
	drawGfxObjects(*surf);

	if (_halfbrite) {
		// FIXME: the implementation of halfbrite is now largely sub-optimal in that a full screen
		// rewrite is needed to apply the effect. Also, we are manipulating the frame buffer. Is it a good idea?
		byte *buf = (byte*)surf->pixels;
		for (int i = 0; i < surf->w*surf->h; i++) {
			*buf++ |= 0x20;
		}
		if (_nextProjectorPos) {
			int16 x = *_nextProjectorPos++;
			int16 y = *_nextProjectorPos++;
			if (x == -1 && y == -1) {
				_nextProjectorPos = 0;
			} else {
				setProjectorPos(x, y);
			}
		}
		if (_hbCircleRadius > 0) {
			drawCircle(_hbCirclePos.x, _hbCirclePos.y, _hbCircleRadius, 0, &halfbritePixel, surf->pixels);
		}
	}

	g_system->unlockScreen();

	_varRenderMode = _varMiscRenderMode;

	drawInventory();
	drawItems();
	drawBalloons();
	drawLabels();

	g_system->updateScreen();
	return;
}



//
//	graphic primitives
//


void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask) {

	Common::Rect r(surf.w, surf.h);
	r.moveTo(x, y);

	uint16 z = (mask) ? _backgroundInfo.getLayer(y) : LAYER_FOREGROUND;
	blt(r, (byte*)surf.pixels, &_backgroundInfo.bg, z, 0);
}

void Gfx::fillBackground(const Common::Rect& r, byte color) {
	_backgroundInfo.bg.fillRect(r, color);
}

void Gfx::invertBackground(const Common::Rect& r) {

	byte *d = (byte*)_backgroundInfo.bg.getBasePtr(r.left, r.top);

	for (int i = 0; i < r.height(); i++) {
		for (int j = 0; j < r.width(); j++) {
			*d ^= 0x1F;
			d++;
		}

		d += (_backgroundInfo.bg.pitch - r.width());
	}

}





void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) {
	surf.create(w, h, 1);
	surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR);
}

uint Gfx::renderFloatingLabel(Font *font, char *text) {

	Graphics::Surface *cnv = new Graphics::Surface;

	uint w, h;

	if (_vm->getPlatform() == Common::kPlatformAmiga) {
		w = font->getStringWidth(text) + 16;
		h = 10;

		setupLabelSurface(*cnv, w, h);

		font->setColor(7);
		font->drawString((byte*)cnv->pixels + 1, cnv->w, text);
		font->drawString((byte*)cnv->pixels + 1 + cnv->w * 2, cnv->w, text);
		font->drawString((byte*)cnv->pixels + cnv->w, cnv->w, text);
		font->drawString((byte*)cnv->pixels + 2 + cnv->w, cnv->w, text);
		font->setColor(1);
		font->drawString((byte*)cnv->pixels + 1 + cnv->w, cnv->w, text);
	} else {
		w = font->getStringWidth(text);
		h = font->height();

		setupLabelSurface(*cnv, w, h);

		drawText(font, cnv, 0, 0, text, 0);
	}

	GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel");
	obj->transparentKey = LABEL_TRANSPARENT_COLOR;
	obj->layer = LAYER_FOREGROUND;

	uint id = _labels.size();
	_labels.insert_at(id, obj);

	return id;
}

void Gfx::showFloatingLabel(uint label) {
	assert(label < _labels.size());

	hideFloatingLabel();

	_labels[label]->x = -1000;
	_labels[label]->y = -1000;
	_labels[label]->setFlags(kGfxObjVisible);

	_floatingLabel = label;
}

void Gfx::hideFloatingLabel() {
	if (_floatingLabel != NO_FLOATING_LABEL) {
		_labels[_floatingLabel]->clearFlags(kGfxObjVisible);
	}
	_floatingLabel = NO_FLOATING_LABEL;
}


void Gfx::updateFloatingLabel() {
	if (_floatingLabel == NO_FLOATING_LABEL) {
		return;
	}

	int16 _si, _di;

	Common::Point	cursor;
	_vm->_input->getCursorPos(cursor);

	Common::Rect r;
	_labels[_floatingLabel]->getRect(0, r);

	if (_vm->_input->_activeItem._id != 0) {
		_si = cursor.x + 16 - r.width()/2;
		_di = cursor.y + 34;
	} else {
		_si = cursor.x + 8 - r.width()/2;
		_di = cursor.y + 21;
	}

	if (_si < 0) _si = 0;
	if (_di > 190) _di = 190;

	if (r.width() + _si > _vm->_screenWidth)
		_si = _vm->_screenWidth - r.width();

	_labels[_floatingLabel]->x = _si;
	_labels[_floatingLabel]->y = _di;
}




uint Gfx::createLabel(Font *font, const char *text, byte color) {
	assert(_labels.size() < MAX_NUM_LABELS);

	Graphics::Surface *cnv = new Graphics::Surface;

	uint w, h;

	if (_vm->getPlatform() == Common::kPlatformAmiga) {
		w = font->getStringWidth(text) + 2;
		h = font->height() + 2;

		setupLabelSurface(*cnv, w, h);

		drawText(font, cnv, 0, 2, text, 0);
		drawText(font, cnv, 2, 0, text, color);
	} else {
		w = font->getStringWidth(text);
		h = font->height();

		setupLabelSurface(*cnv, w, h);

		drawText(font, cnv, 0, 0, text, color);
	}

	GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label");
	obj->transparentKey = LABEL_TRANSPARENT_COLOR;
	obj->layer = LAYER_FOREGROUND;

	int id = _labels.size();

	_labels.insert_at(id, obj);

	return id;
}

void Gfx::showLabel(uint id, int16 x, int16 y) {
	assert(id < _labels.size());
	_labels[id]->setFlags(kGfxObjVisible);

	Common::Rect r;
	_labels[id]->getRect(0, r);

	if (x == CENTER_LABEL_HORIZONTAL) {
		x = CLIP<int16>((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2);
	}

	if (y == CENTER_LABEL_VERTICAL) {
		y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2);
	}

	_labels[id]->x = x;
	_labels[id]->y = y;
}

void Gfx::hideLabel(uint id) {
	assert(id < _labels.size());
	_labels[id]->clearFlags(kGfxObjVisible);
}

void Gfx::freeLabels() {
	for (uint i = 0; i < _labels.size(); i++) {
		delete _labels[i];
	}
	_labels.clear();
	_floatingLabel = NO_FLOATING_LABEL;
}

void Gfx::drawLabels() {
	if (_labels.size() == 0) {
		return;
	}

	updateFloatingLabel();

	Graphics::Surface* surf = g_system->lockScreen();

	for (uint i = 0; i < _labels.size(); i++) {
		drawGfxObject(_labels[i], *surf, false);
	}

	g_system->unlockScreen();
}


void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {

	uint16 lines = 0;
	uint16 w = 0;
	*width = 0;

	uint16 blankWidth = font->getStringWidth(" ");
	uint16 tokenWidth = 0;

	char token[MAX_TOKEN_LEN];

	while (strlen(text) != 0) {

		text = parseNextToken(text, token, MAX_TOKEN_LEN, "   ", true);
		tokenWidth = font->getStringWidth(token);

		w += tokenWidth;

		if (!scumm_stricmp(token, "%p")) {
			lines++;
		} else {
			if (w > maxwidth) {
				w -= tokenWidth;
				lines++;
				if (w > *width)
					*width = w;

				w = tokenWidth;
			}
		}

		w += blankWidth;
		text = Common::ltrim(text);
	}

	if (*width < w) *width = w;
	*width += 10;

	*height = lines * 10 + 20;

	return;
}


void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {

	byte *s = (byte*)src.getBasePtr(r.left, r.top);
	byte *d = (byte*)dst.getBasePtr(0, 0);

	for (uint16 i = 0; i < r.height(); i++) {
		memcpy(d, s, r.width());

		s += src.pitch;
		d += dst.pitch;
	}

	return;
}

void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) {
	copyRect(r, _backgroundInfo.bg, dst);
}


Gfx::Gfx(Parallaction* vm) :
	_vm(vm), _disk(vm->_disk) {

	g_system->beginGFXTransaction();
	g_system->initSize(_vm->_screenWidth, _vm->_screenHeight);
	_vm->initCommonGFX(_vm->getGameType() == GType_BRA);
	g_system->endGFXTransaction();

	setPalette(_palette);

	_numItems = 0;
	_floatingLabel = NO_FLOATING_LABEL;

	_screenX = 0;
	_screenY = 0;

	_halfbrite = false;
	_hbCircleRadius = 0;

	_unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE];
	assert(_unpackedBitmap);

	registerVar("background_mode", 1);
	_varBackgroundMode = 1;

	registerVar("scroll_x", 0);
	_varScrollX = 0;

	registerVar("anim_render_mode", 1);
	registerVar("misc_render_mode", 1);

	return;
}

Gfx::~Gfx() {

	freeBackground();
	freeLabels();

	delete []_unpackedBitmap;

	return;
}



int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) {
	int id = _numItems;

	_items[id].data = frames;
	_items[id].data->x = x;
	_items[id].data->y = y;
	_items[id].data->layer = LAYER_FOREGROUND;
	_items[id].data->transparentKey = transparentColor;

	_numItems++;

	return id;
}

void Gfx::setItemFrame(uint item, uint16 f) {
	assert(item < _numItems);
	_items[item].data->frame = f;
	_items[item].data->setFlags(kGfxObjVisible);
}


GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) {

	GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text);

	obj->layer = LAYER_FOREGROUND;
	obj->frame = 0;
	obj->setFlags(kGfxObjVisible);

	_balloons.push_back(obj);

	return obj;
}

void Gfx::destroyBalloons() {
	for (uint i = 0; i < _balloons.size(); i++) {
		delete _balloons[i];
	}
	_balloons.clear();
}

void Gfx::freeItems() {
	_numItems = 0;
}

void Gfx::freeBackground() {
	_backgroundInfo.free();
}

void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) {

	freeBackground();

	if (type == kBackgroundLocation) {
		_disk->loadScenery(_backgroundInfo, name, mask, path);
		setPalette(_backgroundInfo.palette);
		_palette.clone(_backgroundInfo.palette);
	} else {
		_disk->loadSlide(_backgroundInfo, name);
		setPalette(_backgroundInfo.palette);
	}

}

} // namespace Parallaction
