/* GStreamer
 * Copyright (C) 2023 Igalia, S.L.
 *     Author: Víctor Jáquez <vjaquez@igalia.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 "gstvabase.h"

#include <gst/video/video.h>

#include <gst/va/gstvavideoformat.h>
#include <gst/va/vasurfaceimage.h>

#define GST_CAT_DEFAULT (importer->debug_category)

/* big bad mutex to exclusive access to shared stream buffers, such as
 * DMABuf after a tee */
static GRecMutex GST_VA_SHARED_LOCK = { 0, };

static gboolean
_try_import_dmabuf_unlocked (GstVaBufferImporter * importer, GstBuffer * inbuf)
{
  GstVideoMeta *meta;
  GstVideoInfo in_info = *importer->in_info;
  GstVideoInfoDmaDrm drm_info = *importer->in_drm_info;
  GstMemory *mems[GST_VIDEO_MAX_PLANES];
  guint i, n_planes, usage_hint;
  gsize offset[GST_VIDEO_MAX_PLANES];
  uintptr_t fd[GST_VIDEO_MAX_PLANES];
  gsize plane_size[GST_VIDEO_MAX_PLANES];
  GstVideoAlignment align = { 0, };

  /* This will eliminate most non-dmabuf out there */
  if (!gst_is_dmabuf_memory (gst_buffer_peek_memory (inbuf, 0)))
    return FALSE;

  n_planes = GST_VIDEO_INFO_N_PLANES (&in_info);

  meta = gst_buffer_get_video_meta (inbuf);

  /* Update video info importerd on video meta */
  if (meta) {
    GST_VIDEO_INFO_WIDTH (&in_info) = meta->width;
    GST_VIDEO_INFO_HEIGHT (&in_info) = meta->height;

    g_assert (n_planes == meta->n_planes);

    for (i = 0; i < n_planes; i++) {
      GST_VIDEO_INFO_PLANE_OFFSET (&in_info, i) = meta->offset[i];
      GST_VIDEO_INFO_PLANE_STRIDE (&in_info, i) = meta->stride[i];
    }
  }

  if (!gst_video_info_align_full (&in_info, &align, plane_size))
    return FALSE;

  /* Find and validate all memories */
  for (i = 0; i < n_planes; i++) {
    guint length;
    guint mem_idx;
    gsize mem_skip;

    if (!gst_buffer_find_memory (inbuf,
            GST_VIDEO_INFO_PLANE_OFFSET (&in_info, i), plane_size[i], &mem_idx,
            &length, &mem_skip))
      return FALSE;

    /* We can't have more then one dmabuf per plane */
    if (length != 1)
      return FALSE;

    mems[i] = gst_buffer_peek_memory (inbuf, mem_idx);

    /* And all memory found must be dmabuf */
    if (!gst_is_dmabuf_memory (mems[i]))
      return FALSE;

    offset[i] = mems[i]->offset + mem_skip;
    fd[i] = gst_dmabuf_memory_get_fd (mems[i]);
  }

  usage_hint = va_get_surface_usage_hint (importer->display,
      importer->entrypoint, GST_PAD_SINK, TRUE);

  /* Now create a VASurfaceID for the buffer */
  return gst_va_dmabuf_memories_setup (importer->display, &drm_info, mems, fd,
      offset, usage_hint);
}

static gboolean
_try_import_buffer (GstVaBufferImporter * importer, GstBuffer * inbuf)
{
  VASurfaceID surface;
  gboolean ret;

  surface = gst_va_buffer_get_surface (inbuf);
  if (surface != VA_INVALID_ID &&
      (gst_va_buffer_peek_display (inbuf) == importer->display))
    return TRUE;

  g_rec_mutex_lock (&GST_VA_SHARED_LOCK);
  ret = _try_import_dmabuf_unlocked (importer, inbuf);
  g_rec_mutex_unlock (&GST_VA_SHARED_LOCK);

  return ret;
}

GstFlowReturn
gst_va_buffer_importer_import (GstVaBufferImporter * importer,
    GstBuffer * inbuf, GstBuffer ** outbuf)
{
  GstBuffer *buffer = NULL;
  GstBufferPool *pool;
  GstFlowReturn ret;
  GstVideoFrame in_frame, out_frame;
  gboolean imported, copied;

  imported = _try_import_buffer (importer, inbuf);
  if (imported) {
    *outbuf = gst_buffer_ref (inbuf);
    return GST_FLOW_OK;
  }

  /* input buffer doesn't come from a vapool, thus it is required to
   * have a pool, grab from it a new buffer and copy the input
   * buffer to the new one */
  if (!(pool = importer->get_sinkpad_pool (importer->element,
              importer->pool_data)))
    return GST_FLOW_ERROR;

  ret = gst_buffer_pool_acquire_buffer (pool, &buffer, NULL);
  if (ret != GST_FLOW_OK)
    return ret;

  GST_LOG_OBJECT (importer->element, "copying input frame");

  if (!gst_video_frame_map (&in_frame, importer->in_info, inbuf, GST_MAP_READ))
    goto invalid_buffer;

  if (!gst_video_frame_map (&out_frame, importer->sinkpad_info, buffer,
          GST_MAP_WRITE)) {
    gst_video_frame_unmap (&in_frame);
    goto invalid_buffer;
  }

  copied = gst_video_frame_copy (&out_frame, &in_frame);

  gst_video_frame_unmap (&out_frame);
  gst_video_frame_unmap (&in_frame);

  if (!copied)
    goto invalid_buffer;

  if (!gst_buffer_copy_into (buffer, inbuf, GST_BUFFER_COPY_FLAGS |
          GST_BUFFER_COPY_TIMESTAMPS, 0, -1)) {
    GST_WARNING_OBJECT (importer->element,
        "Couldn't import buffer flags and timestamps");
  }

  *outbuf = buffer;

  return GST_FLOW_OK;

invalid_buffer:
  {
    GST_ELEMENT_WARNING (importer->element, STREAM, FORMAT, (NULL),
        ("invalid video buffer received"));
    if (buffer)
      gst_buffer_unref (buffer);
    return GST_FLOW_ERROR;
  }
}


gboolean
gst_va_base_convert_caps_to_va (GstCaps * caps)
{
  g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);

  /* For DMA buffer, we can only import linear buffers. Replace the drm-format
   * into format field. */
  if (gst_video_is_dma_drm_caps (caps)) {
    GstVideoInfoDmaDrm dma_info;
    GstVideoInfo info;

    if (!gst_video_info_dma_drm_from_caps (&dma_info, caps))
      return FALSE;

    if (dma_info.drm_modifier != DRM_FORMAT_MOD_LINEAR)
      return FALSE;

    if (!gst_va_dma_drm_info_to_video_info (&dma_info, &info))
      return FALSE;

    gst_caps_set_simple (caps, "format", G_TYPE_STRING,
        gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&info)), NULL);
    gst_structure_remove_field (gst_caps_get_structure (caps, 0), "drm-format");
  }

  gst_caps_set_features_simple (caps,
      gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_VA));

  return TRUE;
}
