
#include <Spectre/Graphics/Image.h>
#include <Spectre/Graphics/OpenGL.h>
#include <Spectre/Graphics/Texture.h>

namespace {

	GLint pixelFormatToInternal(enum PixelFormat format) {

		switch(format) {
		case PixelFormat::PF_RGBA	: return GL_RGBA8;
		case PixelFormat::PF_RGB	: return GL_RGB8;
		case PixelFormat::PF_Alpha	:
		default				: return GL_R8;
		}
	}

	GLint pixelFormatToGL(enum PixelFormat format) {

		switch(format) {
		case PixelFormat::PF_RGBA	: return GL_RGBA;
		case PixelFormat::PF_RGB	: return GL_RGB;
		case PixelFormat::PF_Alpha	:
		default				: return GL_RED;
		}
	}
}

Texture::Texture() :
m_id	(0),
m_size	(0, 0)
{
}

Texture::~Texture()
{
	destroy();
}

void Texture::create(unsigned width, unsigned heigth, PixelFormat format)
{
	// TODO: Should have getBpp() in PixelFormat instead.
	// Always allocate 32 bit (4 byte) pixel buffer for now.
	std::vector<unsigned char> empty_pixels(width * heigth * 4);

	if (!m_id) {
		glGenTextures(1, &m_id);
	}

	enable();

	m_size = vec2u(width, heigth);
	m_format = format;

	if (format == PixelFormat::PF_RGBA) {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	} else {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	}

	glTexImage2D(GL_TEXTURE_2D, 0, 
		pixelFormatToInternal(format), width, heigth, 0, 
		pixelFormatToGL(format), GL_UNSIGNED_BYTE, &empty_pixels[0]);

	setSmooth(true);
	setRepeat(false);

	disable();
}

void Texture::create(const Vector2u& size, PixelFormat format)
{
	create(size.x, size.y, format);
}

void Texture::create(const Image& image)
{
	GLint srcFormat;
	GLint glFormat;

	if (!m_id) {
		glGenTextures(1, &m_id);
	}

	m_size = image.getSize();
	m_format = image.getFormat();

	enable();

	if (image.getBpp() == 32) {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	} else {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	}

	srcFormat = pixelFormatToGL(image.getFormat());
	glFormat = pixelFormatToInternal(image.getFormat());

	glTexImage2D(GL_TEXTURE_2D, 0, glFormat, m_size.x, m_size.y, 
			0, srcFormat, GL_UNSIGNED_BYTE, image.getPixels());

	setSmooth(true);
	setRepeat(false);

	disable();
}

void Texture::create(const std::string& filename, bool isTopDown)
{
	Image img;

	if (img.loadFromFile(filename)) {

		// OpenGL expects bottom-up images, flip.
		if (isTopDown) {
			img.flipY();
		}

		create(img);
	}
}

void Texture::destroy()
{
	if (m_id) {
		glDeleteTextures(1, &m_id);
		m_id = 0;
	}

	m_size = vec2u(0, 0);
	m_format = PixelFormat::PF_Unknown;
}

bool Texture::isEmpty() const
{
	return m_id == 0 || m_size == vec2u(0, 0);
}

const Vector2u& Texture::getSize() const
{
	return m_size;
}

void Texture::update(vec2u pos, const Image& image)
{
	GLuint format;

	enable();

	if (image.getFormat() == PixelFormat::PF_RGBA) {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	} else {
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	}

	format = pixelFormatToGL(image.getFormat());

	glTexSubImage2D(GL_TEXTURE_2D, 0, pos.x, pos.y, 
		image.getWidth(), image.getHeight(), 
		format, GL_UNSIGNED_BYTE, image.getPixels());

	disable();
}

void Texture::update(const Image& image)
{
	update(Vector2u(0, 0), image);
}

Image Texture::copyToImage() const
{
	Image img;

	if (!isEmpty()) {
		std::vector<unsigned char> pixels(m_size.x * m_size.y * 4);

		enable();

		//glPixelStorei(GL_PACK_ALIGNMENT, 4);

		glGetTexImage(GL_TEXTURE_2D, 0, 
			pixelFormatToGL(m_format), 
			GL_UNSIGNED_BYTE, 
			&pixels[0]);

		img.create(m_format, m_size.x, m_size.y, &pixels[0]);

		disable();
	}

	return img;
}

void Texture::setMinFilter(enum Filter filter)
{
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
}

void Texture::setMagFilter(enum Filter filter)
{
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}

void Texture::setSmooth(bool value)
{
	Filter filter = value ? Filter::Linear : Filter::Nearest;

	setMinFilter(filter);
	setMagFilter(filter);
}

void Texture::setWrapMode(enum WrapMode mode)
{
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode);
}

void Texture::setRepeat(bool value)
{
	setWrapMode(value ? WrapMode::Repeat : WrapMode::ClampToEdge);
}

void Texture::enable() const
{
	glBindTexture(GL_TEXTURE_2D, m_id);
}

void Texture::disable() const
{
	glBindTexture(GL_TEXTURE_2D, 0);
}

static unsigned int _getMaxSize() {

	GLint size;
	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size);
	return static_cast<unsigned int>(size);
}

unsigned int Texture::getMaxSize()
{
	static unsigned maxSize = _getMaxSize();
	return maxSize;
}

bool Texture::operator<(const Texture* other) const
{
	return m_id < other->m_id;
}

bool Texture::operator!=(const Texture* other) const
{
	return m_id != other->m_id;
}
