/*
 *  Copyright (C) 2009-2022 Exult Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA  02111-1307, USA.
 */

#ifndef INCL_SCALE_BILINEAR_H
#define INCL_SCALE_BILINEAR_H 1

/**
 ** Note: This file should only be included by source files that use the
 ** templates below; the templates will only be instantiated when they
 ** are used anyway.
 **/

using COMPONENT = unsigned int;

// fill `row' with the the disassembled color components from the original
// pixel values in `from'; if we run out of source pixels, just keep copying
// the last one we got
template <class Source_pixel, class Manip_pixels>
inline void fill_rgb_row(
		Source_pixel* from,
		int           src_width,    // number of pixels to read from 'from'
		COMPONENT*    row,
		int           width,    // number of pixels to write into 'row'
		const Manip_pixels& manip) {
	COMPONENT* copy_start = row + src_width * 3;
	COMPONENT* all_stop   = row + width * 3;
	while (row < copy_start) {
		COMPONENT& r = *row++;
		COMPONENT& g = *row++;
		COMPONENT& b = *row++;
		manip.split_source(*from++, r, g, b);
	}
	// any remaining elements to be written to 'row' are a replica of the
	// preceding pixel
	COMPONENT* p = row - 3;
	while (row < all_stop) {
		// we're guaranteed three elements per pixel; could unroll the loop
		// further, especially with a Duff's Device, but the gains would be
		// probably limited (judging by profiler output)
		*row++ = *p++;
		*row++ = *p++;
		*row++ = *p++;
	}
}

template <class Source_pixel, class Dest_pixel, class Manip_pixels>
void Scale_2xBilinear(
		Source_pixel* source,                // ->source pixels.
		int srcx, int srcy,                  // Start of rectangle within src.
		int srcw, int srch,                  // Dims. of rectangle.
		int                 sline_pixels,    // Pixels (words)/line for source.
		int                 sheight,         // Source height.
		Dest_pixel*         dest,            // ->dest pixels.
		int                 dline_pixels,    // Pixels (words)/line for dest.
		const Manip_pixels& manip            // Manipulator methods.
) {
	Source_pixel* from   = source + srcy * sline_pixels + srcx;
	Dest_pixel*   to     = dest + 2 * srcy * dline_pixels + 2 * srcx;
	Dest_pixel*   to_odd = to + dline_pixels;

	// the following are static because we don't want to be freeing and
	// reallocating space on each call, as new[]s are usually very
	// expensive; we do allow it to grow though
	static int        buff_size    = 0;
	static COMPONENT* rgb_row_cur  = nullptr;
	static COMPONENT* rgb_row_next = nullptr;
	if (buff_size < sline_pixels + 1) {
		delete[] rgb_row_cur;
		delete[] rgb_row_next;
		buff_size    = sline_pixels + 1;
		rgb_row_cur  = new COMPONENT[buff_size * 3];
		rgb_row_next = new COMPONENT[buff_size * 3];
	}

	int from_width = sline_pixels - srcx;
	if (srcw + 1 < from_width) {
		from_width = srcw + 1;
	}

	fill_rgb_row(from, from_width, rgb_row_cur, srcw + 1, manip);

	for (int y = 0; y < srch; y++) {
		Source_pixel* from_orig = from;
		Dest_pixel*   to_orig   = to;

		if (y + 1 < sheight) {
			fill_rgb_row(
					from + sline_pixels, from_width, rgb_row_next, srcw + 1,
					manip);
		} else {
			fill_rgb_row(from, from_width, rgb_row_next, srcw + 1, manip);
		}

		// every pixel in the src region, is extended to 4 pixels in the
		// destination, arranged in a square 'quad'; if the current src
		// pixel is 'a', then in what follows 'b' is the src pixel to the
		// right, 'c' is the src pixel below, and 'd' is the src pixel to
		// the right and down
		COMPONENT* cur_row  = rgb_row_cur;
		COMPONENT* next_row = rgb_row_next;
		COMPONENT* ar       = cur_row++;
		COMPONENT* ag       = cur_row++;
		COMPONENT* ab       = cur_row++;
		COMPONENT* cr       = next_row++;
		COMPONENT* cg       = next_row++;
		COMPONENT* cb       = next_row++;
		for (int x = 0; x < srcw; x++) {
			COMPONENT* br = cur_row++;
			COMPONENT* bg = cur_row++;
			COMPONENT* bb = cur_row++;
			COMPONENT* dr = next_row++;
			COMPONENT* dg = next_row++;
			COMPONENT* db = next_row++;

			// upper left pixel in quad: just copy it in
			*to++ = manip.rgb(*ar, *ag, *ab);

			// upper right
			*to++ = manip.rgb(
					(*ar + *br) >> 1, (*ag + *bg) >> 1, (*ab + *bb) >> 1);

			// lower left
			*to_odd++ = manip.rgb(
					(*ar + *cr) >> 1, (*ag + *cg) >> 1, (*ab + *cb) >> 1);

			// lower right
			*to_odd++ = manip.rgb(
					(*ar + *br + *cr + *dr) >> 2, (*ag + *bg + *cg + *dg) >> 2,
					(*ab + *bb + *cb + *db) >> 2);

			// 'b' becomes 'a', 'd' becomes 'c'
			ar = br;
			ag = bg;
			ab = bb;
			cr = dr;
			cg = dg;
			cb = db;
		}

		// the "next" rgb row becomes the current; the old current rgb row is
		// recycled and serves as the new "next" row
		COMPONENT* temp;
		temp         = rgb_row_cur;
		rgb_row_cur  = rgb_row_next;
		rgb_row_next = temp;

		// update the pointers for start of next pair of lines
		from   = from_orig + sline_pixels;
		to     = to_orig + 2 * dline_pixels;
		to_odd = to + dline_pixels;
	}
}

