// SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND LGPL-2.1-or-later

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

#include <unistd.h>

#include "gstmvballocator.h"

#define xstr(s) str(s)
#define str(s) #s

GST_DEBUG_CATEGORY (gst_log_mvb_allocator);
#define GST_CAT_DEFAULT gst_log_mvb_allocator

/* some utils tool */
static struct format_map_t
{
  enum mvb_format_e mvb_format;
  GstVideoFormat    gstformat;
  const gchar* title;
} format_map[] = {
#define DEF_FMT(mvbfmt, gstfmt, title) { MVB_FORMAT_##mvbfmt, GST_VIDEO_FORMAT_##gstfmt, title }

  DEF_FMT (YUYV,  YUY2, "YUYV"),
  DEF_FMT (NV12,  NV12, "NV12"),
  DEF_FMT (RGB,   RGB,  "RGB"),
  DEF_FMT (RGBA,  RGBA, "RGBA"),
  DEF_FMT (BGRA,  BGRA, "BGRA"),
  DEF_FMT (M21S,  M21S, "M21S"),
  DEF_FMT (MM21,  MM21, "MM21"),
  DEF_FMT (M2CS,  M2CS, "M2CS"),
  DEF_FMT (MTST,  MTST, "MTST"),
  DEF_FMT (MCST,  MCST, "MCST"),
  DEF_FMT (MTSR,  MTSR, "MTSR"),
  DEF_FMT (MCST,  MCST, "MCSR"),
  DEF_FMT (MFCS,  MFCS, "MFCS"),
  DEF_FMT (MFST,  MFST, "MFST"),
  DEF_FMT (MTSP,  MTSP, "MTSP"),
  DEF_FMT (ENCODED,  ENCODED, "ENCODED"),

#undef DEF_FMT
};


static const gchar*
_video_format_to_string (GstVideoFormat gstformat)
{
  gint i;

  for (i = 0; i < G_N_ELEMENTS (format_map); i++) {
    if (format_map[i].gstformat == gstformat)
      return format_map[i].title;
  }

  GST_ERROR ("unknown gst format %d in list %d", gstformat, sizeof (format_map)/sizeof(struct format_map_t));
  return "UNKNOWN";
}

static const gchar*
_mvb_format_to_string (enum mvb_format_e mvb_format)
{
  gint i;

  for (i = 0; i < G_N_ELEMENTS (format_map); i++) {
    if (format_map[i].mvb_format == mvb_format)
      return format_map[i].title;
  }

  GST_ERROR ("unknown mvb format %d in list %d", mvb_format, sizeof (format_map)/sizeof(struct format_map_t));
  return "UNKNOWN";
}

static GstVideoFormat
_video_format_from_mvb (enum mvb_format_e mvb_format)
{
  gint i;

  for (i = 0; i < G_N_ELEMENTS (format_map); i++) {
    if (format_map[i].mvb_format == mvb_format)
      return format_map[i].gstformat;
  }

  return GST_VIDEO_FORMAT_UNKNOWN;
}

static enum mvb_format_e
_mvb_format_from_video (GstVideoFormat gstformat)
{
  gint i;

  for (i = 0; i < G_N_ELEMENTS (format_map); i++) {
    if (format_map[i].gstformat == gstformat)
      return format_map[i].mvb_format;
  }

  GST_ERROR ("can not find matched format");
  return 0;
}

struct _GstMvbAllocatorPrivate
{
  struct mvb_heap_t* mvb_heap;
  GstAllocator*      dma_allocator;
};

#define parent_class gst_mvb_allocator_parent_class
G_DEFINE_TYPE_WITH_CODE (GstMvbAllocator, gst_mvb_allocator, GST_TYPE_DMABUF_ALLOCATOR,
    G_ADD_PRIVATE (GstMvbAllocator);
    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "mvballocator", 0, "Mvb Allocator");
);

/* mvb adapter */
// #define DEFAULT_MVB_HEAP_NAME "ion_legacy"

static struct mvb_heap_t*
createMvbHeap()
{
  struct mvb_heap_t* mvb_heap;
  GST_DEBUG ("begin");

  mvb_heap = mvb_heap_create (xstr(DEFAULT_MVB_HEAP_NAME));
  if (!mvb_heap) {
    GST_ERROR ("end, mvb heap creation failed");
    return NULL;
  }

  GST_DEBUG ("end, mvb heap created => %p", mvb_heap);

  return mvb_heap;
}

