/* 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/scale.h"
#include "ultima/ultima4/core/utils.h"
#include "common/system.h"

namespace Ultima {
namespace Ultima4 {

Image *scalePoint(Image *src, int scale, int n);
Image *scale2xBilinear(Image *src, int scale, int n);
Image *scale2xSaI(Image *src, int scale, int N);
Image *scaleScale2x(Image *src, int scale, int N);

Scaler scalerGet(const Common::String &filter) {
	if (filter == "point")
		return &scalePoint;
	else if (filter == "2xBi")
		return &scale2xBilinear;
	else if (filter == "2xSaI")
		return &scale2xSaI;
	else if (filter == "Scale2x")
		return &scaleScale2x;
	else
		return nullptr;
}

/**
 * Returns true if the given scaler can scale by 3 (as well as by 2).
 */
int scaler3x(const Common::String &filter) {
	return filter == "Scale2x";
}

/**
 * A simple row and column duplicating scaler.
 */
Image *scalePoint(Image *src, int scale, int n) {
	int x, y, i, j;
	Image *dest;

	dest = Image::create(src->width() * scale, src->height() * scale, src->format());
	if (!dest)
		return nullptr;

	if (dest->isIndexed())
		dest->setPaletteFromImage(src);

	for (y = 0; y < src->height(); y++) {
		for (x = 0; x < src->width(); x++) {
			for (i = 0; i < scale; i++) {
				for (j = 0; j < scale; j++) {
					uint index;
					src->getPixelIndex(x, y, index);
					dest->putPixelIndex(x * scale + j, y * scale + i, index);
				}
			}
		}
	}

	return dest;
}

/**
 * A scaler that interpolates each intervening pixel from it's two
 * neighbors.
 */
Image *scale2xBilinear(Image *src, int scale, int n) {
	int i, x, y, xoff, yoff;
	RGBA a, b, c, d;
	Image *dest;

	/* this scaler works only with images scaled by 2x */
	assertMsg(scale == 2, "invalid scale: %d", scale);

	Graphics::PixelFormat format = src->isIndexed() ? g_system->getScreenFormat() : src->format();
	dest = Image::create(src->width() * scale, src->height() * scale, format);
	if (!dest)
		return nullptr;

	/*
	 * Each pixel in the source image is translated into four in the
	 * destination.  The destination pixels are dependant on the pixel
	 * itself, and the three surrounding pixels (A is the original
	 * pixel):
	 * A B
	 * C D
	 * The four destination pixels mapping to A are calculated as
	 * follows:
	 * [   A   ] [  (A+B)/2  ]
	 * [(A+C)/2] [(A+B+C+D)/4]
	 */

	for (i = 0; i < n; i++) {
		for (y = (src->height() / n) * i; y < (src->height() / n) * (i + 1); y++) {
			if (y == (src->height() / n) * (i + 1) - 1)
				yoff = 0;
			else
				yoff = 1;

			for (x = 0; x < src->width(); x++) {
				if (x == src->width() - 1)
					xoff = 0;
				else
					xoff = 1;

				src->getPixel(x, y, a.r, a.g, a.b, a.a);
				src->getPixel(x + xoff, y, b.r, b.g, b.b, b.a);
				src->getPixel(x, y + yoff, c.r, c.g, c.b, c.a);
				src->getPixel(x + xoff, y + yoff, d.r, d.g, d.b, d.a);

				dest->putPixel(x * 2, y * 2, a.r, a.g, a.b, a.a);
				dest->putPixel(x * 2 + 1, y * 2, (a.r + b.r) >> 1, (a.g + b.g) >> 1, (a.b + b.b) >> 1, (a.a + b.a) >> 1);
				dest->putPixel(x * 2, y * 2 + 1, (a.r + c.r) >> 1, (a.g + c.g) >> 1, (a.b + c.b) >> 1, (a.a + c.a) >> 1);
				dest->putPixel(x * 2 + 1, y * 2 + 1, (a.r + b.r + c.r + d.r) >> 2, (a.g + b.g + c.g + d.g) >> 2, (a.b + b.b + c.b + d.b) >> 2, (a.a + b.a + c.a + d.a) >> 2);
			}
		}
	}

	return dest;
}

int colorEqual(RGBA a, RGBA b) {
	return
	    a.r == b.r &&
	    a.g == b.g &&
	    a.b == b.b &&
	    a.a == b.a;
}

