/*
 * GStreamer
 * Copyright (c) 2016, Freescale Semiconductor, Inc. 
 *
 * 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 Foundatdma; either
 * versdma 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 Foundatdma, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstglfuncs.h"

#include <string.h>

#include <gst/allocators/gstdmabuf.h>
#include <gst/gl/gstglmemorydma.h>

#if GST_GL_HAVE_IONDMA
#include <gst/allocators/gstionmemory.h>
#endif

GST_DEBUG_CATEGORY_STATIC (GST_CAT_GL_DMA_MEMORY);
#define GST_CAT_DEFAULT GST_CAT_GL_DMA_MEMORY

#define parent_class gst_gl_memory_dma_allocator_parent_class
G_DEFINE_TYPE (GstGLMemoryDMAAllocator, gst_gl_memory_dma_allocator,
    GST_TYPE_GL_MEMORY_ALLOCATOR);

static void
gst_gl_memory_dma_init_instance (void)
{
  GstAllocator *ion_allocator = NULL;
  GstGLMemoryDMAAllocator *_gl_allocator;

  GST_DEBUG_CATEGORY_INIT (GST_CAT_GL_DMA_MEMORY, "glmemorydma", 0, "OpenGL dma memory");

#if GST_GL_HAVE_IONDMA
  ion_allocator = gst_ion_allocator_obtain();
#endif

  if (!ion_allocator)
    return;

  gst_gl_memory_init_once ();

  _gl_allocator = (GstGLMemoryDMAAllocator *) g_object_new (GST_TYPE_GL_MEMORY_DMA_ALLOCATOR, NULL);
  _gl_allocator->ion_allocator = ion_allocator;

  gst_allocator_register (GST_GL_MEMORY_DMA_ALLOCATOR_NAME,
      gst_object_ref (_gl_allocator));
}

GstAllocator *
gst_gl_memory_dma_allocator_obtain (void)
{

  static GOnce once = G_ONCE_INIT;
  GstAllocator *allocator;

  g_once (&once, (GThreadFunc) gst_gl_memory_dma_init_instance, NULL);

  allocator = gst_allocator_find (GST_GL_MEMORY_DMA_ALLOCATOR_NAME);
  if (allocator == NULL)
    GST_WARNING ("No allocator named %s found", GST_GL_MEMORY_DMA_ALLOCATOR_NAME);

  return allocator;
}

static void
gst_gl_memory_dma_allocator_dispose (GObject * object)
{
  GstGLMemoryDMAAllocator *gl_dma_alloc= GST_GL_MEMORY_DMA_ALLOCATOR (object);

  if (gl_dma_alloc->ion_allocator) {
    GST_DEBUG ("free ion allocator");
    gst_object_unref (gl_dma_alloc->ion_allocator);
    gl_dma_alloc->ion_allocator = NULL;
  }

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static gboolean
_gl_mem_create (GstGLMemoryDMA * gl_mem, GError ** error)
{
  GstGLContext *context = gl_mem->mem.mem.context;
  GstGLBaseMemoryAllocatorClass *alloc_class;
  guint dma_fd;

  alloc_class = GST_GL_BASE_MEMORY_ALLOCATOR_CLASS (parent_class);
  if (!alloc_class->create ((GstGLBaseMemory *) gl_mem, error))
    return FALSE;

  dma_fd = gst_dmabuf_memory_get_fd ((GstMemory*) gl_mem->dma);

  gl_mem->eglimage = 
    gst_egl_image_from_dmabuf (context, dma_fd, &gl_mem->mem.info, 0,0);

  if (!gl_mem->eglimage) {
    GST_CAT_ERROR (GST_CAT_GL_DMA_MEMORY, "Can't allocate eglimage memory");
    return FALSE;
  }

  const GstGLFuncs *gl = context->gl_vtable;

  gl->ActiveTexture (GL_TEXTURE0);
  gl->BindTexture (GL_TEXTURE_2D, gl_mem->mem.tex_id);
  gl->EGLImageTargetTexture2D (GL_TEXTURE_2D,
      gst_egl_image_get_image (gl_mem->eglimage));

  GST_CAT_DEBUG (GST_CAT_GL_DMA_MEMORY, "generated dma buffer %p fd %u texid %u",
      gl_mem, dma_fd, gl_mem->mem.tex_id);

  return TRUE;
}

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

  return NULL;
}

static void
_gl_mem_destroy (GstGLMemoryDMA * gl_mem)
{
  GST_CAT_DEBUG (GST_CAT_GL_DMA_MEMORY, "destroy gl dma buffer %p", gl_mem);

  if (gl_mem->eglimage)
    gst_egl_image_unref (gl_mem->eglimage);
  gl_mem->eglimage = NULL;
  if (gl_mem->dma)
    gst_memory_unref (GST_MEMORY_CAST (gl_mem->dma));
  gl_mem->dma = NULL;

  GST_GL_BASE_MEMORY_ALLOCATOR_CLASS (parent_class)->destroy ((GstGLBaseMemory
          *) gl_mem);
}

static GstGLMemoryDMA *
_gl_mem_dma_alloc (GstGLBaseMemoryAllocator * allocator,
    GstGLVideoAllocationParams * params)
{
  GstGLMemoryDMA *mem;
  guint alloc_flags;
  gsize size;
  GstGLMemoryDMAAllocator *gl_dma_alloc = GST_GL_MEMORY_DMA_ALLOCATOR (allocator);

  alloc_flags = params->parent.alloc_flags;

  g_return_val_if_fail (alloc_flags & GST_GL_ALLOCATION_PARAMS_ALLOC_FLAG_VIDEO,
      NULL);

  mem = g_new0 (GstGLMemoryDMA, 1);

  mem->params = params->parent.alloc_params;

  size = gst_gl_get_plane_data_size (params->v_info, params->valign, params->plane);
  mem->dma = gst_allocator_alloc (gl_dma_alloc->ion_allocator, size, mem->params);

  if (!mem->dma) {
    GST_CAT_ERROR (GST_CAT_GL_DMA_MEMORY, "Can't allocate dma memory size %d", size);
    g_free(mem);
    return NULL;
  }

  gst_gl_memory_init (GST_GL_MEMORY_CAST (mem), GST_ALLOCATOR_CAST (allocator),
      NULL, params->parent.context, params->target, params->tex_format,
      params->parent.alloc_params, params->v_info, params->plane,
      params->valign, params->parent.user_data, params->parent.notify);

  return mem;
}

static void
gst_gl_memory_dma_allocator_class_init (GstGLMemoryDMAAllocatorClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstGLBaseMemoryAllocatorClass *gl_base;
  GstAllocatorClass *allocator_class;

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

  gl_base->alloc = (GstGLBaseMemoryAllocatorAllocFunction) _gl_mem_dma_alloc;
  gl_base->create = (GstGLBaseMemoryAllocatorCreateFunction) _gl_mem_create;
  gl_base->destroy = (GstGLBaseMemoryAllocatorDestroyFunction) _gl_mem_destroy;
  gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_gl_memory_dma_allocator_dispose);

  allocator_class->alloc = _gl_mem_alloc;
}

static void
gst_gl_memory_dma_allocator_init (GstGLMemoryDMAAllocator * allocator)
{
  GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);

  alloc->mem_type = GST_GL_MEMORY_DMA_ALLOCATOR_NAME;

  GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);
}

gboolean
gst_is_gl_memory_dma (GstMemory * mem)
{
  return mem != NULL && mem->allocator != NULL
      && g_type_is_a (G_OBJECT_TYPE (mem->allocator),
      GST_TYPE_GL_MEMORY_DMA_ALLOCATOR);
}

static void
_finish_texture (GstGLContext * ctx, gpointer *data)
{
  GstGLFuncs *gl = ctx->gl_vtable;

  gl->Finish ();
}

GstBuffer *
gst_gl_memory_dma_buffer_to_gstbuffer (GstGLContext *ctx, GstVideoInfo * info,
    GstBuffer * glbuf)
{
  GstBuffer *buf;
  GstGLMemoryDMA *glmem;

  gst_gl_context_thread_add (ctx, (GstGLContextThreadFunc) _finish_texture, NULL);

  glmem = gst_buffer_peek_memory (glbuf, 0);

  buf = gst_buffer_new ();
  gst_buffer_append_memory (buf, (GstMemory *) glmem->dma);
  gst_memory_ref ((GstMemory *)glmem->dma);

  gst_buffer_add_video_meta_full (buf, 0,
      GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info),
      GST_VIDEO_INFO_HEIGHT (info), 1, info->offset, info->stride);
  GST_BUFFER_FLAGS (buf) = GST_BUFFER_FLAGS (glbuf);
  GST_BUFFER_PTS (buf) = GST_BUFFER_PTS (glbuf);
  GST_BUFFER_DTS (buf) = GST_BUFFER_DTS (glbuf);
  GST_BUFFER_DURATION (buf) = GST_BUFFER_DURATION (glbuf);

  return buf;
}
