/*
 *  Copyright (C) 2005-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 "GLContextGLX.h"

#include "utils/log.h"

#include <GL/glx.h>

#include "system_gl.h"

using namespace KODI::WINDOWING::X11;

CGLContextGLX::CGLContextGLX(Display *dpy) : CGLContext(dpy)
{
  m_extPrefix = "GLX_";
  m_vsyncMode = 0;
}

bool CGLContextGLX::Refresh(bool force, int screen, Window glWindow, bool &newContext)
{
  bool retVal = false;
  m_glxWindow = glWindow;
  m_nScreen = screen;

  // refresh context
  if (m_glxContext && !force)
  {
    CLog::Log(LOGDEBUG, "CWinSystemX11::RefreshGlxContext: refreshing context");
    glXMakeCurrent(m_dpy, None, NULL);
    glXMakeCurrent(m_dpy, glWindow, m_glxContext);
    return true;
  }

  // create context

  XVisualInfo vMask;
  XVisualInfo *visuals;
  XVisualInfo *vInfo = NULL;
  int availableVisuals = 0;
  vMask.screen = screen;
  XWindowAttributes winAttr;

  /* Assume a depth of 24 in case the below calls to XGetWindowAttributes()
     or XGetVisualInfo() fail. That shouldn't happen unless something is
     fatally wrong, but lets prepare for everything. */
  vMask.depth = 24;

  if (XGetWindowAttributes(m_dpy, glWindow, &winAttr))
  {
    vMask.visualid = XVisualIDFromVisual(winAttr.visual);
    vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals);
    if (!vInfo)
      CLog::Log(LOGWARNING, "Failed to get VisualInfo of visual 0x{:x}", (unsigned)vMask.visualid);
    else if(!IsSuitableVisual(vInfo))
    {
      CLog::Log(LOGWARNING,
                "Visual 0x{:x} of the window is not suitable, looking for another one...",
                (unsigned)vInfo->visualid);
      vMask.depth = vInfo->depth;
      XFree(vInfo);
      vInfo = NULL;
    }
  }
  else
    CLog::Log(LOGWARNING, "Failed to get window attributes");

  /* As per glXMakeCurrent documentation, we have to use the same visual as
     m_glWindow. Since that was not suitable for use, we try to use another
     one with the same depth and hope that the used implementation is less
     strict than the documentation. */
  if (!vInfo)
  {
    visuals = XGetVisualInfo(m_dpy, VisualScreenMask | VisualDepthMask, &vMask, &availableVisuals);
    for (int i = 0; i < availableVisuals; i++)
    {
      if (IsSuitableVisual(&visuals[i]))
      {
        vMask.visualid = visuals[i].visualid;
        vInfo = XGetVisualInfo(m_dpy, VisualScreenMask | VisualIDMask, &vMask, &availableVisuals);
        break;
      }
    }
    XFree(visuals);
  }

  if (vInfo)
  {
    CLog::Log(LOGINFO, "Using visual 0x{:x}", (unsigned)vInfo->visualid);
    if (m_glxContext)
    {
      glXMakeCurrent(m_dpy, None, NULL);
      glXDestroyContext(m_dpy, m_glxContext);
      XSync(m_dpy, False);
    }

    if ((m_glxContext = glXCreateContext(m_dpy, vInfo, NULL, True)))
    {
      // make this context current
      glXMakeCurrent(m_dpy, glWindow, m_glxContext);
      retVal = true;
      newContext = true;
    }
    else
      CLog::Log(LOGERROR, "GLX Error: Could not create context");

    XFree(vInfo);
  }
  else
  {
    CLog::Log(LOGERROR, "GLX Error: vInfo is NULL!");
  }

  return retVal;
}

void CGLContextGLX::Destroy()
{
  glXMakeCurrent(m_dpy, None, NULL);
  glXDestroyContext(m_dpy, m_glxContext);
  m_glxContext = 0;
}

void CGLContextGLX::Detach()
{
  glXMakeCurrent(m_dpy, None, NULL);
}

bool CGLContextGLX::IsSuitableVisual(XVisualInfo *vInfo)
{
  int value;

  if (glXGetConfig(m_dpy, vInfo, GLX_RGBA, &value) || !value)
    return false;
  if (glXGetConfig(m_dpy, vInfo, GLX_DOUBLEBUFFER, &value) || !value)
    return false;
  if (glXGetConfig(m_dpy, vInfo, GLX_RED_SIZE, &value) || value < 8)
    return false;
  if (glXGetConfig(m_dpy, vInfo, GLX_GREEN_SIZE, &value) || value < 8)
    return false;
  if (glXGetConfig(m_dpy, vInfo, GLX_BLUE_SIZE, &value) || value < 8)
    return false;
  if (glXGetConfig(m_dpy, vInfo, GLX_DEPTH_SIZE, &value) || value < 24)
    return false;

  return true;
}

