/*
 * GStreamer
 * Copyright (C) 2015 Matthew Waters <matthew@centricular.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 <string.h>

#include <gst/video/video.h>

#include <gst/gl/gstglbasememory.h>
#include <gst/gl/gstglrenderbuffer.h>

/**
 * SECTION:gstglrenderbuffer
 * @short_description: memory subclass for GL renderbuffer objects
 * @see_also: #GstMemory, #GstAllocator
 *
 * GstGLRenderbuffer is a #GstGLBaseMemory subclass providing support for
 * OpenGL renderbuffers.  
 *
 * #GstGLRenderbuffer is created or wrapped through gst_gl_base_memory_alloc()
 * with #GstGLRenderbufferAllocationParams.
 */

#define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
#define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
#define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
#define USING_GLES2(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 2, 0))
#define USING_GLES3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 3, 0))

static GstAllocator *_gl_renderbuffer_allocator;

GST_DEBUG_CATEGORY_STATIC (GST_CAT_GL_RENDERBUFFER);
#define GST_CAT_DEFAULT GST_CAT_GL_RENDERBUFFER

G_DEFINE_TYPE (GstGLRenderbufferAllocator, gst_gl_renderbuffer_allocator,
    GST_TYPE_GL_BASE_MEMORY_ALLOCATOR);

static guint
_new_renderbuffer (GstGLContext * context, guint format, guint width,
    guint height)
{
  const GstGLFuncs *gl = context->gl_vtable;
  guint rbo_id;

  gl->GenRenderbuffers (1, &rbo_id);
  gl->BindRenderbuffer (GL_RENDERBUFFER, rbo_id);

  gl->RenderbufferStorage (GL_RENDERBUFFER, format, width, height);

  gl->BindRenderbuffer (GL_RENDERBUFFER, 0);

  return rbo_id;
}

static gboolean
_gl_rbo_create (GstGLRenderbuffer * gl_mem, GError ** error)
{
  if (!gl_mem->renderbuffer_wrapped) {
    GstGLContext *context = gl_mem->mem.context;
    GLenum internal_format;
    GLenum tex_format;
    GLenum renderbuffer_type;

    tex_format = gst_gl_format_from_gl_texture_type (gl_mem->renderbuffer_type);
    renderbuffer_type = GL_UNSIGNED_BYTE;
    if (gl_mem->renderbuffer_type == GST_VIDEO_GL_TEXTURE_TYPE_RGB16)
      renderbuffer_type = GL_UNSIGNED_SHORT_5_6_5;

    internal_format =
        gst_gl_sized_gl_format_from_gl_format_type (context, tex_format,
        renderbuffer_type);

    gl_mem->renderbuffer_id =
        _new_renderbuffer (context, internal_format,
        gst_gl_renderbuffer_get_width (gl_mem),
        gst_gl_renderbuffer_get_height (gl_mem));

    GST_CAT_TRACE (GST_CAT_GL_RENDERBUFFER, "Generating renderbuffer id:%u "
        "format:%u dimensions:%ux%u", gl_mem->renderbuffer_id, internal_format,
        gst_gl_renderbuffer_get_width (gl_mem),
        gst_gl_renderbuffer_get_height (gl_mem));
  }

  return TRUE;
}

static void
gst_gl_renderbuffer_init (GstGLRenderbuffer * mem, GstAllocator * allocator,
    GstMemory * parent, GstGLContext * context,
    GstVideoGLTextureType renderbuffer_type, GstAllocationParams * params,
    guint width, guint height, gpointer user_data, GDestroyNotify notify)
{
  gsize size = gst_gl_texture_type_n_bytes (renderbuffer_type) * width * height;

  mem->renderbuffer_type = renderbuffer_type;
  mem->width = width;
  mem->height = height;

  gst_gl_base_memory_init ((GstGLBaseMemory *) mem, allocator, parent, context,
      params, size, user_data, notify);

  GST_CAT_DEBUG (GST_CAT_GL_RENDERBUFFER, "new GL renderbuffer context:%"
      GST_PTR_FORMAT " memory:%p format:%u dimensions:%ux%u ", context, mem,
      mem->renderbuffer_type, gst_gl_renderbuffer_get_width (mem),
      gst_gl_renderbuffer_get_height (mem));
}

