#include "include/FrameManager/FrameManager.h"

#include "include/Utils/Utils.h"
#include "include/Engine.h"

namespace SE
{
    

const int CONST_SCREEN_FRAMEBUFFER = 0;


//===================================================
//================ FRAME MANAGER ====================
//===================================================


TFrameManager::~TFrameManager()
{
	FreeFrameManager();
	*Console<<"ResourceManager::FrameManager deleting"; 
}

void TFrameManager::FreeFrameManager()
{
	AssertIfInMainThread();
    BOOST_FOREACH(auto& i, FrameMap)
	{
		ResourceManager->TexList.DeleteTexture(i.second.TexName);
		glDeleteRenderbuffers(1, &(i.second.DepthRenderBuffer));
		glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
		glDeleteFramebuffers(1, &(i.second.FrameBuffer));
	}

	FrameMap.clear();

}

cardinal TFrameManager::AddFrameRenderBuffer(const std::string& frameName,cardinal width,cardinal height)
{
	AssertIfInMainThread();

	#ifdef TARGET_ANDROID
	#define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
	#endif
    #ifdef TARGET_IOS
    #define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
    #endif
	
	if (FrameMap.count(frameName) == 0)
	{

		
		
		std::string texName = frameName + "Texture";
		cardinal texID = ResourceManager->TexList.AddEmptyTexture(texName,width,height);
	
		
		cardinal frameBuffer;
		cardinal depthRenderBuffer;
		cardinal status;


		glGenFramebuffers(1,&frameBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
		
		
		glGenRenderbuffers(1, &depthRenderBuffer);
		glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBuffer);
		
		#ifdef TARGET_WIN32
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
		#endif
		#ifdef TARGET_ANDROID
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
		#endif
        #ifdef TARGET_IOS
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
        #endif
		
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderBuffer);

		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);
		
		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		
		switch(status)
		{
		case GL_FRAMEBUFFER_COMPLETE_EXT:
			*Console<<"FrameManager:: frame buffer added: "+frameName;
			FrameMap[frameName].FrameBuffer = frameBuffer;
			FrameMap[frameName].DepthRenderBuffer = depthRenderBuffer;
			FrameMap[frameName].TexID = texID;
			FrameMap[frameName].Width = width;
			FrameMap[frameName].Height = height;
			FrameMap[frameName].TexName = texName;

			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
			
			return FrameMap[frameName].FrameBuffer;

			break;
		default:
			*Console<<"FrameManager::ERROR - frame buffer can not be added correctly: "+frameName+", with error: "+tostr(status);
			ResourceManager->TexList.DeleteTexture(texName);
			glDeleteRenderbuffers(1, &depthRenderBuffer);
			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
			glDeleteFramebuffers(1, &frameBuffer);

			return 0;
		}
		
	}
	else
		return FrameMap[frameName].FrameBuffer;

	#ifdef TARGET_ANDROID
	#undef GL_FRAMEBUFFER_COMPLETE_EXT
	#endif
    #ifdef TARGET_IOS
    #undef GL_FRAMEBUFFER_COMPLETE_EXT
    #endif
    
}

cardinal TFrameManager::AddCubemapBuffer(const std::string& frameName,cardinal width,cardinal height)
{
	AssertIfInMainThread();

	#ifdef TARGET_ANDROID
	#define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
	#endif
    #ifdef TARGET_IOS  
    #define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
    #endif
	
	if (FrameMap.count(frameName) == 0)
	{

		std::string texName = frameName + "Texture";
		cardinal texID = ResourceManager->TexList.AddEmptyCubemapTexture(texName,width,height);

		cardinal frameBuffer;
		cardinal depthRenderBuffer;
		cardinal status;


		glGenFramebuffers(1,&frameBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

		glGenRenderbuffers(1, &depthRenderBuffer);
		glBindRenderbuffer(GL_RENDERBUFFER, depthRenderBuffer);
		
		#ifdef TARGET_WIN32
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
		#endif
		#ifdef TARGET_ANDROID
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
		#endif
        #ifdef TARGET_IOS
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
        #endif
		
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderBuffer);


		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X, texID, 0);

		//?? is it needed?
		
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+1, texID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+2, texID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+3, texID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+4, texID, 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X+5, texID, 0);




		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

		switch(status)
		{
		case GL_FRAMEBUFFER_COMPLETE_EXT:
			*Console<<"FrameManager:: frame buffer added: "+frameName;
			FrameMap[frameName].FrameBuffer = frameBuffer;
			FrameMap[frameName].DepthRenderBuffer = depthRenderBuffer;
			FrameMap[frameName].TexID = texID;
			FrameMap[frameName].Width = width;
			FrameMap[frameName].Height = height;
			FrameMap[frameName].TexName = texName;

			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);

			return FrameMap[frameName].FrameBuffer;

			break;
		default:
			*Console<<"ERROR number : "+tostr(status)+endl;
			*Console<<"FrameManager::ERROR - frame buffer can not be added correctly: "+frameName;
			ResourceManager->TexList.DeleteTexture(texName);
			glDeleteRenderbuffers(1, &depthRenderBuffer);
			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
			glDeleteFramebuffers(1, &frameBuffer);

			return 0;
		}


	}
	else
		return FrameMap[frameName].FrameBuffer;
		
	#ifdef TARGET_ANDROID
	#undef GL_FRAMEBUFFER_COMPLETE_EXT
	#endif
    #ifdef TARGET_IOS
    #undef GL_FRAMEBUFFER_COMPLETE_EXT
    #endif

}

