/* 
 * GStreamer
 * Copyright (C) 2013 Matthew Waters <ystreet00@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gl.h"
#include "gstglframebuffer.h"

GST_DEBUG_CATEGORY_STATIC (gst_gl_framebuffer_debug);
#define GST_CAT_DEFAULT gst_gl_framebuffer_debug

#define DEBUG_INIT \
  GST_DEBUG_CATEGORY_INIT (gst_gl_framebuffer_debug, "glframebuffer", 0, "GL Framebuffer");

G_DEFINE_TYPE_WITH_CODE (GstGLFramebuffer, gst_gl_framebuffer, G_TYPE_OBJECT,
    DEBUG_INIT);

#define GST_GL_FRAMEBUFFER_GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_TYPE_GL_FRAMEBUFFER, GstGLFramebufferPrivate))

static void gst_gl_framebuffer_finalize (GObject * object);

struct _GstGLFramebufferPrivate
{
  gint width;
  gint height;

  guint fbo;
  guint depth;
};

static void
gst_gl_framebuffer_class_init (GstGLFramebufferClass * klass)
{
  g_type_class_add_private (klass, sizeof (GstGLFramebufferPrivate));

  G_OBJECT_CLASS (klass)->finalize = gst_gl_framebuffer_finalize;
}

static void
gst_gl_framebuffer_init (GstGLFramebuffer * fbo)
{
  fbo->priv = GST_GL_FRAMEBUFFER_GET_PRIVATE (fbo);
}

static void
gst_gl_framebuffer_finalize (GObject * object)
{
  GstGLFramebuffer *fbo = GST_GL_FRAMEBUFFER (object);

  if (fbo->context) {
    gst_object_unref (fbo->context);
    fbo->context = NULL;
  }

  G_OBJECT_CLASS (gst_gl_framebuffer_parent_class)->finalize (object);
}

GstGLFramebuffer *
gst_gl_framebuffer_new (GstGLContext * context)
{
  GstGLFramebuffer *fbo = g_object_new (GST_TYPE_GL_FRAMEBUFFER, NULL);

  fbo->context = gst_object_ref (context);

  return fbo;
}

gboolean
gst_gl_framebuffer_generate (GstGLFramebuffer * frame, gint width, gint height,
    guint * fbo, guint * depth)
{
  GLuint fake_texture = 0;
  const GstGLFuncs *gl;
  GLenum internal_format;

  g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (frame), FALSE);
  g_return_val_if_fail (fbo != NULL && depth != NULL, FALSE);
  g_return_val_if_fail (width > 0 && height > 0, FALSE);

  gl = frame->context->gl_vtable;

  GST_TRACE ("creating FBO dimensions:%ux%u", width, height);

  if (!gl->GenFramebuffers) {
    gst_gl_context_set_error (frame->context,
        "Context, EXT_framebuffer_object not supported");
    return FALSE;
  }
  /* setup FBO */
  gl->GenFramebuffers (1, fbo);
  gl->BindFramebuffer (GL_FRAMEBUFFER, *fbo);

  /* setup the render buffer for depth */
  gl->GenRenderbuffers (1, depth);
  gl->BindRenderbuffer (GL_RENDERBUFFER, *depth);

  if (gst_gl_context_get_gl_api (frame->context) & (GST_GL_API_OPENGL |
          GST_GL_API_OPENGL3)) {
    gl->RenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width,
        height);
  }
  if (gst_gl_context_get_gl_api (frame->context) & GST_GL_API_GLES2) {
    gl->RenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,
        width, height);
  }

  /* setup a texture to render to */
  gl->GenTextures (1, &fake_texture);
  gl->BindTexture (GL_TEXTURE_2D, fake_texture);
  internal_format =
      gst_gl_sized_gl_format_from_gl_format_type (frame->context, GL_RGBA,
      GL_UNSIGNED_BYTE);
  gl->TexImage2D (GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA,
      GL_UNSIGNED_BYTE, NULL);
  gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  /* attach the texture to the FBO to renderer to */
  gl->FramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
      GL_TEXTURE_2D, fake_texture, 0);

  /* attach the depth render buffer to the FBO */
  gl->FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
      GL_RENDERBUFFER, *depth);

  if (gst_gl_context_get_gl_api (frame->context) & (GST_GL_API_OPENGL |
          GST_GL_API_OPENGL3)) {
    gl->FramebufferRenderbuffer (GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
        GL_RENDERBUFFER, *depth);
  }

  if (gl->CheckFramebufferStatus (GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    gst_gl_context_set_error (frame->context,
        "GL framebuffer status incomplete");

    gl->DeleteTextures (1, &fake_texture);

    return FALSE;
  }

  /* unbind the FBO */
  gl->BindFramebuffer (GL_FRAMEBUFFER, 0);

  gl->DeleteTextures (1, &fake_texture);

  return TRUE;
}

gboolean
gst_gl_framebuffer_use_v2 (GstGLFramebuffer * frame, gint texture_fbo_width,
    gint texture_fbo_height, GLuint fbo, GLuint depth_buffer,
    GLuint texture_fbo, GLCB_V2 cb, gpointer stuff)
{
  const GstGLFuncs *gl;
  GLint viewport_dim[4] = { 0 };

  g_return_val_if_fail (GST_IS_GL_FRAMEBUFFER (frame), FALSE);
  g_return_val_if_fail (texture_fbo_width > 0 && texture_fbo_height > 0, FALSE);
  g_return_val_if_fail (fbo != 0, FALSE);
  g_return_val_if_fail (texture_fbo != 0, FALSE);
  g_return_val_if_fail (cb != NULL, FALSE);

  gl = frame->context->gl_vtable;

  GST_TRACE ("Binding v2 FBO %u dimensions:%ux%u with texture:%u ",
      fbo, texture_fbo_width, texture_fbo_height, texture_fbo);

  gl->BindFramebuffer (GL_FRAMEBUFFER, fbo);

  /* setup a texture to render to */
  gl->BindTexture (GL_TEXTURE_2D, texture_fbo);

  /* attach the texture to the FBO to renderer to */
  gl->FramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
      GL_TEXTURE_2D, texture_fbo, 0);

  gl->GetIntegerv (GL_VIEWPORT, viewport_dim);

  gl->Viewport (0, 0, texture_fbo_width, texture_fbo_height);

  if (gst_gl_context_get_gl_api (frame->context) & (GST_GL_API_OPENGL |
          GST_GL_API_OPENGL3))
    gl->DrawBuffer (GL_COLOR_ATTACHMENT0);

  /* the opengl scene */
  cb (stuff);

  if (gst_gl_context_get_gl_api (frame->context) & (GST_GL_API_OPENGL |
          GST_GL_API_OPENGL3))
    gl->DrawBuffer (GL_NONE);

  gl->Viewport (viewport_dim[0], viewport_dim[1],
      viewport_dim[2], viewport_dim[3]);

  gl->BindFramebuffer (GL_FRAMEBUFFER, 0);

  return TRUE;
}

void
gst_gl_framebuffer_delete (GstGLFramebuffer * frame, guint fbo, guint depth)
{
  const GstGLFuncs *gl;

  g_return_if_fail (GST_IS_GL_FRAMEBUFFER (frame));

  gl = frame->context->gl_vtable;

  GST_TRACE ("Deleting FBO %u", fbo);

  if (fbo) {
    gl->DeleteFramebuffers (1, &fbo);
  }
  if (depth) {
    gl->DeleteRenderbuffers (1, &depth);
  }
}
