/* GStreamer
 * Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
 * Copyright (C) 2023 Seungha Yang <seungha@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 "gstqsg6d3d11node.h"
#include <wrl.h>

GST_DEBUG_CATEGORY_EXTERN (gst_qt6_d3d11_debug);
#define GST_CAT_DEFAULT gst_qt6_d3d11_debug

/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */

GstQSG6D3D11Node::GstQSG6D3D11Node (QQuickItem * item, GstD3D11Device * device)
{
  window_ = item->window ();
  qt_device_ = (GstD3D11Device *) gst_object_ref (device);

  frame_.buffer = nullptr;

  gst_video_info_init (&render_info_);
  gst_video_info_init (&info_);

  setOwnsTexture (true);
  resize (8, 8);

  mapTexture ();
}

GstQSG6D3D11Node::~GstQSG6D3D11Node ()
{
  GST_DEBUG ("Destroying %p", this);

  unmapTexture ();
  gst_clear_buffer (&sharable_);
  gst_clear_buffer (&backbuffer_);
  gst_clear_buffer (&buffer_);
  gst_clear_caps (&caps_);

  if (pool_) {
    gst_buffer_pool_set_active (pool_, FALSE);
    gst_object_unref (pool_);
  }

  gst_clear_object (&conv_);
  gst_clear_object (&device_);
  gst_clear_object (&qt_device_);
}

QSGTexture *
GstQSG6D3D11Node::texture () const
{
  return QSGSimpleTextureNode::texture ();
}

