// SPDX-License-Identifier: GPL-2.0+ AND LGPL-2.0+ AND LGPL-2.1+

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

#include <gst/video/video-info.h>

#include "gstmvbbufferpool.h"
#include "gstmvballocator.h"

GST_DEBUG_CATEGORY_STATIC(gst_mvb_buffer_pool_debug);
#define GST_CAT_DEFAULT gst_mvb_buffer_pool_debug

#define gst_mvb_buffer_pool_parent_class parent_class

struct _GstMvbBufferPoolPrivate
{
  GstAllocator* allocator;
};
typedef struct _GstMvbBufferPoolPrivate GstMvbBufferPoolPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (GstMvbBufferPool, gst_mvb_buffer_pool, GST_TYPE_VIDEO_BUFFER_POOL);

static void gst_mvb_buffer_pool_finalize (GObject* object);

static gboolean
gst_mvb_buffer_pool_set_config (GstBufferPool* base_pool, GstStructure* config)
{
    GstMvbBufferPool* pool = GST_MVB_BUFFER_POOL_CAST(base_pool);
    GstVideoInfo info;
    GstCaps* caps;
    gint width, height;
    guint size;
    GstAllocator* allocator;
    GstAllocationParams params;
    gboolean ret;

    GST_DEBUG ("set config %p", config);

    if (!gst_buffer_pool_config_get_params(config, &caps, &size, NULL, NULL))
      goto wrong_config;

    if (caps == NULL)
      goto no_caps;

    /* now parse the caps from the config */
    if (!gst_video_info_from_caps(&info, caps)) {
        goto wrong_caps;
    }

    if (!gst_buffer_pool_config_get_allocator(config, &allocator, &params)) {
        goto wrong_config;
    }

    GST_DEBUG ("config get allocator %p", allocator);

    if (!GST_IS_MVB_ALLOCATOR(allocator)) {
        GST_DEBUG_OBJECT(pool, "Passed allocator type is not compatible: %" GST_PTR_FORMAT, allocator);

        GstMvbBufferPoolPrivate* priv = gst_mvb_buffer_pool_get_instance_private (pool);
        if (priv->allocator == NULL)
        {
            priv->allocator = allocator = (GstAllocator*) gst_mvb_allocator_new ();
            if (!GST_IS_MVB_ALLOCATOR(allocator)) {
                GST_ERROR_OBJECT(pool, "Unable to get mvb allocator instance %" GST_PTR_FORMAT, allocator);
                goto wrong_config;
            }

            GST_DEBUG_OBJECT (pool, "try to new mvb allocator : %p", priv->allocator);
        }
        gst_buffer_pool_config_set_allocator(config, priv->allocator, NULL);
    }
    if (pool->is_info_valid && !gst_video_info_is_equal(&info, &(pool->info))) {
        GST_DEBUG_OBJECT(pool, "different video info received in caps");
        pool->is_info_valid = FALSE;
    }

    ret = GST_BUFFER_POOL_CLASS(parent_class)->set_config(base_pool, config);

    if (ret) {
        pool->info = info;
        pool->is_info_valid = TRUE;
        pool->size = size;
    }

    GST_WARNING_OBJECT(pool, "returning %d", ret);
    return ret;
    /* ERRORS */
wrong_config : {
    GST_WARNING_OBJECT(pool, "invalid config");
    return FALSE;
}
no_caps : {
    GST_WARNING_OBJECT(pool, "no caps in config");
    return FALSE;
}
wrong_caps : {
    GST_WARNING_OBJECT(pool, "failed getting geometry from caps %" GST_PTR_FORMAT, caps);
    return FALSE;
}
wrong_allocator : {
    GST_WARNING_OBJECT(pool, "Allocator type is not compatible: %" GST_PTR_FORMAT, allocator);
    return FALSE;
}
}

static void
gst_mvb_buffer_pool_free (GstBufferPool* pool, GstBuffer* buffer)
{
    GST_LOG_OBJECT(pool, "Freed buffer: %p", buffer);
    return GST_BUFFER_POOL_CLASS(parent_class)->free_buffer(pool, buffer);
}

