/// Copyright (c) 2010 Ruben Penalva. All rights reserved.
/// Redistribution and use in source and binary forms, with or without modification, 
/// are permitted provided that the following conditions are met:
///    1. Redistributions of source code must retain the above copyright notice, this 
///    list of conditions and the following disclaimer.
///    2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation and/or
///    other materials provided with the distribution.
///    3. The name of the author may not be used to endorse or promote products derived
///    from this software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
/// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 
/// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
/// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
/// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
/// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "OpenglTexture.h"
#include <assert.h>

#include "OpenglException.h"

using namespace TestFrameWork;

OpenglTexture::OpenglTexture(TextureData data, 
							 OpenglTextureFormat format, size_t width,
							 OpenglTextureFilter filter, 
							 OpenglTextureEdgeValue edge)	:	m_target(GL_TEXTURE_1D), 
																m_format(Format2Gl(format)),
																m_internalFormat(Format2InternalFormat(format)),
																m_width(width), m_height(0), 
																m_depth(0)
{
	GLfloat glFilter = Filter2Gl(filter);
	GLint glEdgeValue = EdgeValue2Gl(edge);
	CreateTexture1D(data, glFilter, glEdgeValue);
}

OpenglTexture::OpenglTexture(TextureData data, 
							 OpenglTextureFormat format, 
							 size_t width, size_t height,
							 OpenglTextureFilter filter, 
							 OpenglTextureEdgeValue edge)	:	m_target(GL_TEXTURE_2D), 
							 									m_format(Format2Gl(format)),
																m_internalFormat(Format2InternalFormat(format)),
																m_width(width), m_height(height), 
																m_depth(0)
{
	GLfloat glFilter = Filter2Gl(filter);
	GLint glEdgeValue = EdgeValue2Gl(edge);
	CreateTexture2D(data, glFilter, glEdgeValue);
}

OpenglTexture::OpenglTexture(TextureData data, 
							 OpenglTextureFormat format, 
							 size_t width, size_t height, size_t depth,
							 OpenglTextureFilter filter, 
							 OpenglTextureEdgeValue edge)	:	m_target(GL_TEXTURE_3D), 
						 										m_format(Format2Gl(format)),
																m_internalFormat(Format2InternalFormat(format)),
																m_width(width), m_height(height), 
																m_depth(depth)
{
	GLfloat glFilter = Filter2Gl(filter);
	GLint glEdgeValue = EdgeValue2Gl(edge);
	CreateTexture3D(data, glFilter, glEdgeValue);
}

OpenglTexture::~OpenglTexture(void)
{
	glDeleteTextures(1, &m_id);
}

