// Author of original C++ version: Alex V. Boreskoff <alexboreskoff@mtu-net.ru>, <steps3d@narod.ru>

#include "quakedef.h"
#include "gl_BufferFBO.h"

static	boolean	FBO_Already_Init = false;

fbuffer_t fbuffer;

void FBO_Set (int FBO_ID, int theWidth, int theHeight, int bpp, int stensil)
{
	int i;
	fbuffer.frameBuffer		= FBO_ID;
	fbuffer.width			= theWidth;
	fbuffer.height			= theHeight;
	fbuffer.bpp				= bpp;
	fbuffer.stencil			= stensil;
	//fbuffer.frameBuffer		= 0;
	fbuffer.depthBuffer		= 0;
	fbuffer.stencilBuffer	= 0;
	
	for ( i = 0; i < 8; i++ )
		fbuffer.colorBuffer [i] = 0;
}

boolean	FBO_Create (void)
{
	GLenum status;
	int	depthFormat   = 0;
	int stencilFormat = 0;

	if ( fbuffer.width <= 0 || fbuffer.height <= 0 )
		return false;

	glGenFramebuffersEXT ( 1, &fbuffer.frameBuffer );
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, fbuffer.frameBuffer );

	if ( fbuffer.bpp == 16 )
		depthFormat = GL_DEPTH_COMPONENT16_ARB;
	else if ( fbuffer.bpp == 24 )
		depthFormat = GL_DEPTH_COMPONENT24_ARB;
	else if ( fbuffer.bpp == 32 )
		depthFormat = GL_DEPTH_COMPONENT32_ARB;

	if ( fbuffer.stencil == stencil1 )
		stencilFormat = GL_STENCIL_INDEX1_EXT;
	else if ( fbuffer.stencil == stencil4 )
		stencilFormat = GL_STENCIL_INDEX4_EXT;
	else if ( fbuffer.stencil == stencil8 )
		stencilFormat = GL_STENCIL_INDEX8_EXT;
	else if ( fbuffer.stencil == stencil16 )
		stencilFormat = GL_STENCIL_INDEX16_EXT;

	if ( depthFormat != 0 )
	{
		glGenRenderbuffersEXT        ( 1, &fbuffer.depthBuffer );
		glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, fbuffer.depthBuffer );
		glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, depthFormat, fbuffer.width, fbuffer.height );
		glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT,  GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbuffer.depthBuffer );
	}

	if ( stencilFormat != 0 )
	{
		glGenRenderbuffersEXT        ( 1, &fbuffer.stencilBuffer );
		glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, fbuffer.stencilBuffer );
		glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, stencilFormat, fbuffer.width, fbuffer.height );
		glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT,  GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbuffer.stencilBuffer );
	}

	status = glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT );
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, 0 );

	status = GL_FRAMEBUFFER_COMPLETE_EXT;
	//Con_Printf("BPP %i, Stensil %i, Status %i", depthFormat, stencilFormat, status);
	return status;
}

void FBO_Error (void)
{
    // check FBO status
    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    switch(status)
    {
    case GL_FRAMEBUFFER_COMPLETE_EXT: //Framebuffer complete
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: 
		Con_Printf ( "[ERROR] Framebuffer incomplete: Attachment is NOT complete\n" );
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
		Con_Printf ( "[ERROR] Framebuffer incomplete: No image is attached to FBO\n" );
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
		Con_Printf ( "[ERROR] Framebuffer incomplete: Attached images have different dimensions\n" );
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
		Con_Printf ( "[ERROR] Framebuffer incomplete: Color attached images have different internal formats\n" );
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:	
		Con_Printf ( "[ERROR] Framebuffer incomplete: Draw buffer\n" );
        return;
    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:	
		Con_Printf ( "[ERROR] Framebuffer incomplete: Read buffer\n" );
        return;
    case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
		Con_Printf ( "[ERROR] Unsupported by FBO implementation\n" );
        return;
    default:	
		Con_Printf ( "[ERROR] Unknow error\n" );
        return;
    }
}

