/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Jay Taoko <jay.taoko@canonical.com>
 *              Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */
/** 
 * SECTION:ctk-render-target
 * @short_description: Represents a target for effects to render to.
 *
 * #CtkRenderTarget FIXME: needs elaboration
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include "ctk-render-target.h"

#include <stdlib.h>
#include <GL/glew.h>
#include <GL/glxew.h>

#include "ctk-utils.h"

struct _CtkRenderTarget 
{
  guint fbo;
  guint texture;
  guint depth;
  guint width;
  guint height;
  CtkRenderTargetFlags flags;
};

/* Forwards */

/*
 * Private Methods
 */
static CtkRenderTarget *
ctk_render_target_copy (const CtkRenderTarget *self)
{
  g_warning ("CtkRenderTarget does not support copying");
  return NULL;
}

/*
 * Creates a new CtkRenderTartget struct which is automatically zeroed out
 * Used g_slice_alloc to speed up allocations from in the internal pool
 */
static inline CtkRenderTarget *
ctk_render_target_allocate (void)
{
  CtkRenderTarget *self;

  /* Create a zero'd struct */
  self = g_slice_new0 (CtkRenderTarget);
  
  if (G_UNLIKELY (self == NULL))
    g_critical ("Unable to allocate CtkRenderTarget");

  self->fbo       = 0;
  self->texture   = 0;
  self->depth     = 0;
  self->width     = 0;
  self->height    = 0;
  self->flags     = 0;
  return self;
}


/*
 * Public Methods
 */
GType
ctk_render_target_get_type (void)
{
  static GType _ctk_render_target_type = 0;

  if (G_UNLIKELY (_ctk_render_target_type == 0))
    {
      _ctk_render_target_type =
        g_boxed_type_register_static ("CtkRenderTarget",
                                      (GBoxedCopyFunc)ctk_render_target_copy,
                                      (GBoxedFreeFunc)ctk_render_target_free);
    }

  return _ctk_render_target_type;
}


/**
 * ctk_render_target_new:
 *
 * Creates a new #CtkRenderTarget with only the fbo set
 *
 * Returns: a new #CtkRenderTarget
 **/
CtkRenderTarget *
ctk_render_target_new (void)
{
  CtkRenderTarget *self;

  self = ctk_render_target_allocate ();

  if (G_LIKELY (self))
    {
      CHECKGL (glGenFramebuffersEXT (1, &self->fbo));
      CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->fbo));
      CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0));
    }

  return self;
}


/**
* ctk_render_target_new_sized:
* @width: the width of the desired texture, can be zero
* @height: the height of the desired texture, can be zero
*
* As #ctk_render_target_new, but the @CtkRenderTarget will also have a sized
* texture attached if @width and @height are non-zero
*
* Returns: a new #CtkRenderTarget
**/
CtkRenderTarget *
ctk_render_target_new_sized (guint width,
                             guint height)
{
  CtkRenderTarget *self;

  /* Create a zero'd struct */
  self = ctk_render_target_allocate ();
  
  if (G_LIKELY (self))
  {
      GLenum status;

      if ((width == 0) || (height == 0))
        g_warning("[ctk_render_target_new_sized] Invalid rendertarget size \n");

      self->width = width;
      self->height = height;

      CHECKGL (glGenFramebuffersEXT (1, &self->fbo));
      CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->fbo));
      
      /* Depth */
      if ((self->width > 0) && (self->height > 0))
        {
          CHECKGL (glGenRenderbuffersEXT (1, &self->depth));
          CHECKGL (glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, self->depth));
          CHECKGL (glRenderbufferStorageEXT (GL_RENDERBUFFER_EXT,
                                                   GL_DEPTH_COMPONENT,
                                                   self->width,
                                                   self->height));
          CHECKGL (glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT,
                                                       GL_DEPTH_ATTACHMENT_EXT,
                                                       GL_RENDERBUFFER_EXT,
                                                       self->depth));

          /* Color */
          CHECKGL( glActiveTextureARB(GL_TEXTURE0) );
          CHECKGL (glGenTextures (1, &self->texture));
          CHECKGL (glBindTexture (GL_TEXTURE_2D, self->texture));
          CHECKGL (glTexImage2D (GL_TEXTURE_2D,
                                 0,
                                 GL_RGBA8,
                                 self->width,
                                 self->height,
                                 0,
                                 GL_RGBA,
                                 GL_UNSIGNED_BYTE,
                                 NULL));
          CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                    GL_TEXTURE_MAG_FILTER,
                                    GL_LINEAR));
          CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                    GL_TEXTURE_MIN_FILTER,
                                    GL_LINEAR));
          CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                    GL_TEXTURE_WRAP_S,
                                    GL_CLAMP));
          CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                    GL_TEXTURE_WRAP_T,
                                    GL_CLAMP));
          CHECKGL (glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT,
                                              GL_COLOR_ATTACHMENT0_EXT,
                                              GL_TEXTURE_2D,
                                              self->texture,
                                              0));
        }
      else
        {
          self->texture = 0;
          self->depth = 0;          
        }
        
      status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
      CHECKGL_MSG ("glCheckFramebufferStatusEXT");
      if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
        {
          g_critical ("FBO Error: %d\n", status);
        }

      CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0));
    }

  return self;
}