void OpenglTexture::ModifyData(TextureData data, size_t width)
{
	assert(m_target == GL_TEXTURE_1D);
	assert(width <= m_width);
	
	glBindTexture(GL_TEXTURE_1D, m_id);
	glTexSubImage1D(GL_TEXTURE_1D, 0, 0, static_cast<GLsizei>(width), m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

void OpenglTexture::ModifyData(TextureData data, size_t width, size_t height)
{
	assert(m_target == GL_TEXTURE_2D);
	assert(width <= m_width);
	assert(height <= m_height);
	
	glBindTexture(GL_TEXTURE_2D, m_id);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

void OpenglTexture::ModifyData(TextureData data, size_t width, size_t height, size_t depth)
{
	assert(m_target == GL_TEXTURE_3D);
	assert(width <= m_width);
	assert(height <= m_height);
	assert(depth <= m_depth);

	glBindTexture(GL_TEXTURE_3D, m_id);
	glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), static_cast<GLsizei>(depth), m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

GLuint OpenglTexture::GetOpenglId(void) const
{
	return m_id;
}

GLenum OpenglTexture::GetTarget(void) const
{
	return m_target;
}

GLenum OpenglTexture::Format2Gl(OpenglTextureFormat format)
{
	GLint glFormat = GL_RGB;

	switch(format)
	{
	case Red:
		glFormat = GL_RED;
		break;

	case RGB:
		glFormat = GL_RGB;
		break;

	case RGB32F:
		glFormat = GL_RGB;
		break;

	case RGBA:
		glFormat = GL_RGBA;
		break;

	case RGBA32F:
		glFormat = GL_RGBA;
		break;

	default: 
		assert(false);
		break;
	}

	return glFormat;
}

GLint OpenglTexture::Format2InternalFormat(OpenglTextureFormat format)
{
	GLint glInternalFormat = RGBA;
	switch(format)
	{
	case Red:
		glInternalFormat = GL_RED;
		break;

	case RGB:
		glInternalFormat = GL_RGB;
		break;

	case RGB32F:
		glInternalFormat = GL_RGB32F;
		break;

	case RGBA32F:
		glInternalFormat = GL_RGBA32F;
		break;

	default: 
		assert(false);
		break;
	}

	return glInternalFormat;
}

GLfloat OpenglTexture::Filter2Gl(OpenglTextureFilter filter)
{
	switch(filter)
	{
		case Nearest:
			return GL_NEAREST;
			break;

		case Linear:
			return GL_LINEAR;
			break;

		default:
			assert(false);
			return GL_LINEAR;
			break;
	}
}

GLint OpenglTexture::EdgeValue2Gl(OpenglTextureEdgeValue edge)
{
	GLint glEdge = GL_REPEAT;

	switch(edge)
	{
		case Repeat:
			glEdge = GL_REPEAT;
			break;

		case MirroredRepeat:
			glEdge = GL_MIRRORED_REPEAT;
			break;

		case ClampToEdge:
			glEdge = GL_CLAMP_TO_EDGE;
			break;

		case ClampToBorder:
			glEdge = GL_CLAMP_TO_EDGE;
			break;
	
		case Clamp:
			glEdge = GL_CLAMP;
			break;

		default:
			assert(false);
			break;
	}

	return glEdge;
}

void OpenglTexture::CreateTexture1D(TextureData data, GLfloat filter, GLint edge)
{
	glGenTextures(1, &m_id);
	TestFrameWork::OnGLErrorThrowException();
	
	glBindTexture(GL_TEXTURE_1D, m_id);
	TestFrameWork::OnGLErrorThrowException();

	Configure(filter, edge);

	glTexImage1D(GL_TEXTURE_1D, 0, m_internalFormat, static_cast<GLsizei>(m_width), 0, m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

void OpenglTexture::CreateTexture2D(TextureData data, GLfloat filter, GLint edge)
{
	glGenTextures(1, &m_id);
	TestFrameWork::OnGLErrorThrowException();

	glBindTexture(GL_TEXTURE_2D, m_id);
	TestFrameWork::OnGLErrorThrowException();

	Configure(filter, edge);

	glTexImage2D(GL_TEXTURE_2D, 0, m_internalFormat, static_cast<GLsizei>(m_width), static_cast<GLsizei>(m_height), 0, m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

void OpenglTexture::CreateTexture3D(boost::shared_array<char> data, GLfloat filter, GLint edge)
{
	glGenTextures(1, &m_id);
	TestFrameWork::OnGLErrorThrowException();

	glBindTexture(GL_TEXTURE_3D, m_id);
	TestFrameWork::OnGLErrorThrowException();

	Configure(filter, edge);

	glTexImage3D(GL_TEXTURE_3D, 0, m_internalFormat, static_cast<GLsizei>(m_width), static_cast<GLsizei>(m_height), static_cast<GLsizei>(m_depth), 0, m_format, GL_FLOAT, data.get());
	TestFrameWork::OnGLErrorThrowException();
}

void OpenglTexture::Configure(GLfloat filter, GLint edge) const
{
	glTexParameterf(m_target, GL_TEXTURE_WRAP_S, edge);
	TestFrameWork::OnGLErrorThrowException();

	glTexParameterf(m_target, GL_TEXTURE_WRAP_T, edge);
	TestFrameWork::OnGLErrorThrowException();

	glTexParameteri(m_target, GL_TEXTURE_WRAP_R, edge);
	TestFrameWork::OnGLErrorThrowException();

	glTexParameterf(m_target, GL_TEXTURE_MAG_FILTER, filter);
	TestFrameWork::OnGLErrorThrowException();

	glTexParameterf(m_target, GL_TEXTURE_MIN_FILTER, filter);
	TestFrameWork::OnGLErrorThrowException();
}