void FBO_Init (void)
{
	if (FBO_Already_Init)
		return;

	FBO_Already_Init = true;

	if (!FBO_Create() && developer.value)
	{
		Con_Printf ( "FBuffer create error\n" );
		FBO_Error ();
	}
}

void FBO_Delete (void)
{
	if ( fbuffer.depthBuffer != 0 )		glDeleteRenderbuffersEXT	( 1, &fbuffer.depthBuffer );
	if ( fbuffer.stencilBuffer != 0 )	glDeleteRenderbuffersEXT	( 1, &fbuffer.stencilBuffer );
	if ( fbuffer.frameBuffer != 0 )		glDeleteFramebuffersEXT		( 1, &fbuffer.frameBuffer );
	if (developer.value)
		Con_Printf ( "FBuffer deleted\n" );
}

boolean	FBO_Activate (void)
{
	if ( fbuffer.frameBuffer == 0 )
		return false;

	glFlush ();
								// save current viewport
	glGetIntegerv        ( GL_VIEWPORT, fbuffer.saveViewport );
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, fbuffer.frameBuffer );
	glReadBuffer         ( GL_COLOR_ATTACHMENT0_EXT );
	glDrawBuffer         ( GL_COLOR_ATTACHMENT0_EXT );
	glViewport           ( 0, 0, fbuffer.width , fbuffer.height );
	return true;
}

boolean	FBO_Deactivate (void)
{
	if ( fbuffer.frameBuffer == 0 )
		return false;

	glFlush              ();
	glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, 0 );
	glViewport           ( fbuffer.saveViewport [0], fbuffer.saveViewport [1], fbuffer.saveViewport [2], fbuffer.saveViewport [3] );
	return true;
}

/*
boolean	FrameBufferBindFace ( int face )
{
//	glBindTexture             ( GL_TEXTURE_CUBE_MAP_ARB, colorBuffer [0]  );
	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + face, fbuffer.colorBuffer [0], 0 );

	return FrameBufferBind ();
}
*/

boolean	FrameBufferAddTexture ( GLenum target, unsigned textureId, int ColorBuffer_N )
{
	if ( fbuffer.frameBuffer == 0 )	return false;

	if ( target != GL_TEXTURE_2D && target != GL_TEXTURE_RECTANGLE_ARB && (target < GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB) )
		return false;

	glBindTexture             ( target, fbuffer.colorBuffer [ColorBuffer_N] = textureId );
	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + ColorBuffer_N, target, textureId, 0 );
	return true;
}

boolean	FrameBufferAddDepthTexture ( GLenum target, unsigned textureId )
{
	if ( fbuffer.frameBuffer == 0 )	return false;
	
	fbuffer.depthBuffer = textureId;
	glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, target, textureId, 0 );
	return true;
}

unsigned FrameBufferCreateColorTexture ( GLenum format, GLenum internalFormat, GLenum clamp, int filter )
{
	GLuint	tex;

	glGenTextures   ( 1, &tex );
    glBindTexture   ( GL_TEXTURE_2D, tex );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment
    glTexImage2D    ( GL_TEXTURE_2D, 0, internalFormat, fbuffer.width, fbuffer.height, 0, format, GL_UNSIGNED_BYTE, NULL );

	if ( filter == filterNearest )
	{
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	}
	else
	if ( filter == filterLinear )
	{
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	}
	else
	if ( filter == filterMipmap )
	{
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	}
	
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp );
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp );

    return tex;
}

unsigned FrameBufferCreateColorRectTexture ( GLenum format, GLenum internalFormat )
{
	GLuint	tex;

	glGenTextures   ( 1, &tex );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment
    glBindTexture   ( GL_TEXTURE_RECTANGLE_ARB, tex );
    glTexImage2D    ( GL_TEXTURE_RECTANGLE_ARB, 0, internalFormat, fbuffer.width, fbuffer.height, 0, format, GL_UNSIGNED_BYTE, NULL );

	glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST   );
	glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST   );

    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP );

    return tex;
}