static void
destroyMvbHeap (struct mvb_heap_t* mvb_heap)
{
  GST_DEBUG ("begin, destroy mvb device %p", mvb_heap);

  mvb_heap_destroy (mvb_heap);

  GST_DEBUG ("end, mvb heap destroyed");
}

static struct mvb_frame_t*
createMvbBuffer (struct mvb_heap_t* mvb_heap, enum mvb_format_e mvb_format,
    uint32_t width, uint32_t height, uint32_t mvb_flag)
{
  struct mvb_frame_t* mvb_frame;

  GST_DEBUG ("begin, width: %d height: %d fmt: %s", width, height, _mvb_format_to_string(mvb_format));

  if (!mvb_heap) {
    GST_ERROR ("end, mvb_heap creation failed");
    return NULL;
  }

  mvb_frame = mvb_format_utils_alloc_frame (mvb_heap, width, height, mvb_format, mvb_flag);
  if (!mvb_frame) {
    GST_DEBUG("end, mvb_frame alloc failed");
    return NULL;
  }

  GST_DEBUG ("end, mvb buffer created %p", mvb_frame);

  return mvb_frame;
}

static void
destroyMvbBuffer(struct mvb_frame_t* mvb_frame)
{
  GST_DEBUG ("begin");

  mvb_format_utils_free_frame (mvb_frame);

  GST_DEBUG ("end, mvb frame destoryed");
}

static GstMvbMemory*
_gst_mvb_memory_new (GstMvbAllocator* allocator, gint width, gint height, GstVideoFormat gstformat, gint size,
    GstMvbMemoryGroup * mem_group, guint32 usage);
static void
_gst_mvb_memory_free (GstMvbMemory* mem);
static void
_gst_mvb_allocator_finalize (GObject * object);

static void
_destroy_gst_mvb_memory_group (GstMvbMemoryGroup* mem_group);

static void gst_mvb_memory_group_reference_dec (GstMvbMemory* gst_mvb_mem)
{
  gst_mvb_mem->mem_group->mem_allocated--;
  GST_DEBUG ("allocated gst mvb memory num: %d -> %d",
      gst_mvb_mem->mem_group->mem_allocated+1,
      gst_mvb_mem->mem_group->mem_allocated);

  if (!gst_mvb_mem->mem_group->mem_allocated)
  {
    GST_DEBUG ("unref gst mvb mem: %p", gst_mvb_mem);
    gst_memory_unref ((GstMemory*) gst_mvb_mem);
    GST_DEBUG ("unref gst mvb mem done");

    GST_DEBUG ("destroy mvb memory group begin: %p ", gst_mvb_mem->mem_group);
    _destroy_gst_mvb_memory_group (gst_mvb_mem->mem_group);
    GST_DEBUG ("destroy mvb memory group end");
  }
}

GstMvbMemoryGroup*
create_gst_mvb_memory_group (GstMvbAllocator* allocator, GstVideoInfo* info, guint32 usage)
{
  GstMvbMemoryGroup* mem_group = NULL;
  guint width              = GST_VIDEO_INFO_WIDTH (info);
  guint height             = GST_VIDEO_INFO_HEIGHT (info);
  GstVideoFormat gstformat = GST_VIDEO_INFO_FORMAT (info);
  gint total_size          = GST_VIDEO_INFO_SIZE (info);

  GST_DEBUG ("begin, param: WxH: %d %d, gstformat %s", width, height, _video_format_to_string(gstformat));

  mem_group = g_slice_new0(GstMvbMemoryGroup);

  if (gstformat == GST_VIDEO_FORMAT_ENCODED) {
    if (width * height < MVB_ENCODED_LIMIT_SIZE_HD)
        total_size = MVB_ENCODED_BUFFER_SIZE_2M;
    else
        total_size = MVB_ENCODED_BUFFER_SIZE_4M;
  }
  GST_DEBUG ("total size: %d", total_size);

  /* create gst mvb memory */
  mem_group->mvb_mem =  _gst_mvb_memory_new (allocator, width, height, gstformat, total_size, mem_group, usage);
  mem_group->mem_num = mem_group->mvb_mem->plane_num;

  for (int i = 0; i < mem_group->mem_num; i++)
  {
    mem_group->mem[i] = gst_fd_allocator_alloc (allocator->priv->dma_allocator,
                                                mem_group->mvb_mem->plane_fd[i],
                                                mem_group->mvb_mem->plane_size[i],
                                                GST_FD_MEMORY_FLAG_DONT_CLOSE);
    mem_group->mem_allocated++;

    gst_mini_object_set_qdata (GST_MINI_OBJECT (mem_group->mem[i]),
                                  GST_MVB_MEMORY_QUARK,
                                  mem_group->mvb_mem,
                                  (GDestroyNotify) gst_mvb_memory_group_reference_dec);

    GST_DEBUG ("plane: %d plane_size: %d plane_fd: %d",
        i, mem_group->mvb_mem->plane_size[i], mem_group->mvb_mem->plane_fd[i]);
  }

  GST_DEBUG ("end, create gst mvb memory group => %p", mem_group);
  return mem_group;
}

