#include "GPU_physics.h"

// #define FBO_USE_NVIDIA_FLOAT_TEXTURE_EXTENSION  1
#include "fboSupport.h"

#ifdef FBO_USE_NVIDIA_FLOAT_TEXTURE_EXTENSION

/* nVidia float texture extension */
#define FBO_R_HALFFLOAT     GL_FLOAT_R16_NV
#define FBO_RG_HALFFLOAT    GL_FLOAT_RG16_NV
#define FBO_RGB_HALFFLOAT   GL_FLOAT_RGB16_NV
#define FBO_RGBA_HALFFLOAT  GL_FLOAT_RGBA16_NV

#define FBO_R_FLOAT         GL_FLOAT_R32_NV
#define FBO_RG_FLOAT        GL_FLOAT_RG32_NV
#define FBO_RGB_FLOAT       GL_FLOAT_RGB32_NV
#define FBO_RGBA_FLOAT      GL_FLOAT_RGBA32_NV

#else

/* ATI float texture extension */
#define FBO_R_HALFFLOAT     GL_LUMINANCE_FLOAT16_ATI
#define FBO_RG_HALFFLOAT    GL_LUMINANCE_ALPHA_FLOAT16_ATI
#define FBO_RGB_HALFFLOAT   GL_RGB_FLOAT16_ATI
#define FBO_RGBA_HALFFLOAT  GL_RGBA_FLOAT16_ATI

#define FBO_R_FLOAT         GL_LUMINANCE_FLOAT32_ATI
#define FBO_RG_FLOAT        GL_LUMINANCE_ALPHA_FLOAT32_ATI
#define FBO_RGB_FLOAT       GL_RGB_FLOAT32_ATI
#define FBO_RGBA_FLOAT      GL_RGBA_FLOAT32_ATI

#endif

static void checkFrameBufferStatus ()
{
  GLenum status ;

  status = glCheckFramebufferStatusEXT ( GL_FRAMEBUFFER_EXT ) ;

  switch ( status )
  {
    case GL_FRAMEBUFFER_COMPLETE_EXT :
      break ;

    case GL_FRAMEBUFFER_UNSUPPORTED_EXT :
      fprintf ( stderr, "ERROR: Unsupported FBO setup.\n" ) ;
      exit ( 1 ) ;

    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT          :
      fprintf ( stderr, "WARNING: Incomplete FBO attachment.\n" ) ;
      break ;

    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT  :
      fprintf ( stderr, "WARNING: Incomplete FBO - missing attachment.\n" ) ;
      break ;

#ifdef GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT
    case GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
      fprintf ( stderr, "WARNING: Incomplete FBO - duplicate attachment.\n" ) ;
      break ;
#endif

    case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT          :
      fprintf ( stderr, "WARNING: Incomplete FBO - improper dimensions.\n" ) ;
      break ;

    case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT             :
      fprintf ( stderr, "WARNING: Incomplete FBO - improper formats.\n" ) ;
      break ;

    case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT         :
      fprintf ( stderr, "WARNING: Incomplete FBO draw buffer.\n" ) ;
      break ;

    case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT         :
      fprintf ( stderr, "WARNING: Incomplete FBO read buffer.\n" ) ;
      break ;

    default :
      fprintf ( stderr, "WARNING: Unexpected FBO status : 0x%04x\n", status ) ;
      break ;
  }
}



