/*
* $Id: rendertarget.cpp 873 2009-11-10 12:14:21Z stezkmil $
*
* Description  : One line description of file.
* Author       : Milan Stezka
*
* Purpose      :
*     Long description of what the file is for.
*/



#include "rendertarget.h"
#include "flexilog.h"

using namespace VRUT;

#ifndef VRUT_HPC

void RenderTarget::DeleteFrameBufferObject()
{
	DeleteAllTextureAttachments();
	DeleteAllRenderBufferAttachments();
	glDeleteFramebuffersEXT(1, &frameBufferObject);
	Unbind();
}

void RenderTarget::Bind()
{
	if (!frameBufferObject) {
		glGenFramebuffersEXT(1, &frameBufferObject);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	}
	else
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	}
}

void RenderTarget::Unbind()
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

GLenum RenderTarget::CheckFrameBufferObjectStatus() {
	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if (status == GL_FRAMEBUFFER_COMPLETE_EXT)
	{
		LOGDEBUG(wxT("<RenderTarget>Frame buffer object is complete"));
	}
	else
	{
		LOGDEBUG(wxT("<RenderTarget>FBO not ready. glCheckFramebufferStatus returned ") + wxString::Format(wxT("%i"), status));
	}
	return status; //TODO: Decide which status is undesirable
}

void RenderTarget::ResizeAttachments(GLsizei width, GLsizei height)
{
	GLint originalBinding(-1);
	glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &originalBinding);

//	wxCriticalSectionLocker locker(attachmentMutex);
	TextureAttachmentVector::iterator textureAttachmentIter;
	TextureAttachmentVector::size_type textureAttachmentCount(textureAttachment.size());
	
	for (TextureAttachmentVector::size_type i = 0; i < textureAttachmentCount; i++)
	{
		TextureAttachment *textureAttachment(this->textureAttachment[i]);
		if (textureAttachment->params.GetWidth() != width || textureAttachment->params.GetHeight() != height)
		{
			TextureAttachmentParams backupParams = textureAttachment->params;
			LOGDEBUG(wxT("<RenderTarget>Removing texture attachmentPoint with old dimensions"));
			RemoveTextureAttachment(textureAttachment);
			LOGDEBUG(wxT("<RenderTarget>Adding new texture attachmentPoint with up to date dimensions"));
			AddTextureAttachment(TextureAttachmentParams(width, height, backupParams.GetInternalFormat(), backupParams.GetAttachment(), backupParams.GetFormat(), backupParams.GetType(),
				backupParams.GetSamplesCount(), backupParams.GetFixedSampleLocations()), i);
		}
	}

	RenderBufferAttachmentVector::iterator renderBufferAttachmentIter;
	RenderBufferAttachmentVector::size_type renderBufferAttachmentCount(renderBufferAttachment.size());
	for (RenderBufferAttachmentVector::size_type i = 0; i < renderBufferAttachmentCount; i++)
	{
		RenderBufferAttachment *renderBufferAttachment(this->renderBufferAttachment[i]);
		if (renderBufferAttachment->params.GetWidth() != width || renderBufferAttachment->params.GetHeight() != height)
		{
			RenderBufferAttachmentParams backupParams = renderBufferAttachment->params;
			LOGDEBUG(wxT("<RenderTarget>Removing render buffer attachmentPoint with old dimensions"));
			RemoveRenderBufferAttachment(renderBufferAttachment);
			LOGDEBUG(wxT("<RenderTarget>Adding new render buffer attachmentPoint with up to date dimensions"));
			AddRenderBufferAttachment(RenderBufferAttachmentParams(width, height, backupParams.GetInternalFormat(), backupParams.GetSamples(), backupParams.GetCoverageSamples(),
				backupParams.GetAttachment(), backupParams.GetAntiAliasingType()), i);
		}
	}
	if (GLEW_EXT_framebuffer_object)
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, originalBinding);
}

void RenderTarget::AddCubeTextureAttachment(const TextureAttachmentParams& params, TextureAttachmentVector::size_type position)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	Bind();

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	GLuint object;
	glGenTextures( 1, &object );
	glBindTexture( GL_TEXTURE_CUBE_MAP, object );

	for (int j = 0; j < 6; j++)
	{
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexImage2D ( GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, params.GetInternalFormat(), params.GetWidth(), params.GetHeight(), 0, params.GetFormat(), params.GetType(), NULL);

		const AttachmentPointVector& av(params.GetAttachment());
		AttachmentPointVector incrementedAttachmentPointVector(av);
		for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		{
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, av.at(i) + j, GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, object, 0);
			incrementedAttachmentPointVector[i] += j;
		}

		TextureAttachmentParams incrementedAttachmentPointParams(params.GetWidth(), params.GetHeight(), params.GetInternalFormat(), incrementedAttachmentPointVector, params.GetFormat(), params.GetType());
		TextureAttachment *newAttachment(new TextureAttachment(incrementedAttachmentPointParams, object, this));

		if (position == (size_t)-1)
			textureAttachment.push_back(newAttachment);
		else
			textureAttachment.insert(textureAttachment.begin() + position, newAttachment);
	}
	CheckFrameBufferObjectStatus();
}