RGBA colorAverage(RGBA a, RGBA b) {
	RGBA result;
	result.r = (a.r + b.r) >> 1;
	result.g = (a.g + b.g) >> 1;
	result.b = (a.b + b.b) >> 1;
	result.a = (a.a + b.a) >> 1;
	return result;
}

int _2xSaI_GetResult1(RGBA a, RGBA b, RGBA c, RGBA d) {
	int x = 0;
	int y = 0;
	int r = 0;
	if (colorEqual(a, c)) x++;
	else if (colorEqual(b, c)) y++;
	if (colorEqual(a, d)) x++;
	else if (colorEqual(b, d)) y++;
	if (x <= 1) r++;
	if (y <= 1) r--;
	return r;
}

int _2xSaI_GetResult2(RGBA a, RGBA b, RGBA c, RGBA d) {
	int x = 0;
	int y = 0;
	int r = 0;
	if (colorEqual(a, c)) x++;
	else if (colorEqual(b, c)) y++;
	if (colorEqual(a, d)) x++;
	else if (colorEqual(b, d)) y++;
	if (x <= 1) r--;
	if (y <= 1) r++;
	return r;
}

/**
 * A more sophisticated scaler that interpolates each new pixel the
 * surrounding pixels.
 */
Image *scale2xSaI(Image *src, int scale, int N) {
	int ii, x, y, xoff0, xoff1, xoff2, yoff0, yoff1, yoff2;
	RGBA a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p;
	RGBA prod0, prod1, prod2;
	Image *dest;

	/* this scaler works only with images scaled by 2x */
	assertMsg(scale == 2, "invalid scale: %d", scale);

	Graphics::PixelFormat format = src->isIndexed() ? g_system->getScreenFormat() : src->format();
	dest = Image::create(src->width() * scale, src->height() * scale, format);
	if (!dest)
		return nullptr;

	/*
	 * Each pixel in the source image is translated into four in the
	 * destination.  The destination pixels are dependant on the pixel
	 * itself, and the surrounding pixels as shown below (A is the
	 * original pixel):
	 * I E F J
	 * G A B K
	 * H C D L
	 * M N O P
	 */

	for (ii = 0; ii < N; ii++) {
		for (y = (src->height() / N) * ii; y < (src->height() / N) * (ii + 1); y++) {
			if (y == 0)
				yoff0 = 0;
			else
				yoff0 = -1;
			if (y == (src->height() / N) * (ii + 1) - 1) {
				yoff1 = 0;
				yoff2 = 0;
			} else if (y == (src->height() / N) * (ii + 1) - 2) {
				yoff1 = 1;
				yoff2 = 1;
			} else {
				yoff1 = 1;
				yoff2 = 2;
			}


			for (x = 0; x < src->width(); x++) {
				if (x == 0)
					xoff0 = 0;
				else
					xoff0 = -1;
				if (x == src->width() - 1) {
					xoff1 = 0;
					xoff2 = 0;
				} else if (x == src->width() - 2) {
					xoff1 = 1;
					xoff2 = 1;
				} else {
					xoff1 = 1;
					xoff2 = 2;
				}

				src->getPixel(x, y, a.r, a.g, a.b, a.a);
				src->getPixel(x + xoff1, y, b.r, b.g, b.b, b.a);
				src->getPixel(x, y + yoff1, c.r, c.g, c.b, c.a);
				src->getPixel(x + xoff1, y + yoff1, d.r, d.g, d.b, d.a);

				src->getPixel(x, y + yoff0, e.r, e.g, e.b, e.a);
				src->getPixel(x + xoff1, y + yoff0, f.r, f.g, f.b, f.a);
				src->getPixel(x + xoff0, y, g.r, g.g, g.b, g.a);
				src->getPixel(x + xoff0, y + yoff1, h.r, h.g, h.b, h.a);

				src->getPixel(x + xoff0, y + yoff0, i.r, i.g, i.b, i.a);
				src->getPixel(x + xoff2, y + yoff0, j.r, j.g, j.b, j.a);
				src->getPixel(x + xoff0, y, k.r, k.g, k.b, k.a);
				src->getPixel(x + xoff0, y + yoff1, l.r, l.g, l.b, l.a);

				src->getPixel(x + xoff0, y + yoff2, m.r, m.g, m.b, m.a);
				src->getPixel(x, y + yoff2, n.r, n.g, n.b, n.a);
				src->getPixel(x + xoff1, y + yoff2, o.r, o.g, o.b, o.a);
				src->getPixel(x + xoff2, y + yoff2, p.r, p.g, p.b, p.a);

				if (colorEqual(a, d) && !colorEqual(b, c)) {
					if ((colorEqual(a, e) && colorEqual(b, l)) ||
					        (colorEqual(a, c) && colorEqual(a, f) && !colorEqual(b, e) && colorEqual(b, j)))
						prod0 = a;
					else
						prod0 = colorAverage(a, b);

					if ((colorEqual(a, g) && colorEqual(c, o)) ||
					        (colorEqual(a, b) && colorEqual(a, h) && !colorEqual(g, c) && colorEqual(c, m)))
						prod1 = a;
					else
						prod1 = colorAverage(a, c);

					prod2 = a;
				} else if (colorEqual(b, c) && !colorEqual(a, d)) {
					if ((colorEqual(b, f) && colorEqual(a, h)) ||
					        (colorEqual(b, e) && colorEqual(b, d) && !colorEqual(a, f) && colorEqual(a, i)))
						prod0 = b;
					else
						prod0 = colorAverage(a, b);

					if ((colorEqual(c, h) && colorEqual(a, f)) ||
					        (colorEqual(c, g) && colorEqual(c, d) && !colorEqual(a, h) && colorEqual(a, i)))
						prod1 = c;
					else
						prod1 = colorAverage(a, c);

					prod2 = b;
				} else if (colorEqual(a, d) && colorEqual(b, c)) {
					if (colorEqual(a, b))
						prod0 = prod1 = prod2 = a;
					else {
						int r = 0;
						prod0 = colorAverage(a, b);
						prod1 = colorAverage(a, c);

						r += _2xSaI_GetResult1(a, b, g, e);
						r += _2xSaI_GetResult2(b, a, k, f);
						r += _2xSaI_GetResult2(b, a, h, n);
						r += _2xSaI_GetResult1(a, b, l, o);

						if (r > 0)
							prod2 = a;
						else if (r < 0)
							prod2 = b;
						else {
							prod2.r = (a.r + b.r + c.r + d.r) >> 2;
							prod2.g = (a.g + b.g + c.g + d.g) >> 2;
							prod2.b = (a.b + b.b + c.b + d.b) >> 2;
						}
					}
				} else {
					if (colorEqual(a, c) && colorEqual(a, f) && !colorEqual(b, e) && colorEqual(b, j))
						prod0 = a;
					else if (colorEqual(b, e) && colorEqual(b, d) && !colorEqual(a, f) && colorEqual(a, i))
						prod0 = b;
					else
						prod0 = colorAverage(a, b);

					if (colorEqual(a, b) && colorEqual(a, h) && !colorEqual(g, c) && colorEqual(c, m))
						prod1 = a;
					else if (colorEqual(c, g) && colorEqual(c, d) && !colorEqual(a, h) && colorEqual(a, i))
						prod1 = c;
					else
						prod1 = colorAverage(a, c);

					prod2.r = (a.r + b.r + c.r + d.r) >> 2;
					prod2.g = (a.g + b.g + c.g + d.g) >> 2;
					prod2.b = (a.b + b.b + c.b + d.b) >> 2;
					prod2.a = 255;
				}

				dest->putPixel((x << 1), (y << 1), a.r, a.g, a.b, a.a);
				dest->putPixel((x << 1) + 1, (y << 1), prod0.r, prod0.g, prod0.b, prod0.a);
				dest->putPixel((x << 1), (y << 1) + 1, prod1.r, prod1.g, prod1.b, prod1.a);
				dest->putPixel((x << 1) + 1, (y << 1) + 1, prod2.r, prod2.g, prod2.b, prod2.a);
			}
		}
	}

	return dest;
}

