#include "DisplayManagerX11.h"

///////////////////////////////////////////////////////////////////////////////////////////////////
bool DisplayManagerX11::initialize()
{
  m_displays.clear();

  if (!xdisplay)
    xdisplay = XOpenDisplay(NULL);
  if (!xdisplay)
    return false;

  int event_base, error_base;
  if (!XRRQueryExtension(xdisplay, &event_base, &error_base))
    return false;

  if (resources)
    XRRFreeScreenResources(resources);

  resources = XRRGetScreenResources(xdisplay, RootWindow(xdisplay, DefaultScreen(xdisplay)));
  if (!resources)
    return false;

  for (int o = 0; o < resources->noutput; o++) {
    RROutput output = resources->outputs[o];
    XRRCrtcInfo *crtc = NULL;
    XRROutputInfo *out = XRRGetOutputInfo(xdisplay, resources, output);
    if (!out || !out->crtc)
      goto next;
    crtc = XRRGetCrtcInfo(xdisplay, resources, out->crtc);
    if (!crtc)
      goto next;

    {
      DMDisplayPtr display = DMDisplayPtr(new DMDisplay());
      display->m_id = m_displays.size();
      display->m_name = out->name;
      display->m_privId = o;
      m_displays[display->m_id] = display;

      for (int om = 0; om < out->nmode; om++) {
        RRMode xm = out->modes[om];
        for (int n = 0; n < resources->nmode; n++) {
          XRRModeInfo m = resources->modes[n];
          if (m.id != xm)
            continue;

          DMVideoModePtr mode = DMVideoModePtr(new DMVideoMode());
          mode->m_privId = n;
          mode->m_id = display->m_videoModes.size();
          display->m_videoModes[mode->m_id] = mode;

          mode->m_interlaced = m.modeFlags & RR_Interlace;

          double vTotal = m.vTotal;
          if (m.modeFlags & RR_DoubleScan)
            vTotal *= 2;
          if (m.modeFlags & RR_Interlace)
            vTotal /= 2;
          mode->m_refreshRate = m.dotClock / (m.hTotal * vTotal);

          mode->m_width = m.width;
          mode->m_height = m.height;
          mode->m_bitsPerPixel = 0; // we can't know; depth is not managed by xrandr
        }
      }
    }
  next:
    if (crtc)
      XRRFreeCrtcInfo(crtc);
    if (out)
      XRRFreeOutputInfo(out);
  }

  if (m_displays.empty())
    return false;
  else
    return DisplayManager::initialize();
}

///////////////////////////////////////////////////////////////////////////////////////////////////
bool DisplayManagerX11::setDisplayMode(int display, int mode)
{
  if (!isValidDisplayMode(display, mode) || !resources)
    return false;

  DMDisplayPtr displayptr = m_displays[display];
  DMVideoModePtr videomode = displayptr->m_videoModes[mode];

  RROutput output = resources->outputs[displayptr->m_privId];
  RRMode xrmode = resources->modes[videomode->m_privId].id;

  bool success = false;
  XRRCrtcInfo *crtc = NULL;
  XRROutputInfo *out = XRRGetOutputInfo(xdisplay, resources, output);
  if (!out || !out->crtc)
    goto done;
  crtc = XRRGetCrtcInfo(xdisplay, resources, out->crtc);
  if (!crtc)
    goto done;

  // Keep all information, except the mode.
  success = XRRSetCrtcConfig(xdisplay, resources, out->crtc, crtc->timestamp,
                             crtc->x, crtc->y, xrmode, crtc->rotation,
                             crtc->outputs, crtc->noutput);

  // The return value isn't always accurate, apparently.
  success = true;

done:
  if (crtc)
    XRRFreeCrtcInfo(crtc);
  if (out)
    XRRFreeOutputInfo(out);

  return success;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManagerX11::getCurrentDisplayMode(int display)
{
  if (!isValidDisplay(display) || !resources)
    return -1;

  DMDisplayPtr displayptr = m_displays[display];
  RROutput output = resources->outputs[displayptr->m_privId];

  int videomode_id = -1;
  XRRCrtcInfo *crtc = NULL;
  XRROutputInfo *out = XRRGetOutputInfo(xdisplay, resources, output);
  if (!out || !out->crtc)
    goto done;
  crtc = XRRGetCrtcInfo(xdisplay, resources, out->crtc);
  if (!crtc)
    goto done;

  for(DMVideoModePtr mode : displayptr->m_videoModes)
  {
    XRRModeInfo m = resources->modes[mode->m_privId];
    if (crtc->mode == m.id)
    {
      videomode_id = mode->m_id;
      break;
    }
  }

done:
  if (crtc)
    XRRFreeCrtcInfo(crtc);
  if (out)
    XRRFreeOutputInfo(out);

  return videomode_id;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManagerX11::getMainDisplay()
{
  // This is probably not what DisplayManager means.
  RROutput main = XRRGetOutputPrimary(xdisplay, RootWindow(xdisplay, DefaultScreen(xdisplay)));
  if (!main || !resources)
    return -1;

  for (int o = 0; o < resources->noutput; o++)
  {
    if (main == resources->outputs[o])
    {
      for (int displayid = 0; displayid < m_displays.size(); displayid++)
      {
        if (m_displays[displayid]->m_privId == o)
          return displayid;
      }
      break;
    }
  }
  return -1;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManagerX11::getDisplayFromPoint(int x, int y)
{
  for (int displayid = 0; displayid < m_displays.size(); displayid++)
  {
    RROutput output = resources->outputs[m_displays[displayid]->m_privId];
    XRRCrtcInfo *crtc = NULL;
    XRROutputInfo *out = XRRGetOutputInfo(xdisplay, resources, output);
    bool matches = false;
    if (!out || !out->crtc)
      goto done;
    crtc = XRRGetCrtcInfo(xdisplay, resources, out->crtc);
    if (!crtc)
      goto done;

    matches = x >= crtc->x && y >= crtc->y &&
              x < crtc->x + (int)crtc->width &&
              y < crtc->y + (int)crtc->height;

  done:
    if (crtc)
      XRRFreeCrtcInfo(crtc);
    if (out)
      XRRFreeOutputInfo(out);

    if (matches)
      return displayid;
  }

  return -1;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
DisplayManagerX11::~DisplayManagerX11()
{
  if (resources)
    XRRFreeScreenResources(resources);
  if (xdisplay)
    XCloseDisplay(xdisplay);
}
