// Copyright 2010 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "UICommon/X11Utils.h"

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <spawn.h>
#include <string>
#include <sys/wait.h>
#include <unistd.h>

#include <fmt/format.h>

#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"

extern char** environ;

namespace X11Utils
{
bool ToggleFullscreen(Display* dpy, Window win)
{
  // Init X event structure for _NET_WM_STATE_FULLSCREEN client message
  XEvent event;
  event.xclient.type = ClientMessage;
  event.xclient.message_type = XInternAtom(dpy, "_NET_WM_STATE", False);
  event.xclient.window = win;
  event.xclient.format = 32;
  event.xclient.data.l[0] = _NET_WM_STATE_TOGGLE;
  event.xclient.data.l[1] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);

  // Send the event
  if (!XSendEvent(dpy, DefaultRootWindow(dpy), False,
                  SubstructureRedirectMask | SubstructureNotifyMask, &event))
  {
    ERROR_LOG_FMT(VIDEO, "Failed to switch fullscreen/windowed mode.");
    return false;
  }

  return true;
}

void InhibitScreensaver(Window win, bool suspend)
{
  char id[11];
  snprintf(id, sizeof(id), "0x%lx", win);

  // Call xdg-screensaver
  char* argv[4] = {(char*)"xdg-screensaver", (char*)(suspend ? "suspend" : "resume"), id, nullptr};
  pid_t pid;
  if (!posix_spawnp(&pid, "xdg-screensaver", nullptr, nullptr, argv, environ))
  {
    int status;
    while (waitpid(pid, &status, 0) == -1)
      ;

    INFO_LOG_FMT(VIDEO, "Started xdg-screensaver (PID = {})", pid);
  }
}

#ifdef HAVE_XRANDR
XRRConfiguration::XRRConfiguration(Display* _dpy, Window _win)
    : dpy(_dpy), win(_win), screenResources(nullptr), outputInfo(nullptr), crtcInfo(nullptr),
      fullMode(0), fs_fb_width(0), fs_fb_height(0), fs_fb_width_mm(0), fs_fb_height_mm(0),
      bValid(true), bIsFullscreen(false)
{
  int XRRMajorVersion, XRRMinorVersion;

  if (!XRRQueryVersion(dpy, &XRRMajorVersion, &XRRMinorVersion) ||
      (XRRMajorVersion < 1 || (XRRMajorVersion == 1 && XRRMinorVersion < 3)))
  {
    WARN_LOG_FMT(VIDEO, "XRRExtension not supported.");
    bValid = false;
    return;
  }

  screenResources = XRRGetScreenResourcesCurrent(dpy, win);

  screen = DefaultScreen(dpy);
  fb_width = DisplayWidth(dpy, screen);
  fb_height = DisplayHeight(dpy, screen);
  fb_width_mm = DisplayWidthMM(dpy, screen);
  fb_height_mm = DisplayHeightMM(dpy, screen);

  INFO_LOG_FMT(VIDEO, "XRRExtension-Version {}.{}", XRRMajorVersion, XRRMinorVersion);
  Update();
}

XRRConfiguration::~XRRConfiguration()
{
  if (bValid && bIsFullscreen)
    ToggleDisplayMode(False);
  if (screenResources)
    XRRFreeScreenResources(screenResources);
  if (outputInfo)
    XRRFreeOutputInfo(outputInfo);
  if (crtcInfo)
    XRRFreeCrtcInfo(crtcInfo);
}

