/*
 *  Copyright (C) 2007-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "RendererVAAPIGLES.h"

#include "../RenderFactory.h"
#include "cores/VideoPlayer/DVDCodecs/DVDCodecUtils.h"
#include "cores/VideoPlayer/DVDCodecs/Video/VAAPI.h"
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "utils/EGLFence.h"
#include "utils/GLUtils.h"
#include "utils/log.h"

#include <memory>

using namespace VAAPI;
using namespace KODI::UTILS::EGL;

IVaapiWinSystem* CRendererVAAPIGLES::m_pWinSystem = nullptr;

CBaseRenderer* CRendererVAAPIGLES::Create(CVideoBuffer* buffer)
{
  CVaapiRenderPicture *vb = dynamic_cast<CVaapiRenderPicture*>(buffer);
  if (vb)
    return new CRendererVAAPIGLES();

  return nullptr;
}

void CRendererVAAPIGLES::Register(IVaapiWinSystem* winSystem,
                                  VADisplay vaDpy,
                                  EGLDisplay eglDisplay,
                                  bool& general,
                                  bool& deepColor)
{
  general = deepColor = false;

  int major_version, minor_version;
  if (vaInitialize(vaDpy, &major_version, &minor_version) != VA_STATUS_SUCCESS)
  {
    vaTerminate(vaDpy);
    return;
  }

  // Probe importable surface formats via vaExportSurfaceHandle.
  CCapabilities& caps = CDecoder::GetCaps();
  CVaapi2Texture::TestInteropFormats(vaDpy, eglDisplay, caps);

  CLog::Log(LOGDEBUG, "VAAPI EGL interop: {}", caps.ToString());

  // Bool out-params are views over caps for the OptionalsReg / WinSystem
  // boundary that still expresses capability as the general/deepColor pair.
  general = caps.Supports(AV_PIX_FMT_NV12);
  deepColor = caps.Supports(AV_PIX_FMT_P010);

  vaTerminate(vaDpy);

  if (general)
  {
    VIDEOPLAYER::CRendererFactory::RegisterRenderer("vaapi", CRendererVAAPIGLES::Create);
    m_pWinSystem = winSystem;
  }
}

CRendererVAAPIGLES::CRendererVAAPIGLES() = default;

CRendererVAAPIGLES::~CRendererVAAPIGLES()
{
  for (int i = 0; i < NUM_BUFFERS; ++i)
  {
    DeleteTexture(i);
  }
  // renderer destruction runs on the render thread with the GL context current
  m_texturePool.ReleaseAll();
}

bool CRendererVAAPIGLES::Configure(const VideoPicture& picture, float fps, unsigned int orientation)
{
  CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
  if (pic->procPic.videoSurface != VA_INVALID_ID)
    m_isVAAPIBuffer = true;
  else
    m_isVAAPIBuffer = false;

  m_vaapiFourcc = pic->procPic.fourcc;

  if (m_isVAAPIBuffer)
  {
    InteropInfo interop;
    interop.textureTarget = GL_TEXTURE_2D;
    interop.eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
    interop.eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");
    interop.glEGLImageTargetTexture2DOES =
        (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
    interop.eglDisplay = m_pWinSystem->GetEGLDisplay();

    m_texturePool.ReleaseAll();
    m_texturePool.Init(interop);
    for (auto& tex : m_vaapiTextures)
      tex = nullptr;

    for (auto& fence : m_fences)
    {
      fence = std::make_unique<CEGLFence>(CRendererVAAPIGLES::m_pWinSystem->GetEGLDisplay());
    }
  }

  return CLinuxRendererGLES::Configure(picture, fps, orientation);
}

bool CRendererVAAPIGLES::ConfigChanged(const VideoPicture& picture)
{
  CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(picture.videoBuffer);
  if ((pic->procPic.videoSurface != VA_INVALID_ID && !m_isVAAPIBuffer) ||
      (pic->procPic.videoSurface == VA_INVALID_ID && m_isVAAPIBuffer))
    return true;

  return false;
}

EShaderFormat CRendererVAAPIGLES::GetShaderFormat()
{
  if (!m_isVAAPIBuffer)
    return SHADER_NV12;

  // Per-fourcc sampling path. Semi-planar 4:2:0 surfaces (NV12 / P010 /
  // P012 / P016) all share SHADER_NV12_RRG; Mesa imports their DMA-BUF as
  // GL_R16 / GL_RG16 textures whose normalized 0..1 sampled floats span
  // the full range regardless of underlying bit depth, so one shader
  // covers all 4:2:0 bit depths. Packed 4:2:2 and 4:4:4 each have their
  // own shader because their channel layouts differ.
  switch (m_vaapiFourcc)
  {
    case VA_FOURCC_NV12:
    case VA_FOURCC_P010:
    case VA_FOURCC_P012:
    case VA_FOURCC_P016:
      return SHADER_NV12_RRG;
    case VA_FOURCC_Y210:
    case VA_FOURCC_Y212:
    case VA_FOURCC_Y216:
      return SHADER_Y210;
    case VA_FOURCC_AYUV:
    case VA_FOURCC_XYUV:
      return SHADER_AYUV;
    case VA_FOURCC_Y410:
      return SHADER_Y410;
    case VA_FOURCC_Y412:
    case VA_FOURCC_Y416:
      return SHADER_Y412;
    default:
      CLog::Log(LOGDEBUG, "CRendererVAAPIGLES::GetShaderFormat - unrecognized fourcc: {:#x}",
                m_vaapiFourcc);
      return SHADER_NV12_RRG;
  }
}

bool CRendererVAAPIGLES::LoadShadersHook()
{
  return false;
}

bool CRendererVAAPIGLES::RenderHook(int idx)
{
  return false;
}

bool CRendererVAAPIGLES::CreateTexture(int index)
{
  if (!m_isVAAPIBuffer)
  {
    DeleteTexture(index);

    if (!CreateNV12Texture(index))
      return false;

    // Allocate backing memory for NV12 plane copy (base class leaves planes null).
    // CFFmpegPostproc AVFrame data may be recycled before upload, so we copy into
    // stable buffers — matching the GL renderer's approach.
    YuvImage& im = m_buffers[index].image;
    for (int i = 0; i < 2; i++)
      im.plane[i] = new uint8_t[im.planesize[i]];

    m_nv12Allocated[index] = true;
    return true;
  }

  CPictureBuffer &buf = m_buffers[index];
  YuvImage &im = buf.image;
  CYuvPlane (&planes)[YuvImage::MAX_PLANES] = buf.fields[0];

  DeleteTexture(index);

  im = {};
  std::fill(std::begin(planes), std::end(planes), CYuvPlane{});
  im.height = m_sourceHeight;
  im.width  = m_sourceWidth;
  im.cshift_x = 1;
  im.cshift_y = 1;

  planes[0].id = 1;

  return true;
}

void CRendererVAAPIGLES::DeleteTexture(int index)
{
  ReleaseBuffer(index);

  if (!m_isVAAPIBuffer)
  {
    if (m_nv12Allocated[index])
    {
      YuvImage& im = m_buffers[index].image;
      for (int i = 0; i < 2; i++)
      {
        delete[] im.plane[i];
        im.plane[i] = nullptr;
      }
      m_nv12Allocated[index] = false;
    }
    DeleteNV12Texture(index);
    return;
  }

  CPictureBuffer &buf = m_buffers[index];
  buf.fields[FIELD_FULL][0].id = 0;
  buf.fields[FIELD_FULL][1].id = 0;
  buf.fields[FIELD_FULL][2].id = 0;
}

bool CRendererVAAPIGLES::UploadTexture(int index)
{
  if (!m_isVAAPIBuffer)
  {
    CPictureBuffer& buf = m_buffers[index];
    CVaapiRenderPicture* pic = dynamic_cast<CVaapiRenderPicture*>(buf.videoBuffer);
    if (!pic || !pic->valid)
      return false;

    if (!buf.loaded)
    {
      YuvImage& dst = buf.image;
      YuvImage src;
      pic->GetPlanes(src.plane);
      pic->GetStrides(src.stride);
      CVideoBuffer::CopyNV12Picture(&dst, &src);
    }
    CalculateTextureSourceRects(index, 3);
    return UploadNV12Texture(index);
  }

  CPictureBuffer &buf = m_buffers[index];

  CVaapiRenderPicture *pic = dynamic_cast<CVaapiRenderPicture*>(buf.videoBuffer);

  if (!pic || !pic->valid)
  {
    return false;
  }

  m_vaapiTextures[index] = m_texturePool.Get(pic, m_vaapiTextures);
  if (!m_vaapiTextures[index])
    return false;

  YuvImage &im = buf.image;
  CYuvPlane (&planes)[3] = buf.fields[0];

  auto size = m_vaapiTextures[index]->GetTextureSize();
  planes[0].texwidth  = size.Width();
  planes[0].texheight = size.Height();

  // Packed 4:2:2 fourccs (YUY2 / Y210 / Y212 / Y216) hold two luma per
  // texel, so the GL texture covers half the source width with each texel
  // representing a (Y0, Cb, Y1, Cr) macropixel. Match the LinuxRendererGLES
  // YUYV path: halve texwidth (ceiling for odd widths) and tell the shader
  // pixpertex_x = 2.
  const bool packed422 = (m_vaapiFourcc == VA_FOURCC_Y210 || m_vaapiFourcc == VA_FOURCC_Y212 ||
                          m_vaapiFourcc == VA_FOURCC_Y216);
  if (packed422)
    planes[0].texwidth = (planes[0].texwidth + 1) / 2;

  planes[1].texwidth  = planes[0].texwidth  >> im.cshift_x;
  planes[1].texheight = planes[0].texheight >> im.cshift_y;
  planes[2].texwidth  = planes[1].texwidth;
  planes[2].texheight = planes[1].texheight;

  for (int p = 0; p < 3; p++)
  {
    planes[p].pixpertex_x = packed422 ? 2 : 1;
    planes[p].pixpertex_y = 1;
  }

  // set textures
  planes[0].id = m_vaapiTextures[index]->GetTextureY();
  planes[1].id = m_vaapiTextures[index]->GetTextureVU();
  planes[2].id = m_vaapiTextures[index]->GetTextureVU();

  for (int p=0; p<2; p++)
  {
    glBindTexture(m_textureTarget, planes[p].id);
    glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(m_textureTarget, 0);
    VerifyGLState();
  }

  CalculateTextureSourceRects(index, 3);
  return true;
}

void CRendererVAAPIGLES::AfterRenderHook(int index)
{
  if (m_fences[index])
  {
    m_fences[index]->DestroyFence();
    m_fences[index]->CreateFence();
  }
}

bool CRendererVAAPIGLES::NeedBuffer(int index)
{
  if (m_fences[index])
    return !m_fences[index]->IsSignaled();

  return false;
}

void CRendererVAAPIGLES::ReleaseBuffer(int index)
{
  if (m_fences[index])
    m_fences[index]->DestroyFence();

  if (m_isVAAPIBuffer)
    m_vaapiTextures[index] = nullptr;

  CLinuxRendererGLES::ReleaseBuffer(index);
}