static gpointer
_gl_rbo_map (GstGLRenderbuffer * gl_mem, GstMapInfo * info, gsize maxsize)
{
  GST_CAT_WARNING (GST_CAT_GL_RENDERBUFFER, "Renderbuffer's cannot be mapped");

  return NULL;
}

static void
_gl_rbo_unmap (GstGLRenderbuffer * gl_mem, GstMapInfo * info)
{
}

static GstMemory *
_gl_rbo_copy (GstGLRenderbuffer * src, gssize offset, gssize size)
{
  GST_CAT_WARNING (GST_CAT_GL_RENDERBUFFER, "Renderbuffer's cannot be copied");

  return NULL;
}

static GstMemory *
_gl_rbo_alloc (GstAllocator * allocator, gsize size,
    GstAllocationParams * params)
{
  g_warning ("Use gst_gl_base_memory_alloc to allocate from this allocator");

  return NULL;
}

static void
_gl_rbo_destroy (GstGLRenderbuffer * gl_mem)
{
  const GstGLFuncs *gl = gl_mem->mem.context->gl_vtable;

  if (gl_mem->renderbuffer_id && !gl_mem->renderbuffer_wrapped)
    gl->DeleteRenderbuffers (1, &gl_mem->renderbuffer_id);
}

static GstGLRenderbuffer *
_default_gl_rbo_alloc (GstGLRenderbufferAllocator * allocator,
    GstGLRenderbufferAllocationParams * params)
{
  guint alloc_flags = params->parent.alloc_flags;
  GstGLRenderbuffer *mem;

  g_return_val_if_fail ((alloc_flags &
          GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_WRAP_SYSMEM) == 0, NULL);

  mem = g_new0 (GstGLRenderbuffer, 1);

  if (alloc_flags & GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_WRAP_GPU_HANDLE) {
    mem->renderbuffer_id = GPOINTER_TO_UINT (params->parent.gl_handle);
    mem->renderbuffer_wrapped = TRUE;
  }

  gst_gl_renderbuffer_init (mem, GST_ALLOCATOR_CAST (allocator), NULL,
      params->parent.context, params->renderbuffer_type,
      params->parent.alloc_params, params->width, params->height,
      params->parent.user_data, params->parent.notify);

  return mem;
}

static void
gst_gl_renderbuffer_allocator_class_init (GstGLRenderbufferAllocatorClass *
    klass)
{
  GstGLBaseMemoryAllocatorClass *gl_base;
  GstAllocatorClass *allocator_class;

  gl_base = (GstGLBaseMemoryAllocatorClass *) klass;
  allocator_class = (GstAllocatorClass *) klass;

  gl_base->alloc =
      (GstGLBaseMemoryAllocatorAllocFunction) _default_gl_rbo_alloc;
  gl_base->create = (GstGLBaseMemoryAllocatorCreateFunction) _gl_rbo_create;
  gl_base->destroy = (GstGLBaseMemoryAllocatorDestroyFunction) _gl_rbo_destroy;

  allocator_class->alloc = _gl_rbo_alloc;
}

static void
gst_gl_renderbuffer_allocator_init (GstGLRenderbufferAllocator * allocator)
{
  GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);

  alloc->mem_type = GST_GL_RENDERBUFFER_ALLOCATOR_NAME;

  alloc->mem_map_full = (GstMemoryMapFullFunction) _gl_rbo_map;
  alloc->mem_unmap_full = (GstMemoryUnmapFullFunction) _gl_rbo_unmap;
  alloc->mem_copy = (GstMemoryCopyFunction) _gl_rbo_copy;

  GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}

/**
 * gst_gl_renderbuffer_get_width:
 * @gl_mem: a #GstGLRenderbuffer
 *
 * Returns: the configured width of @gl_mem
 *
 * Since: 1.8
 */
gint
gst_gl_renderbuffer_get_width (GstGLRenderbuffer * gl_mem)
{
  g_return_val_if_fail (gst_is_gl_renderbuffer ((GstMemory *) gl_mem), 0);

  return gl_mem->width;
}

