/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/shared/std/containers.h"
#include "common/system.h"
#include "common/list.h"
#include "common/textconsole.h"
#include "graphics/screen.h"

namespace Ultima {
namespace Ultima4 {

Image::Image() : _surface(nullptr), _disposeAfterUse(DisposeAfterUse::NO),
		_paletted(false) {
}

Image *Image::create(int w, int h) {
	Image *im = new Image();
	im->createInternal(w, h, Graphics::PixelFormat::createFormatCLUT8());

	return im;
}

Image *Image::create(int w, int h, const Graphics::PixelFormat &format) {
	Image *im = new Image();
	im->createInternal(w, h, format);

	return im;
}

void Image::createInternal(int w, int h, const Graphics::PixelFormat &format) {
	_paletted = format.isCLUT8();
	_surface = new Graphics::ManagedSurface(w, h, format);
	_disposeAfterUse = DisposeAfterUse::YES;
}

void Image::blitFrom(const Graphics::Surface &src) {
	_surface->blitFrom(src);
}

Image *Image::createScreenImage() {
	Image *screen = new Image();
	screen->_surface = g_screen;
	screen->_disposeAfterUse = DisposeAfterUse::NO;
	screen->_paletted = false;

	return screen;
}

Image *Image::duplicate(Image *image, const Graphics::PixelFormat &format) {
	bool alphaOn = image->isAlphaOn();
	Image *im = create(image->width(), image->height(), format);

	if (im->isIndexed())
		im->setPaletteFromImage(image);

	/* Turn alpha off before blitting to non-screen surfaces */
	if (alphaOn)
		image->alphaOff();

	image->drawOn(im, 0, 0);

	if (alphaOn)
		image->alphaOn();

	im->_backgroundColor = image->_backgroundColor;

	return im;
}

Image::~Image() {
	if (_disposeAfterUse == DisposeAfterUse::YES)
		delete _surface;
}

void Image::setPalette(const byte *colors, unsigned n_colors) {
	assertMsg(_paletted, "imageSetPalette called on non-paletted image");
	_surface->setPalette(colors, 0, n_colors);
}

void Image::setPaletteFromImage(const Image *src) {
	assertMsg(_paletted && src->_paletted, "imageSetPaletteFromImage called on non-indexed image");

	uint8 srcPal[Graphics::PALETTE_COUNT * 3];
	src->_surface->grabPalette(srcPal, 0, Graphics::PALETTE_COUNT);
	_surface->setPalette(srcPal, 0, Graphics::PALETTE_COUNT);
}

RGBA Image::getPaletteColor(int index) {
	RGBA color = RGBA(0, 0, 0, 0);

	if (_paletted) {
		uint8 pal[1 * 3];
		_surface->grabPalette(pal, index, 1);
		color.r = pal[0];
		color.g = pal[1];
		color.b = pal[2];
		color.a = IM_OPAQUE;
	}

	return color;
}

RGBA Image::setColor(uint8 r, uint8 g, uint8 b, uint8 a) {
	RGBA color = RGBA(r, g, b, a);
	return color;
}

bool Image::setFontColor(ColorFG fg, ColorBG bg) {
	if (!setFontColorFG(fg)) return false;
	if (!setFontColorBG(bg)) return false;
	return true;
}

bool Image::setFontColorFG(ColorFG fg) {
	switch (fg) {
	case FG_GREY:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   153, 153, 153)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 102, 102, 102)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	case FG_BLUE:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   102, 102, 255)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 51, 51, 204)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	case FG_PURPLE:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   255, 102, 255)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 51, 204)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	case FG_GREEN:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   102, 255, 102)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 0, 153, 0)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	case FG_RED:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   255, 102, 102)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 51, 51)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	case FG_YELLOW:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   255, 255, 51)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 153, 51)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    51, 51, 51)) return false;
		break;
	default:
		if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX,   255, 255, 255)) return false;
		if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 204, 204)) return false;
		if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX,    68, 68, 68)) return false;
	}
	return true;
}