template <class Source_pixel, class Dest_pixel, class Manip_pixels>
void Scale_2xBilinearPlus(
		Source_pixel* source,                // ->source pixels.
		int srcx, int srcy,                  // Start of rectangle within src.
		int srcw, int srch,                  // Dims. of rectangle.
		int                 sline_pixels,    // Pixels (words)/line for source.
		int                 sheight,         // Source height.
		Dest_pixel*         dest,            // ->dest pixels.
		int                 dline_pixels,    // Pixels (words)/line for dest.
		const Manip_pixels& manip            // Manipulator methods.
) {
	Source_pixel* from   = source + srcy * sline_pixels + srcx;
	Dest_pixel*   to     = dest + 2 * srcy * dline_pixels + 2 * srcx;
	Dest_pixel*   to_odd = to + dline_pixels;

	// the following are static because we don't want to be freeing and
	// reallocating space on each call, as new[]s are usually very
	// expensive; we do allow it to grow though
	static int        buff_size    = 0;
	static COMPONENT* rgb_row_cur  = nullptr;
	static COMPONENT* rgb_row_next = nullptr;
	if (buff_size < sline_pixels + 1) {
		delete[] rgb_row_cur;
		delete[] rgb_row_next;
		buff_size    = sline_pixels + 1;
		rgb_row_cur  = new COMPONENT[buff_size * 3];
		rgb_row_next = new COMPONENT[buff_size * 3];
	}

	int from_width = sline_pixels - srcx;
	if (srcw + 1 < from_width) {
		from_width = srcw + 1;
	}

	fill_rgb_row(from, from_width, rgb_row_cur, srcw + 1, manip);

	for (int y = 0; y < srch; y++) {
		Source_pixel* from_orig = from;
		Dest_pixel*   to_orig   = to;

		if (y + 1 < sheight) {
			fill_rgb_row(
					from + sline_pixels, from_width, rgb_row_next, srcw + 1,
					manip);
		} else {
			fill_rgb_row(from, from_width, rgb_row_next, srcw + 1, manip);
		}

		// every pixel in the src region, is extended to 4 pixels in the
		// destination, arranged in a square 'quad'; if the current src
		// pixel is 'a', then in what follows 'b' is the src pixel to the
		// right, 'c' is the src pixel below, and 'd' is the src pixel to
		// the right and down
		COMPONENT* cur_row  = rgb_row_cur;
		COMPONENT* next_row = rgb_row_next;
		COMPONENT* ar       = cur_row++;
		COMPONENT* ag       = cur_row++;
		COMPONENT* ab       = cur_row++;
		COMPONENT* cr       = next_row++;
		COMPONENT* cg       = next_row++;
		COMPONENT* cb       = next_row++;
		for (int x = 0; x < srcw; x++) {
			COMPONENT* br = cur_row++;
			COMPONENT* bg = cur_row++;
			COMPONENT* bb = cur_row++;
			COMPONENT* dr = next_row++;
			COMPONENT* dg = next_row++;
			COMPONENT* db = next_row++;

			// upper left pixel in quad: just copy it in
			//*to++ = manip.rgb(*ar, *ag, *ab);
#ifdef USE_ORIGINAL_BILINEAR_PLUS
			*to++ = manip.rgb(
					(((*ar) << 2) + ((*ar)) + (*cr + *br + *br)) >> 3,
					(((*ag) << 2) + ((*ag)) + (*cg + *bg + *bg)) >> 3,
					(((*ab) << 2) + ((*ab)) + (*cb + *bb + *bb)) >> 3);
#else
			*to++ = manip.rgb(
					(((*ar) << 3) + ((*ar) << 1) + (*cr + *br + *br + *cr))
							>> 4,
					(((*ag) << 3) + ((*ag) << 1) + (*cg + *bg + *bg + *cg))
							>> 4,
					(((*ab) << 3) + ((*ab) << 1) + (*cb + *bb + *bb + *cb))
							>> 4);
#endif

			// upper right
			*to++ = manip.rgb(
					(*ar + *br) >> 1, (*ag + *bg) >> 1, (*ab + *bb) >> 1);

			// lower left
			*to_odd++ = manip.rgb(
					(*ar + *cr) >> 1, (*ag + *cg) >> 1, (*ab + *cb) >> 1);

			// lower right
			*to_odd++ = manip.rgb(
					(*ar + *br + *cr + *dr) >> 2, (*ag + *bg + *cg + *dg) >> 2,
					(*ab + *bb + *cb + *db) >> 2);

			// 'b' becomes 'a', 'd' becomes 'c'
			ar = br;
			ag = bg;
			ab = bb;
			cr = dr;
			cg = dg;
			cb = db;
		}

		// the "next" rgb row becomes the current; the old current rgb row is
		// recycled and serves as the new "next" row
		COMPONENT* temp;
		temp         = rgb_row_cur;
		rgb_row_cur  = rgb_row_next;
		rgb_row_next = temp;

		// update the pointers for start of next pair of lines
		from   = from_orig + sline_pixels;
		to     = to_orig + 2 * dline_pixels;
		to_odd = to + dline_pixels;
	}
}

#endif