//TODO: Lots of doubled code for textures and renderbuffers. Unify.

GLenum RenderTarget::AddTextureAttachment(const TextureAttachmentParams& params, size_t position)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	Bind();

	GLuint object;
	glGenTextures(1, &object);

	//normal texture
	GLenum textureType = GL_TEXTURE_2D;
	if(params.GetSamplesCount() == 0)
	{
		glBindTexture(GL_TEXTURE_2D, object);
		//glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		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, params.GetInternalFormat(), params.GetWidth(), params.GetHeight(), 0, params.GetFormat(), params.GetType(), NULL);
//		glGenerateMipmapEXT(GL_TEXTURE_2D);
	}
	//multisample texture
	else
	{
		textureType = GL_TEXTURE_2D_MULTISAMPLE;
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, object);
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, params.GetSamplesCount(), params.GetInternalFormat(), params.GetWidth(), params.GetHeight(), params.GetFixedSampleLocations());
	}

	const AttachmentPointVector& av(params.GetAttachment());
	for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, av.at(i), textureType, object, 0);


	TextureAttachment *newAttachment(new TextureAttachment(params, object, this));
	
	if (position == (size_t)-1)
		textureAttachment.push_back(newAttachment);
	else
		textureAttachment.insert(textureAttachment.begin() + position, newAttachment);

	return CheckFrameBufferObjectStatus();
}

GLenum RenderTarget::AddExistingTextureAttachment(GLuint texture, const TextureAttachmentParams& params, size_t position)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	Bind();

	GLuint object = texture;

	GLenum textureType;
	if (params.GetSamplesCount() == 0)
		textureType = GL_TEXTURE_2D;
	else
		textureType = GL_TEXTURE_2D_MULTISAMPLE;

	glBindTexture(textureType, object);

	const AttachmentPointVector& av(params.GetAttachment());
	for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, av.at(i), textureType, object, 0);

	TextureAttachment *newAttachment(new TextureAttachment(params, object, this));

	if (position == (size_t)-1)
		textureAttachment.push_back(newAttachment);
	else
		textureAttachment.insert(textureAttachment.begin() + position, newAttachment);

	return CheckFrameBufferObjectStatus();
}

GLenum RenderTarget::AddTextureAttachment(TextureAttachment& existingAttachment, size_t position)
{
	//Share existing attachment from another FBO with this FBO
	wxCriticalSectionLocker locker(attachmentMutex);

	Bind();

	const AttachmentPointVector& av(existingAttachment.params.GetAttachment());
	for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, av.at(i), GL_TEXTURE_2D, existingAttachment.object, 0);

	existingAttachment.usedBy.insert(this);

	if (position == (size_t)-1)
		textureAttachment.push_back(&existingAttachment);
	else
		textureAttachment.insert(textureAttachment.begin() + position, &existingAttachment);
	
	return CheckFrameBufferObjectStatus();
}

GLenum RenderTarget::AddRenderBufferAttachment(const RenderBufferAttachmentParams& params, size_t position)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	Bind();

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);
	GLuint object;
	glGenRenderbuffersEXT( 1, &object );
	glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, object );

	switch(params.GetAntiAliasingType())
	{
	case COVERAGE_SAMPLING:
	{
		glRenderbufferStorageMultisampleCoverageNV(GL_RENDERBUFFER_EXT, params.GetCoverageSamples(), params.GetSamples(), params.GetInternalFormat(), params.GetWidth(), params.GetHeight());
#ifndef NDEBUG
		int grantedSampleCount(0);
		glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COLOR_SAMPLES_NV, &grantedSampleCount);
		LOG(wxString::Format(wxT("%d color samples granted to the renderbuffer with internal format 0x%x"), grantedSampleCount, params.GetInternalFormat()));
		glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_COVERAGE_SAMPLES_NV, &grantedSampleCount);
		LOG(wxString::Format(wxT("%d coverage samples granted to the renderbuffer with internal format 0x%x"), grantedSampleCount, params.GetInternalFormat()));