void
GstQSG6D3D11Node::mapTexture ()
{
  if (backbuffer_) {
    gst_video_frame_map (&frame_, &render_info_, backbuffer_,
        (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
  }
}

void
GstQSG6D3D11Node::unmapTexture ()
{
  if (frame_.buffer)
    gst_video_frame_unmap (&frame_);
  frame_.buffer = nullptr;
}

void
GstQSG6D3D11Node::resize (guint width, guint height)
{
  GstStructure *config;
  GstD3D11AllocationParams *params;
  GstCaps *caps;
  GstD3D11Memory *dmem;
  ID3D11Texture2D *tex;

  if (pool_ && width == render_info_.width && height == render_info_.height)
    return;

  gst_clear_buffer (&sharable_);
  gst_clear_buffer (&backbuffer_);

  if (pool_) {
    gst_buffer_pool_set_active (pool_, FALSE);
    gst_clear_object (&pool_);
  }

  pool_ = gst_d3d11_buffer_pool_new (qt_device_);

  gst_video_info_set_format (&render_info_, GST_VIDEO_FORMAT_RGBA,
      width, height);
  render_info_.colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
  caps = gst_video_info_to_caps (&render_info_);

  params = gst_d3d11_allocation_params_new (qt_device_, &render_info_,
      GST_D3D11_ALLOCATION_FLAG_DEFAULT,
      D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
      D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX);

  config = gst_buffer_pool_get_config (pool_);
  gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
  gst_d3d11_allocation_params_free (params);

  gst_buffer_pool_config_set_params (config, caps, render_info_.size, 0, 0);
  gst_caps_unref (caps);
  gst_buffer_pool_set_config (pool_, config);
  gst_buffer_pool_set_active (pool_, TRUE);

  gst_buffer_pool_acquire_buffer (pool_, &backbuffer_, nullptr);

  dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0);
  tex = (ID3D11Texture2D *) gst_d3d11_memory_get_resource_handle (dmem);
  auto qt_texture = QNativeInterface::QSGD3D11Texture::fromNative (tex, window_,
      QSize (render_info_.width, render_info_.height),
      QQuickWindow::TextureHasAlphaChannel);
  setTexture (qt_texture);
}

/* only called from QT rendering thread */
void
GstQSG6D3D11Node::SetCaps (GstCaps * caps)
{
  if (caps_ && caps && gst_caps_is_equal (caps_, caps))
    return;

  gst_clear_object (&conv_);
  gst_clear_buffer (&sharable_);
  gst_clear_buffer (&buffer_);

  if (caps)
    gst_video_info_from_caps (&info_, caps);
  else
    gst_video_info_init (&info_);

  gst_caps_replace (&caps_, caps);
}

/* only called from QT rendering thread */
void
GstQSG6D3D11Node::SetBuffer (GstBuffer * buffer)
{
  gboolean buffer_changed;

  buffer_changed = gst_buffer_replace (&buffer_, buffer);

  unmapTexture ();

  if (buffer_ && buffer_changed) {
    GstD3D11Memory *dmem;

    resize (info_.width, info_.height);

    dmem = (GstD3D11Memory *) gst_buffer_peek_memory (buffer, 0);
    if (dmem->device != device_) {
      gst_clear_object (&conv_);
      gst_clear_buffer (&sharable_);
      gst_clear_object (&device_);
      device_ = (GstD3D11Device *) gst_object_ref (dmem->device);
    }

    gst_d3d11_device_lock (device_);
    if (!conv_)
      conv_ = gst_d3d11_converter_new (device_, &info_, &render_info_, nullptr);

    if (!sharable_) {
      GstMemory *mem_wrapped;
      ID3D11Resource *resource;
      ComPtr < IDXGIResource > dxgi_resource;
      ComPtr < ID3D11Texture2D > shared_texture;
      ID3D11Device *device_handle;
      HANDLE handle;

      device_handle = gst_d3d11_device_get_device_handle (dmem->device);
      dmem = (GstD3D11Memory *) gst_buffer_peek_memory (backbuffer_, 0);
      resource = gst_d3d11_memory_get_resource_handle (dmem);
      resource->QueryInterface (IID_PPV_ARGS (&dxgi_resource));
      dxgi_resource->GetSharedHandle (&handle);
      device_handle->OpenSharedResource (handle,
          IID_PPV_ARGS (&shared_texture));

      mem_wrapped = gst_d3d11_allocator_alloc_wrapped (nullptr, device_,
          shared_texture.Get (), render_info_.size, nullptr, nullptr);

      sharable_ = gst_buffer_new ();
      gst_buffer_append_memory (sharable_, mem_wrapped);
    }

    if (GST_VIDEO_INFO_FORMAT (&info_) == GST_VIDEO_FORMAT_RGBA) {
      GstMapInfo src_info, dst_info;
      GstMemory *src_mem, *dst_mem;
      D3D11_TEXTURE2D_DESC src_desc, dst_desc;
      D3D11_BOX src_box;
      ID3D11Texture2D *src_tex, *dst_tex;
      ID3D11DeviceContext *context =
          gst_d3d11_device_get_device_context_handle (device_);

      src_mem = gst_buffer_peek_memory (buffer_, 0);
      dst_mem = gst_buffer_peek_memory (sharable_, 0);

      gst_memory_map (src_mem, &src_info,
          (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11));
      gst_memory_map (dst_mem,
          &dst_info, (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11));

      src_tex = (ID3D11Texture2D *) src_info.data;
      dst_tex = (ID3D11Texture2D *) dst_info.data;

      src_tex->GetDesc (&src_desc);
      dst_tex->GetDesc (&dst_desc);

      src_box.left = 0;
      src_box.top = 0;
      src_box.front = 0;
      src_box.back = 1;
      src_box.right = MIN (src_desc.Width, dst_desc.Width);
      src_box.bottom = MIN (src_desc.Height, dst_desc.Height);

      context->CopySubresourceRegion (dst_tex, 0,
          0, 0, 0, src_tex, 0, &src_box);
      gst_memory_unmap (src_mem, &src_info);
      gst_memory_unmap (dst_mem, &dst_info);
    } else {
      gst_d3d11_converter_convert_buffer_unlocked (conv_, buffer_, sharable_);
    }
    gst_d3d11_device_unlock (device_);

    markDirty (QSGNode::DirtyMaterial);
  } else if (backbuffer_ && buffer_changed) {
    GstMapInfo map_info;
    auto mem = gst_buffer_peek_memory (backbuffer_, 0);
    auto dmem = GST_D3D11_MEMORY_CAST (mem);

    gst_memory_map (mem,
        &map_info, (GstMapFlags) (GST_MAP_D3D11 | GST_MAP_WRITE));

    auto rtv = gst_d3d11_memory_get_render_target_view (dmem, 0);
    auto ctx = gst_d3d11_device_get_device_context_handle (dmem->device);

    FLOAT clear_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
    ctx->ClearRenderTargetView (rtv, clear_color);

    gst_memory_unmap (mem, &map_info);
    markDirty (QSGNode::DirtyMaterial);
  }

  mapTexture ();
}
