#include "RenderTargetPool.h"

#include <cstddef>

namespace
{
	void ConfigureRenderTargetTexture(
		unsigned width,
		unsigned height,
		GLenum internalFormat,
		GLenum pixelFormat,
		GLenum pixelType)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, pixelFormat, pixelType, NULL);
	}
}

bool RenderTargetPool::Create(
	RenderTargetId id,
	unsigned width,
	unsigned height,
	GLenum internalFormat,
	GLenum pixelFormat,
	GLenum pixelType,
	const char* errorPrefix,
	std::string& error)
{
	RenderTarget& target = mTargets[TargetIndex(id)];
	if (target.texture != 0 || target.framebuffer != 0)
	{
		error = std::string(errorPrefix) + " render target was already initialized.";
		return false;
	}

	glGenTextures(1, &target.texture);
	glBindTexture(GL_TEXTURE_2D, target.texture);
	ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
	glBindTexture(GL_TEXTURE_2D, 0);

	glGenFramebuffers(1, &target.framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		error = std::string("Cannot initialize ") + errorPrefix + " framebuffer.";
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		return false;
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	target.width = width;
	target.height = height;
	target.internalFormat = internalFormat;
	target.pixelFormat = pixelFormat;
	target.pixelType = pixelType;
	return true;
}

bool RenderTargetPool::ReserveTemporaryTargets(
	std::size_t count,
	unsigned width,
	unsigned height,
	GLenum internalFormat,
	GLenum pixelFormat,
	GLenum pixelType,
	std::string& error)
{
	if (mTemporaryTargets.size() == count)
		return true;

	DestroyTemporaryTargets();

	mTemporaryTargets.resize(count);
	for (std::size_t index = 0; index < mTemporaryTargets.size(); ++index)
	{
		RenderTarget& target = mTemporaryTargets[index];
		glGenTextures(1, &target.texture);
		glBindTexture(GL_TEXTURE_2D, target.texture);
		ConfigureRenderTargetTexture(width, height, internalFormat, pixelFormat, pixelType);
		glBindTexture(GL_TEXTURE_2D, 0);

		glGenFramebuffers(1, &target.framebuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, target.framebuffer);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
		{
			error = "Cannot initialize temporary render target.";
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
			return false;
		}

		target.width = width;
		target.height = height;
		target.internalFormat = internalFormat;
		target.pixelFormat = pixelFormat;
		target.pixelType = pixelType;
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	return true;
}

void RenderTargetPool::DestroyTemporaryTargets()
{
	for (RenderTarget& target : mTemporaryTargets)
	{
		if (target.framebuffer != 0)
			glDeleteFramebuffers(1, &target.framebuffer);
		if (target.texture != 0)
			glDeleteTextures(1, &target.texture);
	}
	mTemporaryTargets.clear();
}

void RenderTargetPool::Destroy()
{
	for (RenderTarget& target : mTargets)
	{
		if (target.framebuffer != 0)
			glDeleteFramebuffers(1, &target.framebuffer);
		if (target.texture != 0)
			glDeleteTextures(1, &target.texture);
		target = RenderTarget();
	}

	DestroyTemporaryTargets();
}

const RenderTarget& RenderTargetPool::Target(RenderTargetId id) const
{
	return mTargets[TargetIndex(id)];
}