bool Image::setFontColorBG(ColorBG bg) {
	switch (bg) {
	case BG_BRIGHT:
		if (!setPaletteIndex(TEXT_BG_INDEX, 0, 0, 102))
			return false;
		break;
	default:
		if (!setPaletteIndex(TEXT_BG_INDEX, 0, 0, 0))
			return false;
	}
	return true;
}

bool Image::setPaletteIndex(uint index, uint8 r, uint8 g, uint8 b) {
	if (!_paletted)
		return false;

	const uint8 palette[1 * 3] = {
		r, g, b
	};
	_surface->setPalette(palette, index, 1);

	// success
	return true;
}

bool Image::getTransparentIndex(uint &index) const {
	if (!_paletted || !_surface->hasTransparentColor())
		return false;

	index = _surface->getTransparentColor();
	return true;
}

void Image::initializeToBackgroundColor(RGBA backgroundColor) {
	if (_paletted)
		error("initializeToBackgroundColor: Not supported");

	_backgroundColor = backgroundColor;
	fillRect(0, 0, _surface->w, _surface->h, backgroundColor.r,
		backgroundColor.g, backgroundColor.b, backgroundColor.a);
}

bool Image::isAlphaOn() const {
	return !_paletted;
}

void Image::alphaOn() {
}

void Image::alphaOff() {
}

void Image::putPixel(int x, int y, int r, int g, int b, int a) {
	uint32 color = getColor(r, g, b, a);
	_surface->setPixel(x, y, color);
}

uint Image::getColor(byte r, byte g, byte b, byte a) {
	uint color;

	if (_surface->format.bytesPerPixel == 1) {
		uint8 pal[256 * 3];
		_surface->grabPalette(pal, 0, 256);
		for (color = 0; color <= 0xfe; ++color) {
			byte rv = pal[(color * 3) + 0];
			byte gv = pal[(color * 3) + 1] & 0xff;
			byte bv = pal[(color * 3) + 2] & 0xff;
			if (r == rv && g == gv && b == bv)
				break;
		}

		return color;
	} else {
		return _surface->format.ARGBToColor(a, r, g, b);
	}
}

void Image::makeBackgroundColorTransparent(int haloSize, int shadowOpacity) {
	uint32 bgColor = _surface->format.ARGBToColor(
	                     static_cast<byte>(_backgroundColor.a),
	                     static_cast<byte>(_backgroundColor.r),
	                     static_cast<byte>(_backgroundColor.g),
	                     static_cast<byte>(_backgroundColor.b)
	                 );

	performTransparencyHack(bgColor, 1, 0, haloSize, shadowOpacity);
}

void Image::performTransparencyHack(uint colorValue, uint numFrames,
									uint currentFrameIndex, uint haloWidth,
									uint haloOpacityIncrementByPixelDistance) {
	Common::List<Common::Pair<uint, uint> > opaqueXYs;
	int x, y;
	byte t_r, t_g, t_b;

	_surface->format.colorToRGB(colorValue, t_r, t_g, t_b);

	int frameHeight = _surface->h / numFrames;
	//Min'd so that they never go out of range (>=h)
	int top = MIN(_surface->h, (int16)(currentFrameIndex * frameHeight));
	int bottom = MIN(_surface->h, (int16)(top + frameHeight));

	for (y = top; y < bottom; y++) {

		for (x = 0; x < _surface->w; x++) {
			uint r, g, b, a;
			getPixel(x, y, r, g, b, a);
			if (r == t_r &&
			        g == t_g &&
			        b == t_b) {
				putPixel(x, y, r, g, b, IM_TRANSPARENT);
			} else {
				putPixel(x, y, r, g, b, a);
				if (haloWidth)
					opaqueXYs.push_back(Common::Pair<uint, uint>(x, y));
			}
		}
	}
	int ox, oy;
	for (Common::List<Common::Pair<uint, uint> >::iterator xy = opaqueXYs.begin();
	        xy != opaqueXYs.end();
	        ++xy) {
		ox = xy->first;
		oy = xy->second;
		int span = int(haloWidth);
		int x_start = MAX(0, ox - span);
		int x_finish = MIN(int(_surface->w), ox + span + 1);
		for (x = x_start; x < x_finish; ++x) {
			int y_start = MAX(int(top), oy - span);
			int y_finish = MIN(int(bottom), oy + span + 1);
			for (y = y_start; y < y_finish; ++y) {

				int divisor = 1 + span * 2 - abs(int(ox - x)) - abs(int(oy - y));

				uint r, g, b, a;
				getPixel(x, y, r, g, b, a);
				if (a != IM_OPAQUE) {
					putPixel(x, y, r, g, b, MIN(IM_OPAQUE, a + haloOpacityIncrementByPixelDistance / divisor));
				}
			}
		}
	}


}