/**
 * gst_gl_renderbuffer_get_height:
 * @gl_mem: a #GstGLRenderbuffer
 *
 * Returns: the configured height of @gl_mem
 *
 * Since: 1.8
 */
gint
gst_gl_renderbuffer_get_height (GstGLRenderbuffer * gl_mem)
{
  g_return_val_if_fail (gst_is_gl_renderbuffer ((GstMemory *) gl_mem), 0);

  return gl_mem->height;
}

/**
 * gst_gl_renderbuffer_get_type:
 * @gl_mem: a #GstGLRenderbuffer
 *
 * Returns: the #GstVideoGLTextureType of @gl_mem
 *
 * Since: 1.8
 */
GstVideoGLTextureType
gst_gl_renderbuffer_get_type (GstGLRenderbuffer * gl_mem)
{
  g_return_val_if_fail (gst_is_gl_renderbuffer ((GstMemory *) gl_mem), 0);

  return gl_mem->renderbuffer_type;
}

/**
 * gst_gl_renderbuffer_get_id:
 * @gl_mem: a #GstGLRenderbuffer
 *
 * Returns: the OpenGL renderbuffer handle of @gl_mem
 *
 * Since: 1.8
 */
guint
gst_gl_renderbuffer_get_id (GstGLRenderbuffer * gl_mem)
{
  g_return_val_if_fail (gst_is_gl_renderbuffer ((GstMemory *) gl_mem), 0);

  return gl_mem->renderbuffer_id;
}

/**
 * gst_gl_renderbuffer_init_once:
 *
 * Initializes the GL Base Texture allocator. It is safe to call this function
 * multiple times.  This must be called before any other GstGLRenderbuffer operation.
 *
 * Since: 1.4
 */
void
gst_gl_renderbuffer_init_once (void)
{
  static volatile gsize _init = 0;

  if (g_once_init_enter (&_init)) {
    gst_gl_base_memory_init_once ();

    GST_DEBUG_CATEGORY_INIT (GST_CAT_GL_RENDERBUFFER, "glrenderbuffermemory", 0,
        "OpenGL Renderbuffer memory");

    _gl_renderbuffer_allocator =
        g_object_new (GST_TYPE_GL_RENDERBUFFER_ALLOCATOR, NULL);
    GST_OBJECT_FLAG_SET (_gl_renderbuffer_allocator,
        GST_OBJECT_FLAG_MAY_BE_LEAKED);

    gst_allocator_register (GST_GL_RENDERBUFFER_ALLOCATOR_NAME,
        _gl_renderbuffer_allocator);

    g_once_init_leave (&_init, 1);
  }
}

/**
 * gst_is_gl_renderbuffer:
 * @mem:a #GstMemory
 * 
 * Returns: whether the memory at @mem is a #GstGLRenderbuffer
 *
 * Since: 1.10
 */
gboolean
gst_is_gl_renderbuffer (GstMemory * mem)
{
  return mem != NULL && mem->allocator != NULL
      && g_type_is_a (G_OBJECT_TYPE (mem->allocator),
      GST_TYPE_GL_RENDERBUFFER_ALLOCATOR);
}

static void
_gst_gl_rb_alloc_params_free_data (GstGLRenderbufferAllocationParams * params)
{
  gst_gl_allocation_params_free_data (&params->parent);
}

static void
_gst_gl_rb_alloc_params_copy_data (GstGLRenderbufferAllocationParams * src_vid,
    GstGLRenderbufferAllocationParams * dest_vid)
{
  GstGLAllocationParams *src = (GstGLAllocationParams *) src_vid;
  GstGLAllocationParams *dest = (GstGLAllocationParams *) dest_vid;

  gst_gl_allocation_params_copy_data (src, dest);

  dest_vid->renderbuffer_type = src_vid->renderbuffer_type;
  dest_vid->width = src_vid->width;
  dest_vid->height = src_vid->height;
}