/**
* ctk_render_target_resize:
* @self: the #CtkRenderTarget to resize
* @width: the width of the desired texture
* @height: the height of the desired texture
*
* Will resize the texture associated with @self.
* WARNING: This function changes the binding of the render target. Calling this
* function will replace the previous render-target that was bound to the
* framebuffer
*
**/
void
ctk_render_target_resize (CtkRenderTarget *self,
                          guint            width,
                          guint            height)
{
  g_return_if_fail (self);

  if ((width == 0) || (height == 0))
    g_warning("[ctk_render_target_resize] Invalid rendertarget size \n");

  self->width = width;
  self->height = height;

  CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->fbo));

  if (self->depth)
    {
      CHECKGL (glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, self->depth));
      CHECKGL (glRenderbufferStorageEXT (GL_RENDERBUFFER_EXT,
                                         GL_DEPTH_COMPONENT,
                                         self->width,
                                         self->height));
      CHECKGL (glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT,
                                             GL_DEPTH_ATTACHMENT_EXT,
                                             GL_RENDERBUFFER_EXT,
                                             self->depth));
    }

  if (self->texture)
    {
      CHECKGL (glBindTexture (GL_TEXTURE_2D, self->texture));
      CHECKGL (glTexImage2D (GL_TEXTURE_2D,
                             0,
                             GL_RGBA8,
                             self->width,
                             self->height,
                             0,
                             GL_RGBA,
                             GL_UNSIGNED_BYTE,
                             NULL));
      CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                GL_TEXTURE_MAG_FILTER,
                                GL_LINEAR));
      CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                GL_TEXTURE_MIN_FILTER,
                                GL_LINEAR));
      CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                GL_TEXTURE_WRAP_S,
                                GL_CLAMP));

      CHECKGL (glTexParameteri (GL_TEXTURE_2D,
                                GL_TEXTURE_WRAP_T,
                                GL_CLAMP));
      CHECKGL (glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT,
                                          GL_COLOR_ATTACHMENT0_EXT,
                                          GL_TEXTURE_2D,
                                          self->texture,
                                          0));
    }
}

/**
* ctk_render_target_free:
* @self: the #CtkRenderTarget to free
*
* Will free itself, plus any memory allocated for any textures that are
* present.
*
**/
void
ctk_render_target_free (CtkRenderTarget *self)
{
  if (G_LIKELY (self != NULL))
    {
      if (self->texture)
        CHECKGL (glDeleteTextures (1, &self->texture));
      if (self->depth)
        CHECKGL (glDeleteRenderbuffersEXT (1, &self->depth));
      if (self->fbo)
        CHECKGL (glDeleteFramebuffersEXT (1, &self->fbo));

     g_slice_free (CtkRenderTarget, self);
   }
}

