/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 * Video mode switching
 * Copyright (C) 1997-2001 Id Software, Inc.
 * Ported from Quake 2 by Alex Fraser <alex@phatcore.com>
 */

/** \file
 * \ingroup GHOST
 */

#include <stdio.h>

#ifdef WITH_X11_XF86VMODE
#  include <X11/Xlib.h>
#  include <X11/extensions/xf86vmode.h>
#endif

#include "GHOST_DisplayManagerX11.h"
#include "GHOST_SystemX11.h"

GHOST_DisplayManagerX11::GHOST_DisplayManagerX11(GHOST_SystemX11 *system)
    : GHOST_DisplayManager(), m_system(system)
{
  /* nothing to do. */
}

GHOST_TSuccess GHOST_DisplayManagerX11::getNumDisplays(GHOST_TUns8 &numDisplays) const
{
  numDisplays = m_system->getNumDisplays();
  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_DisplayManagerX11::getNumDisplaySettings(GHOST_TUns8 display,
                                                              GHOST_TInt32 &numSettings) const
{
#ifdef WITH_X11_XF86VMODE
  int majorVersion, minorVersion;
  XF86VidModeModeInfo **vidmodes;
  Display *dpy = m_system->getXDisplay();

  GHOST_ASSERT(display < 1, "Only single display systems are currently supported.\n");

  if (dpy == NULL)
    return GHOST_kFailure;

  majorVersion = minorVersion = 0;
  if (!XF86VidModeQueryVersion(dpy, &majorVersion, &minorVersion)) {
    fprintf(stderr, "Error: XF86VidMode extension missing!\n");
    return GHOST_kFailure;
  }

  if (XF86VidModeGetAllModeLines(dpy, DefaultScreen(dpy), &numSettings, &vidmodes)) {
    XFree(vidmodes);
  }

#else
  /* We only have one X11 setting at the moment. */
  GHOST_ASSERT(display < 1, "Only single display systems are currently supported.\n");
  numSettings = 1;
#endif

  (void)display;
  return GHOST_kSuccess;
}

/* from SDL2 */
#ifdef WITH_X11_XF86VMODE
static int calculate_rate(XF86VidModeModeInfo *info)
{
  return (info->htotal && info->vtotal) ? (1000 * info->dotclock / (info->htotal * info->vtotal)) :
                                          0;
}
#endif

GHOST_TSuccess GHOST_DisplayManagerX11::getDisplaySetting(GHOST_TUns8 display,
                                                          GHOST_TInt32 index,
                                                          GHOST_DisplaySetting &setting) const
{
  Display *dpy = m_system->getXDisplay();

  if (dpy == NULL)
    return GHOST_kFailure;

  (void)display;

#ifdef WITH_X11_XF86VMODE
  int majorVersion, minorVersion;

  GHOST_ASSERT(display < 1, "Only single display systems are currently supported.\n");

  majorVersion = minorVersion = 0;
  if (XF86VidModeQueryVersion(dpy, &majorVersion, &minorVersion)) {
    XF86VidModeModeInfo **vidmodes;
    int numSettings;

    if (XF86VidModeGetAllModeLines(dpy, DefaultScreen(dpy), &numSettings, &vidmodes)) {
      GHOST_ASSERT(index < numSettings, "Requested setting outside of valid range.\n");

      setting.xPixels = vidmodes[index]->hdisplay;
      setting.yPixels = vidmodes[index]->vdisplay;
      setting.bpp = DefaultDepth(dpy, DefaultScreen(dpy));
      setting.frequency = calculate_rate(vidmodes[index]);
      XFree(vidmodes);

      return GHOST_kSuccess;
    }
  }
  else {
    fprintf(stderr, "Warning: XF86VidMode extension missing!\n");
    /* fallback to non xf86vmode below */
  }
#endif /* WITH_X11_XF86VMODE */

  GHOST_ASSERT(display < 1, "Only single display systems are currently supported.\n");
  GHOST_ASSERT(index < 1, "Requested setting outside of valid range.\n");
  (void)index;

  setting.xPixels = DisplayWidth(dpy, DefaultScreen(dpy));
  setting.yPixels = DisplayHeight(dpy, DefaultScreen(dpy));
  setting.bpp = DefaultDepth(dpy, DefaultScreen(dpy));
  setting.frequency = 60.0f;

  return GHOST_kSuccess;
}

GHOST_TSuccess GHOST_DisplayManagerX11::getCurrentDisplaySetting(
    GHOST_TUns8 display, GHOST_DisplaySetting &setting) const
{
  /* According to the xf86vidmodegetallmodelines man page,
   * "The first element of the array corresponds to the current video mode."
   */
  return getDisplaySetting(display, 0, setting);
}

GHOST_TSuccess GHOST_DisplayManagerX11::setCurrentDisplaySetting(
    GHOST_TUns8 /*display*/, const GHOST_DisplaySetting &setting)
{
#ifdef WITH_X11_XF86VMODE
  /* Mode switching code ported from SDL:
   * See: src/video/x11/SDL_x11modes.c:set_best_resolution
   */
  int majorVersion, minorVersion;
  XF86VidModeModeInfo **vidmodes;
  Display *dpy = m_system->getXDisplay();
  int scrnum, num_vidmodes;

  if (dpy == NULL)
    return GHOST_kFailure;

  scrnum = DefaultScreen(dpy);

  /* Get video mode list */
  majorVersion = minorVersion = 0;
  if (!XF86VidModeQueryVersion(dpy, &majorVersion, &minorVersion)) {
    fprintf(stderr, "Error: XF86VidMode extension missing!\n");
    return GHOST_kFailure;
  }
#  ifdef DEBUG
  printf("Using XFree86-VidModeExtension Version %d.%d\n", majorVersion, minorVersion);
#  endif

  if (XF86VidModeGetAllModeLines(dpy, scrnum, &num_vidmodes, &vidmodes)) {
    int best_fit = -1;

    for (int i = 0; i < num_vidmodes; i++) {
      if (vidmodes[i]->hdisplay < setting.xPixels || vidmodes[i]->vdisplay < setting.yPixels) {
        continue;
      }

      if (best_fit == -1 || (vidmodes[i]->hdisplay < vidmodes[best_fit]->hdisplay) ||
          (vidmodes[i]->hdisplay == vidmodes[best_fit]->hdisplay &&
           vidmodes[i]->vdisplay < vidmodes[best_fit]->vdisplay)) {
        best_fit = i;
        continue;
      }

      if ((vidmodes[i]->hdisplay == vidmodes[best_fit]->hdisplay) &&
          (vidmodes[i]->vdisplay == vidmodes[best_fit]->vdisplay)) {
        if (!setting.frequency) {
          /* Higher is better, right? */
          if (calculate_rate(vidmodes[i]) > calculate_rate(vidmodes[best_fit])) {
            best_fit = i;
          }
        }
        else {
          if (abs(calculate_rate(vidmodes[i]) - (int)setting.frequency) <
              abs(calculate_rate(vidmodes[best_fit]) - (int)setting.frequency)) {
            best_fit = i;
          }
        }
      }
    }

    if (best_fit != -1) {
#  ifdef DEBUG
      printf("Switching to video mode %dx%d %dx%d %d\n",
             vidmodes[best_fit]->hdisplay,
             vidmodes[best_fit]->vdisplay,
             vidmodes[best_fit]->htotal,
             vidmodes[best_fit]->vtotal,
             calculate_rate(vidmodes[best_fit]));
#  endif

      /* change to the mode */
      XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]);

      /* Move the viewport to top left */
      XF86VidModeSetViewPort(dpy, scrnum, 0, 0);
    }

    XFree(vidmodes);
  }
  else {
    return GHOST_kFailure;
  }

  XFlush(dpy);
  return GHOST_kSuccess;

#else
  (void)setting;

  /* Just pretend the request was successful. */
  return GHOST_kSuccess;
#endif
}