static GstFlowReturn
gst_mvb_buffer_pool_alloc (
        GstBufferPool* base_pool,
        GstBuffer** buffer,
        GstBufferPoolAcquireParams* params)
{
    GstMvbBufferPool* pool = GST_MVB_BUFFER_POOL_CAST(base_pool);
    GstStructure* config = gst_buffer_pool_get_config(base_pool);
    GstAllocator* allocator;
    GstFlowReturn ret = GST_FLOW_ERROR;
    GstVideoInfo* info = &(pool->info);
    guint32 usage = 0;

    GST_DEBUG ("begin");

    if (!pool->is_info_valid) {
        GST_ERROR_OBJECT(pool, "Vid Info is not valid");
        goto out;
    }

    if (!gst_buffer_pool_config_get_allocator(config, &allocator, NULL) || !GST_IS_MVB_ALLOCATOR(allocator)) {
        GST_ERROR_OBJECT(pool, "Unable to get allocator");
        goto out;
    }

    *buffer = gst_buffer_new();
    if (*buffer == NULL) {
        GST_ERROR_OBJECT(pool, "Unable to get buffer");
        goto out;
    }

    usage = gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_MVB_PROTECTED) ? MVB_HEAP_FLAG_SECURE : 0;

    GST_LOG_OBJECT(pool, "allocating buffer (usage: %x) of size: %" G_GSIZE_FORMAT, usage, pool->size);

    GstMvbMemoryGroup* grp = create_gst_mvb_memory_group (allocator, info, usage);
    if (grp == NULL) {
        GST_ERROR_OBJECT(pool, "Unable to get mvb memory group");
        goto out;
    }

    for (gint i = 0; i < grp->mem_num; i++) {
        gst_buffer_append_memory(*buffer, grp->mem[i]);
    }

    gst_buffer_add_video_meta_full(
            *buffer,
            GST_VIDEO_FRAME_FLAG_NONE,
            GST_VIDEO_INFO_FORMAT(info),
            GST_VIDEO_INFO_WIDTH(info),
            GST_VIDEO_INFO_HEIGHT(info),
            GST_VIDEO_INFO_N_PLANES(info),
            info->offset,
            info->stride);

    gst_structure_free (config);

    GST_LOG_OBJECT(pool, "Allocated buffer: 0x%p with Grp: 0x%p", *buffer, grp);
    ret = GST_FLOW_OK;
    GST_DEBUG ("end");

out:
    return ret;
}

static GstFlowReturn
gst_mvb_buffer_pool_acquire_buffer (
        GstBufferPool* pool,
        GstBuffer** buffer,
        GstBufferPoolAcquireParams* params)
{
    GstFlowReturn ret;

    GST_LOG_OBJECT(pool, "begin acquire buffer");

    ret = GST_BUFFER_POOL_CLASS(parent_class)->acquire_buffer(pool, buffer, params);

    GST_LOG_OBJECT(pool, "Buffer %p acquired, status: %d ", *buffer, ret);
    return ret;
}

static void
gst_mvb_buffer_pool_release_buffer (
        GstBufferPool* pool,
        GstBuffer* buffer)
{
    GST_BUFFER_POOL_CLASS(parent_class)->release_buffer(pool, buffer);

    GST_LOG_OBJECT(pool, "Buffer %p released", buffer);
    return;
}

GstBufferPool*
gst_mvb_buffer_pool_new ()
{
    GstMvbBufferPool* pool;

    pool = g_object_new(GST_TYPE_MVB_BUFFER_POOL, NULL);
    gst_object_ref_sink(pool);

    GST_DEBUG_OBJECT(pool, "new video buffer pool %p", pool);
    return GST_BUFFER_POOL_CAST(pool);
}

static void
gst_mvb_buffer_pool_class_init (GstMvbBufferPoolClass* klass)
{
    GObjectClass*       gobject_class       = (GObjectClass*) klass;
    GstBufferPoolClass* gstbufferpool_class = (GstBufferPoolClass*) klass;

    gobject_class->finalize           = gst_mvb_buffer_pool_finalize;
    gstbufferpool_class->set_config   = gst_mvb_buffer_pool_set_config;
    gstbufferpool_class->alloc_buffer = gst_mvb_buffer_pool_alloc;

    gstbufferpool_class->acquire_buffer = gst_mvb_buffer_pool_acquire_buffer;
    gstbufferpool_class->release_buffer = gst_mvb_buffer_pool_release_buffer;

    GST_DEBUG_CATEGORY_INIT(gst_mvb_buffer_pool_debug, "mvbbufferpool", 0, "mvb buffer pool object");
}

static void
gst_mvb_buffer_pool_init (GstMvbBufferPool* pool)
{
    memset((void*) &(pool->info), 0, sizeof(GstVideoInfo));
    pool->is_info_valid = FALSE;
}

static void
gst_mvb_buffer_pool_finalize (GObject* object)
{
    GstMvbBufferPool* pool = GST_MVB_BUFFER_POOL_CAST(object);
    GstMvbBufferPoolPrivate* priv = gst_mvb_buffer_pool_get_instance_private (pool);

    GST_DEBUG_OBJECT(pool, "finalize mvb buffer pool %p", pool);
    if (gst_buffer_pool_is_active(pool)) {
        GST_WARNING_OBJECT(pool, "pool still active %p", pool);
        gst_buffer_pool_set_active(pool, FALSE);
    }
    G_OBJECT_CLASS(gst_mvb_buffer_pool_parent_class)->finalize(object);

    if (priv->allocator) {
      GST_DEBUG ("try to unref allocator: %p ref_count: %d",
          priv->allocator, GST_OBJECT_REFCOUNT_VALUE (priv->allocator));
      gst_object_unref (priv->allocator);
      priv->allocator = NULL;
    }

    GST_DEBUG ("finalized");
}
