/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/scummsys.h"

#if defined(USE_OPENGL)

#include "backends/graphics/opengl/gltexture.h"
#include "backends/graphics/opengl/glerrorcheck.h"

#include "common/rect.h"
#include "common/array.h"
#include "common/util.h"
#include "common/tokenizer.h"

// Supported GL extensions
static bool npot_supported = false;
static bool glext_inited = false;

/*static inline GLint xdiv(int numerator, int denominator) {
	assert(numerator < (1 << 16));
	return (numerator << 16) / denominator;
}*/

static GLuint nextHigher2(GLuint v) {
	if (v == 0)
		return 1;
	v--;
	v |= v >> 1;
	v |= v >> 2;
	v |= v >> 4;
	v |= v >> 8;
	v |= v >> 16;
	return ++v;
}

void GLTexture::initGLExtensions() {

	// Return if extensions were already checked
	if (glext_inited)
		return;

	// Get a string with all extensions
	const char *ext_string = (const char *)glGetString(GL_EXTENSIONS);
	CHECK_GL_ERROR();
	Common::StringTokenizer tokenizer(ext_string, " ");
	// Iterate all string tokens
	while (!tokenizer.empty()) {
		Common::String token = tokenizer.nextToken();
		if (token == "GL_ARB_texture_non_power_of_two")
			npot_supported = true;
	}

	glext_inited = true;
}

GLTexture::GLTexture(byte bpp, GLenum internalFormat, GLenum format, GLenum type)
	:
	_bytesPerPixel(bpp),
	_internalFormat(internalFormat),
	_glFormat(format),
	_glType(type),
	_textureWidth(0),
	_textureHeight(0),
	_realWidth(0),
	_realHeight(0),
	_refresh(false),
	_filter(GL_NEAREST) {

	// Generate the texture ID
	glGenTextures(1, &_textureName); CHECK_GL_ERROR();
}

GLTexture::~GLTexture() {
	// Delete the texture
	glDeleteTextures(1, &_textureName); CHECK_GL_ERROR();
}

void GLTexture::refresh() {
	// Delete previous texture
	glDeleteTextures(1, &_textureName); CHECK_GL_ERROR();

	// Generate the texture ID
	glGenTextures(1, &_textureName); CHECK_GL_ERROR();
	_refresh = true;
}

void GLTexture::allocBuffer(GLuint w, GLuint h) {
	_realWidth = w;
	_realHeight = h;

	if (!_refresh) {
		if (npot_supported && _filter == GL_LINEAR) {
			// Check if we already allocated a correctly-sized buffer
			// This is so we don't need to duplicate the last row/column
			if (w == _textureWidth && h == _textureHeight)
				return;
		} else {
			// Check if we already have a large enough buffer
			if (w <= _textureWidth && h <= _textureHeight)
				return;
		}
	}

	if (npot_supported) {
		_textureWidth = w;
		_textureHeight = h;
	} else {
		_textureWidth = nextHigher2(w);
		_textureHeight = nextHigher2(h);
	}

	// Select this OpenGL texture
	glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR();

	// Set the texture parameters
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _filter); CHECK_GL_ERROR();
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _filter); CHECK_GL_ERROR();
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); CHECK_GL_ERROR();
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); CHECK_GL_ERROR();

	// Allocate room for the texture
	glTexImage2D(GL_TEXTURE_2D, 0, _internalFormat,
	             _textureWidth, _textureHeight, 0, _glFormat, _glType, NULL); CHECK_GL_ERROR();

	_refresh = false;
}

void GLTexture::updateBuffer(const void *buf, int pitch, GLuint x, GLuint y, GLuint w, GLuint h) {
	// Skip empty updates.
	if (w * h == 0)
		return;

	// Select this OpenGL texture
	glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR();

	// Check if the buffer has its data contiguously
	if ((int)w * _bytesPerPixel == pitch) {
		glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h,
		                _glFormat, _glType, buf); CHECK_GL_ERROR();
	} else {
		// Update the texture row by row
		const byte *src = (const byte *)buf;
		GLuint curY = y;
		GLuint height = h;
		do {
			glTexSubImage2D(GL_TEXTURE_2D, 0, x, curY,
			                w, 1, _glFormat, _glType, src); CHECK_GL_ERROR();
			curY++;
			src += pitch;
		} while (--height);
	}

	// If we're in linear filter mode, repeat the last row/column if the real dimensions
	// doesn't match the texture dimensions.
	if (_filter == GL_LINEAR) {
		if (_realWidth != _textureWidth && x + w == _realWidth) {
			const byte *src = (const byte *)buf + (w - 1) * _bytesPerPixel;
			GLuint curY = y;
			GLuint height = h;

			do {
				glTexSubImage2D(GL_TEXTURE_2D, 0, x + w,
						curY, 1, 1, _glFormat, _glType, src); CHECK_GL_ERROR();

				curY++;
				src += pitch;
			} while (--height);
		}

		if (_realHeight != _textureHeight && y + h == _realHeight) {
			glTexSubImage2D(GL_TEXTURE_2D, 0, x, y + h,
					w, 1, _glFormat, _glType, (const byte *)buf + pitch * (h - 1)); CHECK_GL_ERROR();
		}
	}
}

void GLTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) {
	// Select this OpenGL texture
	glBindTexture(GL_TEXTURE_2D, _textureName); CHECK_GL_ERROR();

	// Calculate the texture rect that will be drawn
	const GLfloat texWidth = (GLfloat)_realWidth / _textureWidth;//xdiv(_surface.w, _textureWidth);
	const GLfloat texHeight = (GLfloat)_realHeight / _textureHeight;//xdiv(_surface.h, _textureHeight);
	const GLfloat texcoords[] = {
		0, 0,
		texWidth, 0,
		0, texHeight,
		texWidth, texHeight,
	};
	glTexCoordPointer(2, GL_FLOAT, 0, texcoords); CHECK_GL_ERROR();

	// Calculate the screen rect where the texture will be drawn
	const GLshort vertices[] = {
		x,                y,
		(GLshort)(x + w), y,
		x,                (GLshort)(y + h),
		(GLshort)(x + w), (GLshort)(y + h),
	};
	glVertexPointer(2, GL_SHORT, 0, vertices); CHECK_GL_ERROR();

	// Draw the texture to the screen buffer
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); CHECK_GL_ERROR();
}

#endif