void XRRConfiguration::Update()
{
  const std::string fullscreen_display_res = Config::Get(Config::MAIN_FULLSCREEN_DISPLAY_RES);
  if (fullscreen_display_res == "Auto")
    return;

  if (!bValid)
    return;

  if (outputInfo)
  {
    XRRFreeOutputInfo(outputInfo);
    outputInfo = nullptr;
  }
  if (crtcInfo)
  {
    XRRFreeCrtcInfo(crtcInfo);
    crtcInfo = nullptr;
  }
  fullMode = 0;

  // Get the resolution setings for fullscreen mode
  unsigned int fullWidth, fullHeight;
  char* output_name = nullptr;
  char auxFlag = '\0';
  if (fullscreen_display_res.find(':') == std::string::npos)
  {
    fullWidth = fb_width;
    fullHeight = fb_height;
  }
  else
  {
    sscanf(fullscreen_display_res.c_str(), "%m[^:]: %ux%u%c", &output_name, &fullWidth, &fullHeight,
           &auxFlag);
  }
  bool want_interlaced = ('i' == auxFlag);

  for (int i = 0; i < screenResources->noutput; i++)
  {
    XRROutputInfo* output_info =
        XRRGetOutputInfo(dpy, screenResources, screenResources->outputs[i]);
    if (output_info && output_info->crtc && output_info->connection == RR_Connected)
    {
      XRRCrtcInfo* crtc_info = XRRGetCrtcInfo(dpy, screenResources, output_info->crtc);
      if (crtc_info)
      {
        if (!output_name || !strcmp(output_name, output_info->name))
        {
          // Use the first output for the default setting.
          if (!output_name)
          {
            output_name = strdup(output_info->name);
            Config::SetBaseOrCurrent(
                Config::MAIN_FULLSCREEN_DISPLAY_RES,
                fmt::format("{}: {}x{}", output_info->name, fullWidth, fullHeight));
          }
          outputInfo = output_info;
          crtcInfo = crtc_info;
          for (int j = 0; j < output_info->nmode && fullMode == 0; j++)
          {
            for (int k = 0; k < screenResources->nmode && fullMode == 0; k++)
            {
              if (output_info->modes[j] == screenResources->modes[k].id)
              {
                if (fullWidth == screenResources->modes[k].width &&
                    fullHeight == screenResources->modes[k].height &&
                    want_interlaced == !!(screenResources->modes[k].modeFlags & RR_Interlace))
                {
                  fullMode = screenResources->modes[k].id;
                  if (crtcInfo->x + (int)screenResources->modes[k].width > fs_fb_width)
                    fs_fb_width = crtcInfo->x + screenResources->modes[k].width;
                  if (crtcInfo->y + (int)screenResources->modes[k].height > fs_fb_height)
                    fs_fb_height = crtcInfo->y + screenResources->modes[k].height;
                }
              }
            }
          }
        }
        else
        {
          if (crtc_info->x + (int)crtc_info->width > fs_fb_width)
            fs_fb_width = crtc_info->x + crtc_info->width;
          if (crtc_info->y + (int)crtc_info->height > fs_fb_height)
            fs_fb_height = crtc_info->y + crtc_info->height;
        }
      }
      if (crtc_info && crtcInfo != crtc_info)
        XRRFreeCrtcInfo(crtc_info);
    }
    if (output_info && outputInfo != output_info)
      XRRFreeOutputInfo(output_info);
  }
  fs_fb_width_mm = fs_fb_width * DisplayHeightMM(dpy, screen) / DisplayHeight(dpy, screen);
  fs_fb_height_mm = fs_fb_height * DisplayHeightMM(dpy, screen) / DisplayHeight(dpy, screen);

  if (output_name)
    free(output_name);

  if (outputInfo && crtcInfo && fullMode)
  {
    INFO_LOG_FMT(VIDEO, "Fullscreen Resolution {}x{}", fullWidth, fullHeight);
  }
  else
  {
    ERROR_LOG_FMT(VIDEO, "Failed to obtain fullscreen size.\n"
                         "Using current desktop resolution for fullscreen.");
  }
}

void XRRConfiguration::ToggleDisplayMode(bool bFullscreen)
{
  if (!bValid || !screenResources || !outputInfo || !crtcInfo || !fullMode)
    return;
  if (bFullscreen == bIsFullscreen)
    return;

  XGrabServer(dpy);
  if (bFullscreen)
  {
    XRRSetCrtcConfig(dpy, screenResources, outputInfo->crtc, CurrentTime, crtcInfo->x, crtcInfo->y,
                     fullMode, crtcInfo->rotation, crtcInfo->outputs, crtcInfo->noutput);
    XRRSetScreenSize(dpy, win, fs_fb_width, fs_fb_height, fs_fb_width_mm, fs_fb_height_mm);
    bIsFullscreen = true;
  }
  else
  {
    XRRSetCrtcConfig(dpy, screenResources, outputInfo->crtc, CurrentTime, crtcInfo->x, crtcInfo->y,
                     crtcInfo->mode, crtcInfo->rotation, crtcInfo->outputs, crtcInfo->noutput);
    XRRSetScreenSize(dpy, win, fb_width, fb_height, fb_width_mm, fb_height_mm);
    bIsFullscreen = false;
  }
  XUngrabServer(dpy);
  XSync(dpy, false);
}

void XRRConfiguration::AddResolutions(std::vector<std::string>& resos)
{
  if (!bValid || !screenResources)
    return;

  // Get all full screen resolutions for the config dialog
  for (int i = 0; i < screenResources->noutput; i++)
  {
    XRROutputInfo* output_info =
        XRRGetOutputInfo(dpy, screenResources, screenResources->outputs[i]);

    if (output_info && output_info->crtc && output_info->connection == RR_Connected)
    {
      for (int j = 0; j < output_info->nmode; j++)
      {
        for (int k = 0; k < screenResources->nmode; k++)
        {
          if (output_info->modes[j] == screenResources->modes[k].id)
          {
            bool interlaced = !!(screenResources->modes[k].modeFlags & RR_Interlace);
            const std::string strRes = std::string(output_info->name) + ": " +
                                       std::string(screenResources->modes[k].name) +
                                       (interlaced ? "i" : "");
            // Only add unique resolutions
            if (std::find(resos.begin(), resos.end(), strRes) == resos.end())
            {
              resos.push_back(strRes);
            }
          }
        }
      }
    }
    if (output_info)
      XRRFreeOutputInfo(output_info);
  }
}

#endif
}  // namespace X11Utils