static void
_destroy_gst_mvb_memory_group (GstMvbMemoryGroup* mem_group)
{
  GST_DEBUG ("begin, destroy gst mvb memory group => %p", mem_group);

  if (!mem_group)
  {
    GST_ERROR ("invalid pointer for mem_group");
    return;
  }

  g_slice_free (GstMvbMemoryGroup, mem_group);

  GST_DEBUG ("end");
}

static GstMvbMemory*
_gst_mvb_memory_new (GstMvbAllocator* allocator, gint width, gint height, GstVideoFormat gstformat, gint size,
    GstMvbMemoryGroup * mem_group, guint32 mvb_flag)
{
  GstMvbMemory* gst_mvb_mem = NULL;
  struct mvb_frame_t* mvb_frame = NULL;

  GST_DEBUG ("begin, width: %d height: %d size: %d mvbformat: %s",
      width, height, size, _video_format_to_string (gstformat));

  mvb_frame = createMvbBuffer (allocator->priv->mvb_heap,
                        _mvb_format_from_video (gstformat),
                        width, height, mvb_flag);
  if (!mvb_frame) {
    GST_ERROR ("end, failed to create mvb buffer");
    return NULL;
  }

  gst_mvb_mem = g_slice_new0 (GstMvbMemory);
  if (!gst_mvb_mem) {
    destroyMvbBuffer (mvb_frame);
    GST_ERROR ("end, failed to new mem");
    return NULL;
  }

  gst_memory_init (GST_MEMORY_CAST (gst_mvb_mem), 0, (GstAllocator*) allocator, NULL, size, 0, 0, size);

  gst_mvb_mem->mem_group   = mem_group;
  gst_mvb_mem->mvb_frame   = mvb_frame;
  gst_mvb_mem->plane_num   = mvb_format_utils_frame_get_plane_num (mvb_frame);

  for (gint i = 0; i < gst_mvb_mem->plane_num; i++)
  {
    gst_mvb_mem->plane_fd[i]   = mvb_format_utils_frame_get_plane_fd (gst_mvb_mem->mvb_frame, i);
    gst_mvb_mem->plane_size[i] = mvb_format_utils_frame_get_plane_size (gst_mvb_mem->mvb_frame, i);
    gst_mvb_mem->plane_data[i] = NULL;
  }

  GST_DEBUG ("end, create gst mvb mem => %p", gst_mvb_mem);

  return gst_mvb_mem;
}

static void
_gst_mvb_memory_free (GstMvbMemory* gst_mvb_mem)
{
  GST_DEBUG ("begin, free gst mvb mem => %p", gst_mvb_mem);

  destroyMvbBuffer (gst_mvb_mem->mvb_frame);

  g_slice_free (GstMvbMemory, gst_mvb_mem);

  GST_DEBUG ("end");
}

static GstMemory *
_gst_mvb_allocator_alloc (GstAllocator * allocator, gsize size, GstAllocationParams * params)
{
  GST_WARNING_OBJECT (allocator, "should not call this function, size %d params %p", size, params);

  return NULL;
}

static void
_gst_mvb_allocator_free (GstAllocator * allocator, GstMemory * gst_mem)
{
  GstMvbAllocator * alloc = GST_MVB_ALLOCATOR (allocator);

  _gst_mvb_memory_free ((GstMvbMemory*) gst_mem);
}