void CGLContextGLX::SetVSync(bool enable)
{
  // turn of current setting first
  if(m_glXSwapIntervalEXT)
    m_glXSwapIntervalEXT(m_dpy, m_glxWindow, 0);
  else if(m_glXSwapIntervalMESA)
    m_glXSwapIntervalMESA(0);

  m_iVSyncErrors = 0;

  if(!enable)
    return;

  if (m_glXSwapIntervalEXT)
  {
    m_glXSwapIntervalEXT(m_dpy, m_glxWindow, 1);
    m_vsyncMode = 6;
  }
  if (m_glXSwapIntervalMESA)
  {
    if(m_glXSwapIntervalMESA(1) == 0)
      m_vsyncMode = 2;
    else
      CLog::Log(LOGWARNING, "{} - glXSwapIntervalMESA failed", __FUNCTION__);
  }
  if (m_glXWaitVideoSyncSGI && m_glXGetVideoSyncSGI && !m_vsyncMode)
  {
    unsigned int count;
    if(m_glXGetVideoSyncSGI(&count) == 0)
      m_vsyncMode = 3;
    else
      CLog::Log(LOGWARNING, "{} - glXGetVideoSyncSGI failed, glcontext probably not direct",
                __FUNCTION__);
  }
}

void CGLContextGLX::SwapBuffers()
{
  if (m_vsyncMode == 3)
  {
    glFinish();
    unsigned int before = 0, after = 0;
    if (m_glXGetVideoSyncSGI(&before) != 0)
      CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count",
                __FUNCTION__);

    glXSwapBuffers(m_dpy, m_glxWindow);
    glFinish();

    if(m_glXGetVideoSyncSGI(&after) != 0)
      CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count",
                __FUNCTION__);

    if (after == before)
      m_iVSyncErrors = 1;
    else
      m_iVSyncErrors--;

    if (m_iVSyncErrors > 0)
    {
      CLog::Log(LOGINFO, "GL: retrace count didn't change after buffer swap, switching to vsync mode 4");
      m_iVSyncErrors = 0;
      m_vsyncMode = 4;
    }

    if (m_iVSyncErrors < -200)
    {
      CLog::Log(
          LOGINFO,
          "GL: retrace count change for {} consecutive buffer swap, switching to vsync mode 2",
          -m_iVSyncErrors);
      m_iVSyncErrors = 0;
      m_vsyncMode = 2;
    }
  }
  else if (m_vsyncMode == 4)
  {
    glFinish();
    unsigned int before = 0, swap = 0, after = 0;
    if (m_glXGetVideoSyncSGI(&before) != 0)
      CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count",
                __FUNCTION__);

    if(m_glXWaitVideoSyncSGI(2, (before+1)%2, &swap) != 0)
      CLog::Log(LOGERROR, "{} - glXWaitVideoSyncSGI - Returned error", __FUNCTION__);

    glXSwapBuffers(m_dpy, m_glxWindow);
    glFinish();

    if (m_glXGetVideoSyncSGI(&after) != 0)
      CLog::Log(LOGERROR, "{} - glXGetVideoSyncSGI - Failed to get current retrace count",
                __FUNCTION__);

    if (after == before)
      CLog::Log(LOGERROR, "{} - glXWaitVideoSyncSGI - Woke up early", __FUNCTION__);

    if (after > before + 1)
      m_iVSyncErrors++;
    else
      m_iVSyncErrors = 0;

    if (m_iVSyncErrors > 30)
    {
      CLog::Log(LOGINFO, "GL: retrace count seems to be changing due to the swapbuffers call, switching to vsync mode 3");
      m_vsyncMode = 3;
      m_iVSyncErrors = 0;
    }
  }
  else
    glXSwapBuffers(m_dpy, m_glxWindow);
}

void CGLContextGLX::QueryExtensions()
{
  m_extensions  = " ";
  m_extensions += glXQueryExtensionsString(m_dpy, m_nScreen);
  m_extensions += " ";

  CLog::Log(LOGDEBUG, "GLX_EXTENSIONS:{}", m_extensions);

  if (IsExtSupported("GLX_SGI_video_sync"))
    m_glXWaitVideoSyncSGI = (int (*)(int, int, unsigned int*))glXGetProcAddress((const GLubyte*)"glXWaitVideoSyncSGI");
  else
    m_glXWaitVideoSyncSGI = NULL;

  if (IsExtSupported("GLX_SGI_video_sync"))
    m_glXGetVideoSyncSGI = (int (*)(unsigned int*))glXGetProcAddress((const GLubyte*)"glXGetVideoSyncSGI");
  else
    m_glXGetVideoSyncSGI = NULL;

  if (IsExtSupported("GLX_MESA_swap_control"))
    m_glXSwapIntervalMESA = (int (*)(int))glXGetProcAddress((const GLubyte*)"glXSwapIntervalMESA");
  else
    m_glXSwapIntervalMESA = NULL;

  if (IsExtSupported("GLX_EXT_swap_control"))
    m_glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT");
  else
    m_glXSwapIntervalEXT = NULL;
}