void TFrameManager::DeleteFrameRenderBuffer(const std::string& frameName)
{
	AssertIfInMainThread();

	if (FrameMap.count(frameName) > 0)
	{
		ResourceManager->TexList.DeleteTexture(FrameMap[frameName].TexName);
		glDeleteRenderbuffers(1, &(FrameMap[frameName].DepthRenderBuffer));
		glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
		glDeleteFramebuffers(1, &(FrameMap[frameName].FrameBuffer));

		FrameMap.erase(frameName);
	}
}

cardinal TFrameManager::AddDepthBuffer(const std::string& frameName, cardinal width, cardinal height)
{
	AssertIfInMainThread();

	#ifdef TARGET_ANDROID
	#define GL_FRAMEBUFFER_COMPLETE_EXT GL_FRAMEBUFFER_COMPLETE
	#endif
	
	if (FrameMap.count(frameName) == 0)
	{

		std::string texName = frameName + "Texture";
		cardinal depthTexID = ResourceManager->TexList.AddDepthTexture(texName,width,height);

		cardinal frameBuffer;
		cardinal status;


		glGenFramebuffers(1,&frameBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexID, 0);
		
		#ifdef TARGET_WIN32
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
		#endif
		
		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

		switch(status)
		{
		case GL_FRAMEBUFFER_COMPLETE:
			*Console<<"FrameManager:: frame buffer added: "+frameName;
			FrameMap[frameName].FrameBuffer = frameBuffer;
			FrameMap[frameName].DepthRenderBuffer = 0;
			FrameMap[frameName].TexID = depthTexID;
			FrameMap[frameName].Width = width;
			FrameMap[frameName].Height = height;
			FrameMap[frameName].TexName = texName;

			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);

			return FrameMap[frameName].FrameBuffer;
			break;
		default:
			*Console<<"FrameManager::ERROR - frame buffer can not be added correctly: "+frameName;
			ResourceManager->TexList.DeleteTexture(texName);
			glBindFramebuffer(GL_FRAMEBUFFER, CONST_SCREEN_FRAMEBUFFER);
			glDeleteFramebuffers(1, &frameBuffer);

			return 0;
		}


	}
	else
		return FrameMap[frameName].FrameBuffer;
		
	#ifdef TARGET_ANDROID
	#undef GL_FRAMEBUFFER_COMPLETE_EXT
	#endif
}



cardinal TFrameManager::GetFrameTexture(const std::string& frameName)
{
	if (FrameMap.count(frameName) > 0)
	{
		return FrameMap[frameName].TexID;
	}
	else
		return 0;
}


ivec2 TFrameManager::GetFrameWidthHeight(const std::string& frameName)
{
	if (FrameMap.count(frameName) == 0)
	{
		throw ErrorToLog("Frame "+frameName+" not found!");
	}

	return ivec2(FrameMap[frameName].Width, FrameMap[frameName].Height);
}


TTextureData TFrameManager::ReadFromBufferToTexData(const std::string& frameName)
{
	TTextureData texData;
	texData.Width = FrameMap[frameName].Width;
	texData.Height = FrameMap[frameName].Height;
	texData.DataSize = texData.Width * texData.Height * 3;

	texData.Data = boost::shared_array<char>(new char[texData.DataSize]);

	boost::shared_array<char> data(new char[texData.Width * texData.Height * 4]);

	glReadPixels(0, 0, FrameMap[frameName].Width, FrameMap[frameName].Height, GL_RGBA, GL_UNSIGNED_BYTE, &data[0]);

	for (cardinal i=0; i < texData.Width; i++)
	{
		for (cardinal j=0; j < texData.Height; j++)
		{
			int x = (i * texData.Height + j) + (i * texData.Height + j) + (i * texData.Height + j);

			int y = 4 * (i * texData.Height + j);

			texData.Data[x + 2] = data[y + 2];
			texData.Data[x + 1] = data[y + 1];
			texData.Data[x + 0] = data[y + 0];
		}
	}

	return texData;

}

} //namespace SE