/* 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 "graphics/tinygl/gl.h"
#include "graphics/tinygl/zgl.h"
#include "graphics/tinygl/zbuffer.h"
#include "graphics/tinygl/texelbuffer.h"

namespace TinyGL {

#define ZB_POINT_ST_UNIT (1 << ZB_POINT_ST_FRAC_BITS)
#define ZB_POINT_ST_FRAC_MASK (ZB_POINT_ST_UNIT - 1)

TexelBuffer::TexelBuffer(uint width, uint height, uint textureSize) {
	assert(width);
	assert(height);
	assert(textureSize);

	_width = width;
	_height = height;
	_fracTextureUnit = textureSize << ZB_POINT_ST_FRAC_BITS;
	_fracTextureMask = _fracTextureUnit - 1;
	_widthRatio = (float) width / textureSize;
	_heightRatio = (float) height / textureSize;
}

static inline uint wrap(uint wrap_mode, int coord, uint _fracTextureUnit, uint _fracTextureMask) {
	switch (wrap_mode) {
	case TGL_MIRRORED_REPEAT:
		if (coord & _fracTextureUnit)
			return _fracTextureMask - (coord & _fracTextureMask);
		return coord & _fracTextureMask;
	case TGL_CLAMP_TO_EDGE:
		if (coord < 0)
			return 0;
		if ((uint) coord > _fracTextureMask)
			return _fracTextureMask;
		return coord;
	default:
		// Fall through
	case TGL_REPEAT:
		return coord & _fracTextureMask;
	}
}

void TexelBuffer::getARGBAt(
	uint wrap_s, uint wrap_t,
	int s, int t,
	uint8 &a, uint8 &r, uint8 &g, uint8 &b
) const {
	uint x, y;
	x = wrap(wrap_s, s, _fracTextureUnit, _fracTextureMask) * _widthRatio;
	y = wrap(wrap_t, t, _fracTextureUnit, _fracTextureMask) * _heightRatio;
	getARGBAt(
		(x >> ZB_POINT_ST_FRAC_BITS) + (y >> ZB_POINT_ST_FRAC_BITS) * _width,
		x & ZB_POINT_ST_FRAC_MASK, y & ZB_POINT_ST_FRAC_MASK,
		a, r, g, b
	);
}

// Nearest: store texture in original size.
NearestTexelBuffer::NearestTexelBuffer(const Graphics::PixelBuffer &buf, uint width, uint height, uint textureSize) : TexelBuffer(width, height, textureSize) {
	uint pixel_count = _width * _height;
	_buf = Graphics::PixelBuffer(buf.getFormat(), pixel_count, DisposeAfterUse::NO);
	_buf.copyBuffer(0, pixel_count, buf);
}

NearestTexelBuffer::~NearestTexelBuffer() {
	_buf.free();
}

void NearestTexelBuffer::getARGBAt(
	uint pixel,
	uint, uint,
	uint8 &a, uint8 &r, uint8 &g, uint8 &b
) const {
	_buf.getARGBAt(pixel, a, r, g, b);
}

// Bilinear: each texture coordinates corresponds to the 4 original image
// pixels linear interpolation has to work on, so that they are near each
// other in CPU data cache, and a single actual memory fetch happens. This
// allows applying linear filtering at render time at a very low performance
// cost. As we expect to work on small-ish textures (512*512 ?) the 4x memory
// usage increase should be negligible.
#define A_OFFSET (0 * 4)
#define R_OFFSET (1 * 4)
#define G_OFFSET (2 * 4)
#define B_OFFSET (3 * 4)
#define P00_OFFSET 0
#define P01_OFFSET 1
#define P10_OFFSET 2
#define P11_OFFSET 3
#define PIXEL_PER_TEXEL_SHIFT 2

BilinearTexelBuffer::BilinearTexelBuffer(const Graphics::PixelBuffer &buf, uint width, uint height, uint textureSize) : TexelBuffer(width, height, textureSize) {
	uint pixel00_offset = 0, pixel11_offset, pixel01_offset, pixel10_offset;
	uint8 *texel8;
	uint32 *texel32;

	texel32 = _texels = new uint32[_width * _height << PIXEL_PER_TEXEL_SHIFT];
	for (uint y = 0; y < _height; y++) {
		for (uint x = 0; x < _width; x++) {
			texel8 = (uint8 *)texel32;
			pixel11_offset = pixel00_offset + _width + 1;
			buf.getARGBAt(
				pixel00_offset,
				*(texel8 + P00_OFFSET + A_OFFSET),
				*(texel8 + P00_OFFSET + R_OFFSET),
				*(texel8 + P00_OFFSET + G_OFFSET),
				*(texel8 + P00_OFFSET + B_OFFSET)
			);
			if ((x + 1) == _width) {
				pixel11_offset -= 1;
				pixel01_offset = pixel00_offset;
			} else
				pixel01_offset = pixel00_offset + 1;
			buf.getARGBAt(
				pixel01_offset,
				*(texel8 + P01_OFFSET + A_OFFSET),
				*(texel8 + P01_OFFSET + R_OFFSET),
				*(texel8 + P01_OFFSET + G_OFFSET),
				*(texel8 + P01_OFFSET + B_OFFSET)
			);
			if ((y + 1) == _height) {
				pixel11_offset -= _width;
				pixel10_offset = pixel00_offset;
			} else
				pixel10_offset = pixel00_offset + _width;
			buf.getARGBAt(
				pixel10_offset,
				*(texel8 + P10_OFFSET + A_OFFSET),
				*(texel8 + P10_OFFSET + R_OFFSET),
				*(texel8 + P10_OFFSET + G_OFFSET),
				*(texel8 + P10_OFFSET + B_OFFSET)
			);
			buf.getARGBAt(
				pixel11_offset,
				*(texel8 + P11_OFFSET + A_OFFSET),
				*(texel8 + P11_OFFSET + R_OFFSET),
				*(texel8 + P11_OFFSET + G_OFFSET),
				*(texel8 + P11_OFFSET + B_OFFSET)
			);
			texel32 += 1 << PIXEL_PER_TEXEL_SHIFT;
			pixel00_offset++;
		}
	}
}

BilinearTexelBuffer::~BilinearTexelBuffer() {
	delete[] _texels;
}

static inline int interpolate(int v00, int v01, int v10, int xf, int yf) {
	return v00 + (((v01 - v00) * xf + (v10 - v00) * yf) >> ZB_POINT_ST_FRAC_BITS);
}

void BilinearTexelBuffer::getARGBAt(
	uint pixel,
	uint ds, uint dt,
	uint8 &a, uint8 &r, uint8 &g, uint8 &b
) const {
	uint p00_offset, p01_offset, p10_offset;
	uint8 *texel = (uint8 *)(_texels + (pixel << PIXEL_PER_TEXEL_SHIFT));
	if ((ds + dt) > ZB_POINT_ST_UNIT) {
		p00_offset = P11_OFFSET;
		p10_offset = P01_OFFSET;
		p01_offset = P10_OFFSET;
		ds = ZB_POINT_ST_UNIT - ds;
		dt = ZB_POINT_ST_UNIT - dt;
	} else {
		p00_offset = P00_OFFSET;
		p10_offset = P10_OFFSET;
		p01_offset = P01_OFFSET;
	}
	a = interpolate(
		*(texel + p00_offset + A_OFFSET),
		*(texel + p01_offset + A_OFFSET),
		*(texel + p10_offset + A_OFFSET),
		ds,
		dt
	);
	r = interpolate(
		*(texel + p00_offset + R_OFFSET),
		*(texel + p01_offset + R_OFFSET),
		*(texel + p10_offset + R_OFFSET),
		ds,
		dt
	);
	g = interpolate(
		*(texel + p00_offset + G_OFFSET),
		*(texel + p01_offset + G_OFFSET),
		*(texel + p10_offset + G_OFFSET),
		ds,
		dt
	);
	b = interpolate(
		*(texel + p00_offset + B_OFFSET),
		*(texel + p01_offset + B_OFFSET),
		*(texel + p10_offset + B_OFFSET),
		ds,
		dt
	);
}

} // end of namespace TinyGL