static void
gst_mvb_allocator_class_init (GstMvbAllocatorClass * klass)
{
  GObjectClass *gobject_class;
  GstAllocatorClass *allocator_class;

  gobject_class = (GObjectClass *) klass;
  gobject_class->finalize = _gst_mvb_allocator_finalize;

  allocator_class = (GstAllocatorClass *) klass;
  allocator_class->alloc = _gst_mvb_allocator_alloc;
  allocator_class->free =  _gst_mvb_allocator_free;


  GST_DEBUG ("mvb allocator class init");
}

static gpointer
_gst_mvb_memory_map (GstMemory * mem, gsize maxsize, GstMapFlags flags)
{
  GST_DEBUG ("no need");
  return NULL;
}

static gboolean
_gst_mvb_memory_unmap (GstMemory * mem)
{
  GST_DEBUG ("no need");

  return TRUE;
}
static gpointer
_gst_mvb_memory_map_full (GstMemory * gmem, GstMapInfo * info, gsize maxsize)
{
  GST_DEBUG ("no need");

  return NULL;
}

static void
_gst_mvb_memory_unmap_full (GstMemory * gmem, GstMapInfo * info)
{
  GST_DEBUG ("no need");

  return;
}

static void
gst_mvb_allocator_init (GstMvbAllocator * allocator)
{
  GstAllocator *alloc;

  alloc = GST_ALLOCATOR_CAST (allocator);

  GST_DEBUG ("begin");

  allocator->priv = gst_mvb_allocator_get_instance_private (allocator);

  allocator->priv->mvb_heap = createMvbHeap ();
  if (NULL == allocator->priv->mvb_heap)
  {
    GST_ERROR ("failed to create mvb heap");
  }

  allocator->priv->dma_allocator = gst_dmabuf_allocator_new ();
  if (NULL == allocator->priv->dma_allocator)
  {
    GST_ERROR ("failed to create dma allocator");
  }

  alloc->mem_type  = GST_MVB_ALLOCATOR_NAME;
  alloc->mem_map   = (GstMemoryMapFunction) _gst_mvb_memory_map;
  alloc->mem_unmap = (GstMemoryUnmapFunction) _gst_mvb_memory_unmap;
  alloc->mem_share = (GstMemoryShareFunction) NULL;
  alloc->mem_copy = (GstMemoryShareFunction) NULL;
  alloc->mem_map_full = (GstMemoryMapFullFunction) _gst_mvb_memory_map_full;
  alloc->mem_unmap_full = (GstMemoryUnmapFullFunction) _gst_mvb_memory_unmap_full;

  GST_OBJECT_FLAG_SET (allocator, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC);

  GST_DEBUG ("end");
}

static void
_gst_mvb_allocator_finalize (GObject * object)
{
  GstMvbAllocator *alloc = GST_MVB_ALLOCATOR (object);

  GST_DEBUG ("begin");

  if (NULL != alloc->priv->mvb_heap)
  {
    destroyMvbHeap (alloc->priv->mvb_heap);
    alloc->priv->mvb_heap = NULL;
  }
  if (NULL != alloc->priv->dma_allocator)
  {
    gst_object_unref (alloc->priv->dma_allocator);
    alloc->priv->dma_allocator = NULL;
  }

  GST_DEBUG_OBJECT (object, "finalize");

  G_OBJECT_CLASS (parent_class)->finalize (object);

  GST_DEBUG ("end");
}

GstAllocator *
gst_mvb_allocator_new (void)
{
  GstAllocator *alloc;

  GST_DEBUG_CATEGORY_INIT (gst_log_mvb_allocator, "mvballocator", 0, "gst mvb allocator");

  GST_DEBUG ("begin");

  alloc = g_object_new (GST_TYPE_MVB_ALLOCATOR, "name", "MvbMemory::allocator", NULL);

  gst_object_ref_sink (alloc);

  GST_DEBUG ("end");

  return alloc;
}

GQuark
gst_mvb_memory_quark (void)
{
  static GQuark quark = 0;

  if (quark == 0)
    quark = g_quark_from_string ("GstMvbMemory");

  return quark;
}