#endif
		break;
	}
	case MULTI_SAMPLING:
	{
		glRenderbufferStorageMultisampleEXT( GL_RENDERBUFFER_EXT, params.GetSamples(), params.GetInternalFormat(), params.GetWidth(), params.GetHeight());
#ifndef NDEBUG
		int grantedSampleCount(0);
		glGetRenderbufferParameterivEXT( GL_RENDERBUFFER_EXT, GL_RENDERBUFFER_SAMPLES_EXT, &grantedSampleCount);
		LOG(wxString::Format(wxT("%d color samples granted to the renderbuffer with internal format %x"), grantedSampleCount, params.GetInternalFormat()));
#endif
		break;
	}
	default:
		glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, params.GetInternalFormat(), params.GetWidth(), params.GetHeight());
		break;
	};

	const AttachmentPointVector& av(params.GetAttachment());
	for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, av.at(i), GL_RENDERBUFFER_EXT, object);

	RenderBufferAttachment *newAttachment(new RenderBufferAttachment(params, object, this));
	
	if (position == (size_t)-1)
		renderBufferAttachment.push_back(newAttachment);
	else
		renderBufferAttachment.insert(renderBufferAttachment.begin() + position, newAttachment);

	return CheckFrameBufferObjectStatus();
}

GLenum RenderTarget::AddRenderBufferAttachment(RenderBufferAttachment& existingAttachment, size_t position)
{
	//Share existing attachment from another FBO with this FBO
	wxCriticalSectionLocker locker(attachmentMutex);

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);

	const AttachmentPointVector& av(existingAttachment.params.GetAttachment());
	for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, av.at(i), GL_RENDERBUFFER_EXT, existingAttachment.object);

	existingAttachment.usedBy.insert(this);

	if (position == (size_t)-1)
		renderBufferAttachment.push_back(&existingAttachment);
	else
		renderBufferAttachment.insert(renderBufferAttachment.begin() + position, &existingAttachment);
	
	return CheckFrameBufferObjectStatus();
}

void RenderTarget::RemoveTextureAttachment(TextureAttachment *existingAttachment)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	TextureAttachmentVector::iterator attachmentIter;
	for (attachmentIter = textureAttachment.begin(); attachmentIter != textureAttachment.end(); ++attachmentIter)
	{
		if (*attachmentIter == existingAttachment)
			break;
	}

	if (attachmentIter != textureAttachment.end())
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);

		const AttachmentPointVector& av(existingAttachment->params.GetAttachment());
		for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		{
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, av.at(i), GL_TEXTURE_2D, 0, 0);
		}

		existingAttachment->usedBy.erase(existingAttachment->usedBy.find(this));
		textureAttachment.erase(attachmentIter);

		if (!existingAttachment->usedBy.size())
		{
			glDeleteTextures(1,&(existingAttachment->object));
			delete existingAttachment;
		}
	}
}

void RenderTarget::RemoveRenderBufferAttachment(RenderBufferAttachment *existingAttachment)
{
	wxCriticalSectionLocker locker(attachmentMutex);

	RenderBufferAttachmentVector::iterator attachmentIter;
	for (attachmentIter = renderBufferAttachment.begin(); attachmentIter != renderBufferAttachment.end(); ++attachmentIter)
	{
		if (*attachmentIter == existingAttachment)
			break;
	}

	if (attachmentIter != renderBufferAttachment.end())
	{
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frameBufferObject);

		const AttachmentPointVector& av(existingAttachment->params.GetAttachment());
		for (AttachmentPointVector::size_type i = 0; i < av.size(); i++)
		{
			glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, av.at(i), GL_RENDERBUFFER_EXT, 0);
		}

		existingAttachment->usedBy.erase(existingAttachment->usedBy.find(this));
		renderBufferAttachment.erase(attachmentIter);

		if (!existingAttachment->usedBy.size())
		{
			glDeleteRenderbuffersEXT(1,&(existingAttachment->object));
			delete existingAttachment;
		}
	}
}

void RenderTarget::DeleteAllRenderBufferAttachments()
{
	while(!renderBufferAttachment.empty())
		RemoveRenderBufferAttachment(*renderBufferAttachment.begin());
}

void RenderTarget::DeleteAllTextureAttachments()
{
	while(!textureAttachment.empty())
		RemoveTextureAttachment(*textureAttachment.begin());
}

RenderTarget::TextureAttachment * RenderTarget::GetTextureAttachmentOfType(GLenum type) const
{
	for (TextureAttachmentVector::const_iterator i = textureAttachment.begin(); i != textureAttachment.end(); ++i)
	{
		TextureAttachment *a(*i);
		const AttachmentPointVector& av(a->params.GetAttachment());
		for (AttachmentPointVector::size_type j = 0; j < av.size(); ++j)
		{
			if (av.at(j) == type)
				return a;
		}
	}
	return NULL;
}

const RenderTarget::RenderBufferAttachment * RenderTarget::GetRenderBufferAttachmentOfType(GLenum type) const
{
	for (RenderBufferAttachmentVector::const_iterator i = renderBufferAttachment.begin(); i != renderBufferAttachment.end(); ++i)
	{
		RenderBufferAttachment *a(*i);
		const AttachmentPointVector& av(a->params.GetAttachment());
		for (AttachmentPointVector::size_type j = 0; j < av.size(); ++j)
		{
			if (av.at(j) == type)
				return a;
		}
	}
	return NULL;
}
#endif