FrameBufferObject::FrameBufferObject ( int         _width ,
                                       int         _height,
                                       int         _ncomps,
                                       fboDataType _type  )
{
  if ( (_width & (_width-1)) == 0 )
    width  = _width  ;
  else
  {
    fprintf ( stderr, "FBO: Non-power of two width!\n" ) ;
    width  = 512 ;
  }

  if ( (_height & (_height-1)) == 0 )
    height  = _height  ;
  else
  {
    fprintf ( stderr, "FBO: Non-power of two height!\n" ) ;
    width  = 512 ;
  }

  ncomps = _ncomps ;
  type   = _type   ;

  switch ( ncomps )
  {
    case 1 : format = GL_LUMINANCE       ; break ;
    case 2 : format = GL_LUMINANCE_ALPHA ; break ;
    case 3 : format = GL_RGB             ; break ;
    case 4 : format = GL_RGBA            ; break ;
    default: fprintf ( stderr, "Illegal number of components"
                               " in a FrameBufferObject.\n" ) ;
             ncomps = 4 ; format = GL_RGBA ; break ;
  }


  switch ( type )
  {
    case FBO_BYTE           :
    case FBO_UNSIGNED_BYTE  : iformat = format ; break ;

    case FBO_INT            :
    case FBO_UNSIGNED_INT   :
      fprintf ( stderr, "FBO: GL_INT/GL_UINT textures are unsupported,"
                        " truncating to 16 bits per component" ) ;
      /* FALL THROUGH */
    case FBO_SHORT          :
    case FBO_UNSIGNED_SHORT :
      switch ( ncomps )
      {
        case 1 : iformat = GL_LUMINANCE16         ; break ;
        case 2 : iformat = GL_LUMINANCE16_ALPHA16 ; break ;
        case 3 : iformat = GL_RGB16               ; break ;
        case 4 : iformat = GL_RGBA16              ; break ;
      }
      break ;

    case FBO_DOUBLE :
      fprintf ( stderr, "FBO: GL_DOUBLE textures are unsupported,"
                        " truncating to GL_FLOAT per component" ) ;
      /* FALL THROUGH */
    case FBO_FLOAT  :
      switch ( ncomps )
      {
        case 1 : iformat = FBO_R_FLOAT           ; break ;
        case 2 : iformat = FBO_RG_FLOAT          ; break ;
        case 3 : iformat = FBO_RGB_FLOAT         ; break ;
        case 4 : iformat = FBO_RGBA_FLOAT        ; break ;
      }
      break ;

    case FBO_HALF   :
      switch ( ncomps )
      {
        case 1 : iformat = FBO_R_HALFFLOAT       ; break ;
        case 2 : iformat = FBO_RG_HALFFLOAT      ; break ;
        case 3 : iformat = FBO_RGB_HALFFLOAT     ; break ;
        case 4 : iformat = FBO_RGBA_HALFFLOAT    ; break ;
      }
      break ;

    default :
      fprintf ( stderr, "FBO: Unsupported data type?!?" ) ;
      break ;
  }

  glGenTextures ( 1, & textureHandle ) ;
  glBindTexture   ( GL_TEXTURE_2D, textureHandle ) ;
  glTexParameterf ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ) ;
  glTexParameterf ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ) ;
  //glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ) ;
  //glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ) ;
  fillTexture ( (void *) NULL ) ;

  glGenFramebuffersEXT  ( 1, & fboHandle     ) ;
  glBindFramebufferEXT  ( GL_FRAMEBUFFER_EXT, fboHandle ) ;

  glFramebufferTexture2DEXT ( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                              GL_TEXTURE_2D, textureHandle, 0 ) ;

#ifdef NEED_DEPTH_BUFFER
  static GLuint depth_rb = 0 ;

  if ( depth_rb == 0 )
  {
    glGenRenderbuffersEXT        ( 1, & depth_rb ) ;
    glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, depth_rb ) ;
    glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24,
                                                        width, height ) ;
    glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT,  GL_DEPTH_ATTACHMENT_EXT,
                                   GL_RENDERBUFFER_EXT, depth_rb ) ;
  }
  else
    glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                                   GL_RENDERBUFFER_EXT, depth_rb ) ;
#else
  glDisable ( GL_DEPTH_TEST ) ;
  glDepthMask ( 0 ) ;
  glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_DEPTH_ATTACHMENT_EXT,
                                 GL_RENDERBUFFER_EXT, 0 ) ;
#endif

#ifdef NEED_STENCIL_BUFFER
  static GLuint stencil_rb = 0 ;

  if ( stencil_rb == 0 )
  {
    glGenRenderbuffersEXT        ( 1, & stencil_rb ) ;
    glBindRenderbufferEXT        ( GL_RENDERBUFFER_EXT, stencil_rb ) ;
    glRenderbufferStorageEXT     ( GL_RENDERBUFFER_EXT, GL_STENCIL_INDEX,
                                                        width, height ) ;
    glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_STENCIL_ATTACHMENT_EXT,
                                   GL_RENDERBUFFER_EXT, stencil_rb ) ;
  }
  else
    glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_STENCIL_ATTACHMENT_EXT,
                                   GL_RENDERBUFFER_EXT, stencil_rb ) ;