/**
 * A more sophisticated scaler that doesn't interpolate, but avoids
 * the stair step effect by detecting angles.
 */
Image *scaleScale2x(Image *src, int scale, int n) {
	int ii, x, y, xoff0, xoff1, yoff0, yoff1;
	RGBA a, b, c, d, e, f, g, h, i;
	RGBA e0, e1, e2, e3;
	RGBA e4, e5, e6, e7;
	Image *dest;

	/* this scaler works only with images scaled by 2x or 3x */
	assertMsg(scale == 2 || scale == 3, "invalid scale: %d", scale);

	dest = Image::create(src->width() * scale, src->height() * scale, src->format());
	if (!dest)
		return nullptr;

	if (dest->isIndexed())
		dest->setPaletteFromImage(src);

	/*
	 * Each pixel in the source image is translated into four (or
	 * nine) in the destination.  The destination pixels are dependant
	 * on the pixel itself, and the eight surrounding pixels (E is the
	 * original pixel):
	 *
	 * A B C
	 * D E F
	 * G H I
	 */

	for (ii = 0; ii < n; ii++) {
		for (y = (src->height() / n) * ii; y < (src->height() / n) * (ii + 1); y++) {
			if (y == 0)
				yoff0 = 0;
			else
				yoff0 = -1;
			if (y == (src->height() / n) * (ii + 1) - 1)
				yoff1 = 0;
			else
				yoff1 = 1;

			for (x = 0; x < src->width(); x++) {
				if (x == 0)
					xoff0 = 0;
				else
					xoff0 = -1;
				if (x == src->width() - 1)
					xoff1 = 0;
				else
					xoff1 = 1;

				src->getPixel(x + xoff0, y + yoff0, a.r, a.g, a.b, a.a);
				src->getPixel(x, y + yoff0, b.r, b.g, b.b, b.a);
				src->getPixel(x + xoff1, y + yoff0, c.r, c.g, c.b, c.a);

				src->getPixel(x + xoff0, y, d.r, d.g, d.b, d.a);
				src->getPixel(x, y, e.r, e.g, e.b, e.a);
				src->getPixel(x + xoff1, y, f.r, f.g, f.b, f.a);

				src->getPixel(x + xoff0, y + yoff1, g.r, g.g, g.b, g.a);
				src->getPixel(x, y + yoff1, h.r, h.g, h.b, h.a);
				src->getPixel(x + xoff1, y + yoff1, i.r, i.g, i.b, i.a);

				// lissen diagonals (45°,135°,225°,315°)
				// corner : if there is gradient towards a diagonal direction,
				// take the color of surrounding points in this direction
				e0 = colorEqual(d, b) && (!colorEqual(b, f)) && (!colorEqual(d, h)) ? d : e;
				e1 = colorEqual(b, f) && (!colorEqual(b, d)) && (!colorEqual(f, h)) ? f : e;
				e2 = colorEqual(d, h) && (!colorEqual(d, b)) && (!colorEqual(h, f)) ? d : e;
				e3 = colorEqual(h, f) && (!colorEqual(d, h)) && (!colorEqual(b, f)) ? f : e;

				// lissen eight more directions (22° or 67°, 112° or 157°...)
				// middle of side : if there is a gradient towards one of these directions (middle of side direction and of direction of either diagonal around this side),
				// take the color of surrounding points in this direction
				e4 = colorEqual(e0, c) ? e0 : colorEqual(e1, a) ? e1 : e;
				e5 = colorEqual(e2, a) ? e2 : colorEqual(e0, g) ? e0 : e;
				e6 = colorEqual(e1, i) ? e1 : colorEqual(e3, c) ? e3 : e;
				e7 = colorEqual(e3, g) ? e3 : colorEqual(e2, i) ? e2 : e;

				if (scale == 2) {
					dest->putPixel(x * 2, y * 2, e0.r, e0.g, e0.b, e0.a);
					dest->putPixel(x * 2 + 1, y * 2, e1.r, e1.g, e1.b, e1.a);
					dest->putPixel(x * 2, y * 2 + 1, e2.r, e2.g, e2.b, e2.a);
					dest->putPixel(x * 2 + 1, y * 2 + 1, e3.r, e3.g, e3.b, e3.a);
				} else if (scale == 3) {
					dest->putPixel(x * 3, y * 3, e0.r, e0.g, e0.b, e0.a);
					dest->putPixel(x * 3 + 1, y * 3, e4.r, e4.g, e4.b, e4.a);
					dest->putPixel(x * 3 + 2, y * 3, e1.r, e1.g, e1.b, e1.a);
					dest->putPixel(x * 3, y * 3 + 1, e5.r, e5.g, e5.b, e5.a);
					dest->putPixel(x * 3 + 1, y * 3 + 1, e.r, e.g, e.b, e.a);
					dest->putPixel(x * 3 + 2, y * 3 + 1, e6.r, e6.g, e6.b, e6.a);
					dest->putPixel(x * 3, y * 3 + 2, e2.r, e2.g, e2.b, e2.a);
					dest->putPixel(x * 3 + 1, y * 3 + 2, e7.r, e7.g, e7.b, e7.a);
					dest->putPixel(x * 3 + 2, y * 3 + 2, e3.r, e3.g, e3.b, e3.a);

				}
			}
		}
	}

	return dest;
}

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