const unsigned	getColorBuffer ( int ColorBuffer_N )
{
	return fbuffer.colorBuffer [ColorBuffer_N];
}



//static GLuint	frameBuffer;					// id of framebuffer object

//GLuint fboId;                       // ID of FBO
//GLuint rboId;                       // ID of Renderbuffer object
//GLuint textureId;                   // ID of texture
//boolean fboSupported;
//boolean fboUsed;

// constants
//const int TEXTURE_WIDTH = 256;
//const int TEXTURE_HEIGHT = 256;

/*const boolean	FrameBufferOk ()
{
	int	 status;
	GLint	currentFb;

	glGetIntegerv ( GL_FRAMEBUFFER_BINDING_EXT, &currentFb );

	if ( currentFb != fbuffer.frameBuffer )
	{
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, fbuffer.frameBuffer );
		glReadBuffer         ( GL_COLOR_ATTACHMENT0_EXT );
	}

	status = glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT );

	if ( currentFb != fbuffer.frameBuffer )
		glBindFramebufferEXT ( GL_FRAMEBUFFER_EXT, currentFb );

	return 	status == GL_FRAMEBUFFER_COMPLETE_EXT;
}*/


/*

        // check once again FBO extension
        if(glGenFramebuffersEXT && glDeleteFramebuffersEXT && glBindFramebufferEXT && glCheckFramebufferStatusEXT &&
           glGetFramebufferAttachmentParameterivEXT && glGenerateMipmapEXT && glFramebufferTexture2DEXT && glFramebufferRenderbufferEXT &&
           glGenRenderbuffersEXT && glDeleteRenderbuffersEXT && glBindRenderbufferEXT && glRenderbufferStorageEXT &&
           glGetRenderbufferParameterivEXT && glIsRenderbufferEXT)
        {
            fboSupported = fboUsed = true;
            //cout << "Video card supports GL_EXT_framebuffer_object." << endl;
        }
        else
        {
            fboSupported = fboUsed = false;
            //cout << "Video card does NOT support GL_EXT_framebuffer_object." << endl;
        }

  
          // create a framebuffer object, you need to delete them when program exits.
        glGenFramebuffersEXT(1, &fboId);
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);

        // create a renderbuffer object to store depth info
        // NOTE: A depth renderable image should be attached the FBO for depth test.
        // If we don't attach a depth renderable image to the FBO, then
        // the rendering output will be corrupted because of missing depth test.
        // If you also need stencil test for your rendering, then you must
        // attach additional image to the stencil attachement point, too.
        glGenRenderbuffersEXT(1, &rboId);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);
        glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, TEXTURE_WIDTH, TEXTURE_HEIGHT);
        glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

        // attach a texture to FBO color attachement point
        glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, textureId, 0);

        // attach a renderbuffer to depth attachment point
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rboId);

        //@ disable color buffer if you don't attach any color buffer image,
        //@ for example, rendering depth buffer only to a texture.
        //@ Otherwise, glCheckFramebufferStatusEXT will not be complete.
        //glDrawBuffer(GL_NONE);
        //glReadBuffer(GL_NONE);

        // check FBO status
        //printFramebufferInfo();
        status = checkFramebufferStatus();
        if(!status)
            fboUsed = false;

        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

*/
//=============================================
/*
int		FrameBufferMaxColorAttachemnts ()
{
    GLint n;
    glGetIntegerv ( GL_MAX_COLOR_ATTACHMENTS_EXT, &n );
	return n;
}

int		FrameBufferMaxSize ()
{
    GLint sz;
    glGetIntegerv ( GL_MAX_RENDERBUFFER_SIZE_EXT, &sz );
	return sz;
}
*/