void Image::setTransparentIndex(uint index) {
	if (_paletted)
		_surface->setTransparentColor(index);
}

void Image::putPixelIndex(int x, int y, uint index) {
	_surface->setPixel(x, y, index);
}

void Image::fillRect(int x, int y, int w, int h, int r, int g, int b, int a) {
	uint32 color = getColor(r, g, b, a);
	_surface->fillRect(Common::Rect(x, y, x + w, y + h), color);
}

void Image::getPixel(int x, int y, uint &r, uint &g, uint &b, uint &a) const {
	uint index;
	byte r1, g1, b1, a1;

	index = _surface->getPixel(x, y);

	if (_surface->format.bytesPerPixel == 1) {
		uint8 pal[1 * 3];
		_surface->grabPalette(pal, index, 1);
		r = pal[0];
		g = pal[1];
		b = pal[2];
		a = IM_OPAQUE;
	} else {
		_surface->format.colorToARGB(index, a1, r1, g1, b1);
		r = r1;
		g = g1;
		b = b1;
		a = a1;
	}
}

void Image::getPixelIndex(int x, int y, uint &index) const {
	index = _surface->getPixel(x, y);
}

Graphics::ManagedSurface *Image::getSurface(Image *d) const {
	if (d)
		return d->_surface;

	return g_screen;
}

void Image::drawOn(Image *d, int x, int y) const {
	Graphics::ManagedSurface *destSurface = getSurface(d);
	destSurface->blitFrom(*_surface, Common::Point(x, y));
}

void Image::drawSubRectOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const {
	Graphics::ManagedSurface *destSurface = getSurface(d);

	Common::Rect srcRect(rx, ry, MIN(rx + rw, (int)_surface->w), MIN(ry + rh, (int)_surface->h));
	Common::Point destPos(x, y);

	// Handle when the source rect is off the surface
	if (srcRect.left < 0) {
		destPos.x += -srcRect.left;
		srcRect.left = 0;
	}

	if (srcRect.top < 0) {
		destPos.y += -srcRect.top;
		srcRect.top = 0;
	}

	if (srcRect.isValidRect())
		// Blit the surface
		destSurface->blitFrom(*_surface, srcRect, destPos);
}

void Image::drawSubRectInvertedOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const {
	Graphics::ManagedSurface *destSurface = getSurface(d);
	int i;
	Common::Rect src;
	Common::Point destPos;

	for (i = 0; i < rh; i++) {
		src.left = rx;
		src.top = ry + i;
		src.right = rx + rw;
		src.bottom = ry + i + 1;

		destPos.x = x;
		destPos.y = y + rh - i - 1;

		destSurface->blitFrom(*_surface, src, destPos);
	}
}

void Image::dump() {
	g_screen->blitFrom(*_surface, Common::Point(0, 0));
	g_screen->update();
}

void Image::drawHighlighted() {
	RGBA c;
	for (int i = 0; i < _surface->h; i++) {
		for (int j = 0; j < _surface->w; j++) {
			getPixel(j, i, c.r, c.g, c.b, c.a);
			putPixel(j, i, 0xff - c.r, 0xff - c.g, 0xff - c.b, c.a);
		}
	}
}

} // End of namespace Ultima4
} // End of namespace Ultima