#else
  glDisable ( GL_STENCIL_TEST ) ;
  glStencilMask ( 0 ) ;
  glFramebufferRenderbufferEXT ( GL_FRAMEBUFFER_EXT , GL_STENCIL_ATTACHMENT_EXT,
                                 GL_RENDERBUFFER_EXT, 0 ) ;
#endif

  // Check framebuffer completeness at the end of initialization.

  checkFrameBufferStatus () ;
  restoreFrameBuffer () ;
}



void FrameBufferObject::fetchTexture ( void *data )
{
  glBindTexture ( GL_TEXTURE_2D, textureHandle ) ;
  glGetTexImage ( GL_TEXTURE_2D, 0,             /* MIP level...zero  */
                                format,        /* External format   */
                                type,          /* Data type         */
                                data           /* Image data        */ ) ;
}



void FrameBufferObject::fetchTexture ( unsigned char *data )
{
  if ( type != FBO_UNSIGNED_BYTE )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fetchTexture ( (void *)data ) ;
}



void FrameBufferObject::fetchTexture ( unsigned short *data )
{
  if ( type != FBO_UNSIGNED_SHORT )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fetchTexture ( (void *)data ) ;
}



void FrameBufferObject::fetchTexture ( float *data )
{
  if ( type != FBO_FLOAT )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fetchTexture ( (void *)data ) ;
}



void FrameBufferObject::fillTexture ( void *data )
{
  glBindTexture( GL_TEXTURE_2D, textureHandle ) ;
  glTexImage2D ( GL_TEXTURE_2D, 0,             /* MIP level...zero  */
                                iformat,       /* Internal format   */
                                width, height, /* Size              */
                                0,             /* Border...false    */
                                format,        /* External format   */
                                type,          /* Data type         */
                                data           /* Image data        */ ) ;
}



void FrameBufferObject::fillTexture ( unsigned char *data )
{
  if ( type != FBO_UNSIGNED_BYTE )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fillTexture ( (void *)data ) ;
}

void FrameBufferObject::fillTexture ( unsigned short *data )
{
  if ( type != FBO_UNSIGNED_SHORT )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fillTexture ( (void *)data ) ;
}


void FrameBufferObject::fillTexture ( float *data )
{
  if ( type != FBO_FLOAT )
  {
    fprintf ( stderr, "FBO: Data format mismatch!" ) ;
    return ;
  }

  fillTexture ( (void *)data ) ;
}


void FrameBufferObject::prepare ( bool clear )
{
  makeDestination () ;

  glViewport        ( 0, 0, width, height ) ;

  if ( clear ) glClear ( GL_COLOR_BUFFER_BIT ) ;

  glMatrixMode      ( GL_PROJECTION ) ;
  glLoadIdentity    () ;
  glOrtho           ( -1, 1, -1, 1, -1, 1 ) ;

  glMatrixMode      ( GL_MODELVIEW ) ;
  glLoadIdentity    () ;

  glDisable         ( GL_DEPTH_TEST ) ;
  glDisable         ( GL_CULL_FACE  ) ;
  glDisable         ( GL_BLEND      ) ;
}


void FrameBufferObject::fill ()
{
  float s_min = 0.5f / (float) width ;
  float s_max = (((float) width) - 0.5f) / (float) width ;
  float t_min = 0.5f / (float) height ;
  float t_max = (((float) height) - 0.5f) / (float) height ;

  glBegin ( GL_QUADS ) ;
  glTexCoord2f ( s_min, t_min ) ; glVertex2f ( -1, -1 ) ;
  glTexCoord2f ( s_max, t_min ) ; glVertex2f (  1, -1 ) ;
  glTexCoord2f ( s_max, t_max ) ; glVertex2f (  1,  1 ) ;
  glTexCoord2f ( s_min, t_max ) ; glVertex2f ( -1,  1 ) ;
  glEnd () ;
}