static gboolean
    _gst_gl_renderbuffer_allocation_params_init_full
    (GstGLRenderbufferAllocationParams * params, gsize struct_size,
    guint alloc_flags, GstGLAllocationParamsCopyFunc copy,
    GstGLAllocationParamsFreeFunc free, GstGLContext * context,
    GstAllocationParams * alloc_params, guint width, guint height,
    GstVideoGLTextureType renderbuffer_type, gpointer wrapped_data,
    gpointer gl_handle, gpointer user_data, GDestroyNotify notify)
{
  g_return_val_if_fail (params != NULL, FALSE);
  g_return_val_if_fail (copy != NULL, FALSE);
  g_return_val_if_fail (free != NULL, FALSE);
  g_return_val_if_fail (GST_IS_GL_CONTEXT (context), FALSE);

  memset (params, 0, sizeof (*params));

  if (!gst_gl_allocation_params_init ((GstGLAllocationParams *) params,
          struct_size, alloc_flags, copy, free, context, 0, alloc_params,
          wrapped_data, gl_handle, user_data, notify))
    return FALSE;

  params->renderbuffer_type = renderbuffer_type;
  params->width = width;
  params->height = height;

  return TRUE;
}

/**
 * gst_gl_renderbuffer_allocation_params_new:
 * @context: a #GstGLContext
 * @alloc_params: (allow-none): the #GstAllocationParams for sysmem mappings of the texture
 * @width: the width of the renderbuffer
 * @height: the height of the renderbuffer
 * @renderbuffer_type: the #GstVideoGLTextureType for the created textures
 *
 * Returns: a new #GstGLRenderbufferAllocationParams for allocating #GstGLRenderbuffer's
 *
 * Since: 1.10
 */
GstGLRenderbufferAllocationParams *
gst_gl_renderbuffer_allocation_params_new (GstGLContext * context,
    GstAllocationParams * alloc_params, GstVideoGLTextureType renderbuffer_type,
    guint width, guint height)
{
  GstGLRenderbufferAllocationParams *params =
      g_new0 (GstGLRenderbufferAllocationParams, 1);

  if (!_gst_gl_renderbuffer_allocation_params_init_full (params,
          sizeof (GstGLRenderbufferAllocationParams),
          GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_ALLOC |
          GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_VIDEO,
          (GstGLAllocationParamsCopyFunc) _gst_gl_rb_alloc_params_copy_data,
          (GstGLAllocationParamsFreeFunc) _gst_gl_rb_alloc_params_free_data,
          context, alloc_params, width, height, renderbuffer_type, NULL, 0,
          NULL, NULL)) {
    g_free (params);
    return NULL;
  }

  return params;
}

/**
 * gst_gl_renderbuffer_allocation_params_new_wrapped:
 * @context: a #GstGLContext
 * @alloc_params: (allow-none): the #GstAllocationParams for @tex_id
 * @width: the width of the renderbuffer
 * @height: the height of the renderbuffer
 * @renderbuffer_type: the #GstVideoGLTextureType for @tex_id
 * @gl_handle: the GL handle to wrap
 * @user_data: (allow-none): user data to call @notify with
 * @notify: (allow-none): a #GDestroyNotify
 *
 * Returns: a new #GstGLRenderbufferAllocationParams for wrapping @gl_handle as a
 *          renderbuffer
 *
 * Since: 1.10
 */
GstGLRenderbufferAllocationParams *
gst_gl_renderbuffer_allocation_params_new_wrapped (GstGLContext * context,
    GstAllocationParams * alloc_params, GstVideoGLTextureType renderbuffer_type,
    guint width, guint height, gpointer gl_handle, gpointer user_data,
    GDestroyNotify notify)
{
  GstGLRenderbufferAllocationParams *params =
      g_new0 (GstGLRenderbufferAllocationParams, 1);

  if (!_gst_gl_renderbuffer_allocation_params_init_full (params,
          sizeof (GstGLRenderbufferAllocationParams),
          GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_WRAP_GPU_HANDLE |
          GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_VIDEO,
          (GstGLAllocationParamsCopyFunc) _gst_gl_rb_alloc_params_copy_data,
          (GstGLAllocationParamsFreeFunc) _gst_gl_rb_alloc_params_free_data,
          context, alloc_params, width, height, renderbuffer_type, NULL,
          gl_handle, user_data, notify)) {
    g_free (params);
    return NULL;
  }

  return params;
}
