/*
Copyright (C) 2005 The Pentagram Team
Copyright (C) 2010-2022 The Exult Team

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "pent_include.h"

#include "PointScaler.h"

#include "ignore_unused_variable_warning.h"
#include "manip.h"

#include <cstring>

namespace Pentagram {

	// Very very simple point scaler
	template <class uintX, class Manip, class uintS = uintX>
	class PointScalerInternal {
		static void WritePix(uint8* dest, uintX p) {
			std::memcpy(dest, &p, sizeof(uintX));
		}

	public:
		static bool Scale(
				SDL_Surface* tex, sint32 sx, sint32 sy, sint32 sw, sint32 sh,
				uint8* pixel, sint32 dw, sint32 dh, sint32 pitch,
				bool clamp_src) {
			ignore_unused_variable_warning(clamp_src);
			// Source buffer pointers
			const int tpitch = tex->pitch / sizeof(uintS);
			uintS*    texel
					= static_cast<uintS*>(tex->pixels) + (sy * tpitch + sx);
			uintS*    tline_end = texel + sw;
			uintS*    tex_end   = texel + sh * tpitch;
			const int tex_diff  = tpitch - sw;

			// First detect integer up scalings, since they are 'easy'
			const bool x_intscale = ((dw / sw) * sw) == dw;
			const bool y_intscale = ((dh / sh) * sh) == dh;

			//
			// 2x
			//
			if ((sw * 2 == dw) && (sh * 2 == dh)) {
				uint8*    pixel2 = pixel + pitch;
				const int p_diff = (pitch * 2) - (dw * sizeof(uintX));

				// Src Loop Y
				do {
					// Src Loop X
					do {
						uintX p = Manip::copy(*texel);

						WritePix(pixel + 0, p);
						WritePix(pixel + sizeof(uintX), p);
						WritePix(pixel2 + 0, p);
						WritePix(pixel2 + sizeof(uintX), p);
						pixel += sizeof(uintX) * 2;
						pixel2 += sizeof(uintX) * 2;
						texel++;
					} while (texel != tline_end);

					pixel += p_diff;
					pixel2 += p_diff;

					texel += tex_diff;
					tline_end += tpitch;
				} while (texel != tex_end);

			}
			//
			// Integer scaling, x and y
			//
			else if (x_intscale && y_intscale) {
				const int xf = dw / sw;
				const int yf = dh / sh;

				uint8* px_end = pixel + xf * sizeof(uintX);
				uint8* py_end = pixel + yf * pitch;

				const int block_w     = xf * sizeof(uintX);
				const int block_h     = pitch * yf;
				const int block_xdiff = pitch - block_w;
				const int p_diff      = block_h - dw * sizeof(uintX);

				// Src Loop Y
				do {
					// Src Loop X
					do {
						uintX p = Manip::copy(*texel);

						//
						// Inner loops
						//

						// Dest Loop Y
						do {
							// Dest Loop X
							do {
								WritePix(pixel, p);
								pixel += sizeof(uintX);
							} while (pixel != px_end);

							pixel += block_xdiff;
							px_end += pitch;
						} while (pixel != py_end);

						pixel += block_w - block_h;
						px_end += block_w - block_h;
						py_end += block_w;
						texel++;
					} while (texel != tline_end);

					pixel += p_diff;
					py_end += p_diff;
					px_end += p_diff;

					texel += tex_diff;
					tline_end += tpitch;
				} while (texel != tex_end);

			}
			//
			// 2x X and Arbitrary Upscaled Y
			// Specifically to handle 320x200 -> 640x480
			//
			else if ((sw * 2 == dw) && (dh >= sh)) {
				uint32 pos_y;
				uint32 end_y      = dh;
				uint32 dst_y      = 0;
				uint8* next_block = nullptr;

				// Src Loop Y
				do {
					next_block = pixel;

					// Src Loop X
					do {
						pos_y = dst_y;

						uintX p = Manip::copy(*texel);

						//
						// Inner loops
						//
						pixel      = next_block;
						next_block = next_block + sizeof(uintX) * 2;

						// Dest Loop Y
						do {
							WritePix(pixel + 0, p);
							WritePix(pixel + sizeof(uintX), p);
							pixel += pitch;
							pos_y += sh;
						} while (pos_y < end_y);

						texel++;
					} while (texel != tline_end);

					pixel -= sizeof(uintX) * (dw - 2);
					dst_y = pos_y;
					end_y += dh;

					texel += tex_diff;
					tline_end += tpitch;
				} while (texel != tex_end);
			}
			//
			// 1x X and Arbitrary Upscaled Y
			// Specifically to handle 640x400 -> 640x480
			//
			else if ((sw == dw) && (dh >= sh)) {
				uint32 pos_y;
				uint32 end_y      = dh;
				uint32 dst_y      = 0;
				uint8* next_block = nullptr;

				// Src Loop Y
				do {
					next_block = pixel;

					// Src Loop X
					do {
						pos_y = dst_y;

						uintX p = Manip::copy(*texel);

						//
						// Inner loops
						//
						pixel      = next_block;
						next_block = next_block + sizeof(uintX);

						// Dest Loop Y
						do {
							WritePix(pixel, p);
							pixel += pitch;
							pos_y += sh;
						} while (pos_y < end_y);

						texel++;
					} while (texel != tline_end);

					pixel -= sizeof(uintX) * (dw - 1);
					dst_y = pos_y;
					end_y += dh;

					texel += tex_diff;
					tline_end += tpitch;
				} while (texel != tex_end);
			}
			//
			// Arbitrary scaling X and Y (optimized for upscaling)
			//
			else {
				uint32 pos_y           = 0;
				uint32 pos_x           = 0;
				uint32 end_y           = dh;
				uint32 dst_y           = 0;
				uint8* blockline_start = nullptr;
				uint8* next_block      = nullptr;

				// Src Loop Y
				do {
					uint32 end_x = dw;
					uint32 dst_x = 0;

					next_block = pixel;

					// Src Loop X
					do {
						pos_y = dst_y;

						uintX p = Manip::copy(*texel);

						//
						// Inner loops
						//
						blockline_start = next_block;
						next_block      = nullptr;

						// Dest Loop Y
						while (pos_y < end_y) {
							pos_x = dst_x;
							pixel = blockline_start;

							// Dest Loop X
							while (pos_x < end_x) {
								WritePix(pixel, p);
								pixel += sizeof(uintX);
								pos_x += sw;
							}
							if (!next_block) {
								next_block = pixel;
							}

							blockline_start += pitch;

							pos_y += sh;
						}

						dst_x = pos_x;
						end_x += dw;
						texel++;
					} while (texel != tline_end);

					pixel += pitch - sizeof(uintX) * (dw);

					dst_y = pos_y;
					end_y += dh;

					texel += tex_diff;
					tline_end += tpitch;
				} while (texel != tex_end);
			}

			return true;
		}
	};

	PointScaler::PointScaler() {
		Scale8To8   = PointScalerInternal<uint8, Manip8to8, uint8>::Scale;
		Scale8To32  = PointScalerInternal<uint32, Manip8to32, uint8>::Scale;
		Scale32To32 = PointScalerInternal<uint32, Manip32to32, uint32>::Scale;

		Scale8To16  = PointScalerInternal<uint16, Manip8to16, uint8>::Scale;
		Scale8To555 = PointScalerInternal<uint16, Manip8to555, uint8>::Scale;
		Scale8To565 = PointScalerInternal<uint16, Manip8to565, uint8>::Scale;

		Scale16To16 = PointScalerInternal<uint16, Manip16to16, uint16>::Scale;
		Scale555To555
				= PointScalerInternal<uint16, Manip555to555, uint16>::Scale;
		Scale565To565
				= PointScalerInternal<uint16, Manip565to565, uint16>::Scale;
	}

	uint32 PointScaler::ScaleBits() const {
		return 0xFFFFFFFF;
	}

	bool PointScaler::ScaleArbitrary() const {
		return true;
	}

	const char* PointScaler::ScalerName() const {
		return "point";
	}

	const char* PointScaler::ScalerDesc() const {
		return "Fast Simple Point Scaler";
	}

	const char* PointScaler::ScalerCopyright() const {
		return "Copyright (C) 2005 The Pentagram Team, 2010 The Exult Team";
	}

}    // namespace Pentagram