/**
* ctk_render_target_bind:
* @self: the #CtkRenderTarget to bind
*
* Will bind @self (and it's texture, if it exists) to the framebuffer
*
**/
void
ctk_render_target_bind (CtkRenderTarget *self)
{
  GLenum status;

  g_return_if_fail (self);

  CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, self->fbo));
  
  if (self->texture)
    CHECKGL (glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT,
                                        GL_COLOR_ATTACHMENT0_EXT,
                                        GL_TEXTURE_2D,
                                        self->texture,
                                        0));

  if (self->depth)
    CHECKGL (glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT,
                                           GL_DEPTH_ATTACHMENT_EXT,
                                           GL_RENDERBUFFER_EXT,
                                           self->depth));


  status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
  CHECKGL_MSG ("glCheckFramebufferStatusEXT");
  if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
    {
      g_critical ("Incomplete render target: %d", status);
    }
    
  if ((self->width == 0) || (self->height == 0))
    g_warning("[ctk_render_target_bind] Invalid rendertarget size \n");

  CHECKGL (glViewport (0, 0, self->width, self->height));
}


/**
* ctk_render_target_unbind:
* @self: the #CtkRenderTarget to unbind
*
* Will unbind @self from the framebuffer
*
**/
void
ctk_render_target_unbind (void)
{
  CHECKGL (glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0));
}


/**
* ctk_render_target_get_width:
* @self: a #CtkRenderTarget
*
* Will retrieve the width of @self
*
* Returns: the width of @self
**/
guint
ctk_render_target_get_width (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->width;
}


/**
* ctk_render_target_get_height:
* @self: a #CtkRenderTarget
*
* Will retrieve the height of @self
*
* Returns: the height of @self
**/
guint
ctk_render_target_get_height (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->height;
}


/**
* ctk_render_target_get_size:
* @self: a #CtkRenderTarget
* @width: (out): a location to store the width
* @height: (out): a location to store the height
*
* Will populate @width and @height with the size of @self
**/
void
ctk_render_target_get_size (CtkRenderTarget *self,
                            guint           *width,
                            guint           *height)
{
  g_return_if_fail (self);

  if (width)
    *width = self->width;

  if (height)
    *height = self->height;
}

/**
* ctk_render_target_get_height:
* @self: a #CtkRenderTarget
*
* Will retrieve the opengl id of the depth buffer of @self
*
* Returns: the opengl id of the depth buffer of @self
**/
guint
ctk_render_target_get_depth_buffer_ogl_id (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->depth;
}

/**
* ctk_render_target_get_height:
* @self: a #CtkRenderTarget
*
* Will retrieve the opengl id of the color buffer of @self
*
* Returns: the opengl id of the color of @self
**/
guint 
ctk_render_target_get_color_buffer_ogl_id (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->texture;
}

/**
* ctk_render_target_get_height:
* @self: a #CtkRenderTarget
*
* Will retrieve the opengl id of the frame buffer of @self
*
* Returns: the opengl id of the frame buffer of @self
**/
guint
ctk_render_target_get_frame_buffer_ogl_id (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->fbo;
}

/**
* ctk_render_target_get_flags:
* @self: a #CtkRenderTarget
*
* Returns: the current #CtkRenderTargetFlags for @self
*
**/
CtkRenderTargetFlags
ctk_render_target_get_flags (CtkRenderTarget *self)
{
  g_return_val_if_fail (self, 0);

  return self->flags;
}


/**
* ctk_render_target_set_flags:
* @self: a #CtkRenderTarget
* @flags: #CtkRenderTargetFlags
*
* Sets the #CtkRenderTargetFlags of @self to @flags
*
**/
void
ctk_render_target_set_flags (CtkRenderTarget      *self,
                             CtkRenderTargetFlags  flags)
{
  g_return_if_fail (self);

  self->flags = flags;
}
