/**
 * projectM -- Milkdrop-esque visualisation SDK
 * Copyright (C)2003-2004 projectM Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * See 'LICENSE.txt' included within this release
 *
 */
/**
 * $Id: FBO.c,v 1.1.1.1 2005/12/23 18:05:00 psperl Exp $
 *
 * Render this methods
 */

#include <stdio.h>
//#include <GL/gl.h>
#include <iostream>
#include "Common.hpp"
#include "FBO.hpp"



RenderTarget::~RenderTarget() {


	glDeleteTextures( 1, &this->textureID[0]);

#ifdef USE_FBO
	if (useFBO) 
         {
		glDeleteTextures( 1, &this->textureID[1] );
		glDeleteRenderbuffersEXT(1,  &this->depthb[0]);
		glDeleteFramebuffersEXT(1, &this->fbuffer[0]);
		if(renderToTexture)
		  {
		    glDeleteTextures( 1, &this->textureID[2] );
		    glDeleteRenderbuffersEXT(1,  &this->depthb[1]);
		    glDeleteFramebuffersEXT(1, &this->fbuffer[1]);
		  }
         }
#endif

}

GLuint RenderTarget::initRenderToTexture()
{
#ifdef USE_FBO

  if (this->useFBO==1)
    {
      this->renderToTexture=1;
      
      GLuint   fb2, depth_rb2;
      glGenFramebuffersEXT(1, &fb2);
      glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fb2 );
      glGenRenderbuffersEXT(1, &depth_rb2);
      glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, depth_rb2 );
      
      glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, this->texsize,this->texsize  );
      glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb2 );         
      this->fbuffer[1] = fb2;
      this->depthb[1]=  depth_rb2;
      glGenTextures(1, &this->textureID[2]);
      glBindTexture(GL_TEXTURE_2D, this->textureID[2]); 
      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, texsize, texsize, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
      glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, this->textureID[2], 0 );
      return this->textureID[2];
    }
#endif 
return -1;
      
}

/** Creates new pbuffers */
RenderTarget::RenderTarget(int texsize, int width, int height) : useFBO(false) {

   int mindim = 0;
   int origtexsize = 0;

   this->renderToTexture = 0;
   this->texsize = texsize;

#ifdef USE_FBO
   glewInit();
      // Forceably disable FBO if user requested it but the video card / driver lacks
      // the appropraite frame buffer extension.
      if (useFBO = glewIsSupported("GL_EXT_framebuffer_object"))
	{	 

	  GLuint   fb,  depth_rb, rgba_tex,  other_tex;
	  glGenFramebuffersEXT(1, &fb);
	  glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fb );
	  
	  glGenRenderbuffersEXT(1, &depth_rb);
	  glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, depth_rb );
	  glRenderbufferStorageEXT( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, this->texsize,this->texsize  );
	  glFramebufferRenderbufferEXT( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth_rb );
	  this->fbuffer[0] = fb;
	  this->depthb[0]=  depth_rb;
	  
	  glGenTextures(1, &other_tex);
	  glBindTexture(GL_TEXTURE_2D,other_tex);
	  glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, texsize, texsize, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
	  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	  //glGenerateMipmapEXT(GL_TEXTURE_2D);
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	  
	  
	  
	  glGenTextures(1, &rgba_tex);
	  glBindTexture(GL_TEXTURE_2D, rgba_tex); 
	  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	  glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, texsize, texsize, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	  //glGenerateMipmapEXT(GL_TEXTURE_2D);
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	  
	  
	  
	  glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, rgba_tex, 0 );         
	  this->textureID[0] = rgba_tex;
	  this->textureID[1] = other_tex; 
	  
	  GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	  if (status == GL_FRAMEBUFFER_COMPLETE_EXT) {
	    return;
	  }	
	  std::cerr << "[projecM] warning: FBO support not detected. Using fallback." << std::endl;
	}

#endif 

// Can reach here via two code paths: 
// (1) useFBO was set to false externally by cmake / system setting / etc.
// (2) useFBO was true but forced to false as it failed to pass all the GLU extension checks.

    /** Fallback pbuffer creation via teximage hack */
    /** Check the texture size against the viewport size */
    /** If the viewport is smaller, then we'll need to scale the texture size down */
    /** If the viewport is larger, scale it up */
    mindim = width < height ? width : height;
    origtexsize = this->texsize;
    this->texsize = nearestPower2( mindim, SCALE_MINIFY );
        glGenTextures(1, &this->textureID[0] );

        glBindTexture(GL_TEXTURE_2D, this->textureID[0] );
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D,
		    0,
		    GL_RGB,
		    this->texsize, this->texsize,
		    0,
		    GL_RGBA,
		    GL_UNSIGNED_BYTE,
		    NULL);
      

   
    return;
  }

  void RenderTarget::fallbackRescale(int width, int height)
  {
	int mindim = width < height ? width : height;
    int origtexsize = this->texsize;
    this->texsize = nearestPower2( mindim, SCALE_MINIFY );

    if (origtexsize == texsize)
      return;

    /* Create the texture that will be bound to the render this */
    /*

        if ( this->texsize != origtexsize ) {

            glDeleteTextures( 1, &this->textureID[0] );
          }
    */

        glGenTextures(1, &this->textureID[0] );

        glBindTexture(GL_TEXTURE_2D, this->textureID[0] );
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D,
		    0,
		    GL_RGB,
		    this->texsize, this->texsize,
		    0,
		    GL_RGBA,
		    GL_UNSIGNED_BYTE,
		    NULL);
      

  }

/** Destroys the pbuffer */

/** Locks the pbuffer */
void RenderTarget::lock() {

#ifdef USE_FBO
  if(this->useFBO)
    { 
      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, this->fbuffer[0]);     
    }
#endif
    }

/** Unlocks the pbuffer */
void RenderTarget::unlock() {

#ifdef USE_FBO
  if(this->useFBO)
    {
      glBindTexture( GL_TEXTURE_2D, this->textureID[1] );
      glCopyTexSubImage2D( GL_TEXTURE_2D,
                         0, 0, 0, 0, 0, 
                         this->texsize, this->texsize );
      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
      return;
    }
#endif
    /** Fallback texture path */
    
    glBindTexture( GL_TEXTURE_2D, this->textureID[0] );
    
	glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, this->texsize, this->texsize );
  }

/** 
 * Calculates the nearest power of two to the given number using the
 * appropriate rule
 */
int RenderTarget::nearestPower2( int value, TextureScale scaleRule ) {

    int x = value;
    int power = 0;

    while ( ( x & 0x01 ) != 1 ) {
        x >>= 1;
      }

    if ( x == 1 ) {
        return value;
      } else {
        x = value;
        while ( x != 0 ) {
            x >>= 1;
            power++;
          }
        switch ( scaleRule ) {
            case SCALE_NEAREST:
                if ( ( ( 1 << power ) - value ) <= ( value - ( 1 << ( power - 1 ) ) ) ) {
                    return 1 << power;
                  } else {
                    return 1 << ( power - 1 );
                  }
            case SCALE_MAGNIFY:
                return 1 << power;
            case SCALE_MINIFY:
                return 1 << ( power - 1 );
            default:
                break;
          }
      }
    return 0;
  }