//
//  DisplayManager.cpp
//  konvergo
//
//  Created by Lionel CHAZALLON on 28/09/2014.
//
//

#include "QsLog.h"
#include "DisplayManager.h"
#include "math.h"
#include "settings/SettingsComponent.h"

///////////////////////////////////////////////////////////////////////////////////////////////////
DisplayManager::DisplayManager(QObject* parent) : QObject(parent) {}

///////////////////////////////////////////////////////////////////////////////////////////////////
bool DisplayManager::initialize()
{
  QLOG_INFO() << QString("DisplayManager found %1 Display(s).").arg(m_displays.size());

  // list video modes
  for(int displayid : m_displays.keys())
  {
    DMDisplayPtr display = m_displays[displayid];
    QLOG_INFO() << QString("Available modes for Display #%1 (%2)").arg(displayid).arg(display->m_name);
    for (int modeid = 0; modeid < display->m_videoModes.size(); modeid++)
    {
      DMVideoModePtr mode = display->m_videoModes[modeid];
      QLOG_INFO() << QString("Mode %1: %2").arg(modeid, 2).arg(mode->getPrettyName());
    }
  }

  // Log current display mode
  int mainDisplay = getMainDisplay();
  if (mainDisplay >= 0)
  {
    int currentMode = getCurrentDisplayMode(mainDisplay);
    if (currentMode >= 0)
      QLOG_INFO() << QString("DisplayManager : Current Display Mode on Display #%1 is %2")
                     .arg(mainDisplay)
                     .arg(m_displays[mainDisplay]->m_videoModes[currentMode]->getPrettyName());
    else
      QLOG_ERROR() << "DisplayManager : unable to retrieve current video mode";
  }
  else
    QLOG_ERROR() << "DisplayManager : unable to retrieve main display";

  return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
DMVideoModePtr DisplayManager::getCurrentVideoMode(int display)
{
  int currentMode = getCurrentDisplayMode(display);
  DMVideoModePtr currentVideoMode;

  if (currentMode >= 0)
    currentVideoMode = m_displays[display]->m_videoModes[currentMode];

  return currentVideoMode;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
bool DisplayManager::isValidDisplay(int display) { return m_displays.contains(display); }

///////////////////////////////////////////////////////////////////////////////////////////////////
bool DisplayManager::isValidDisplayMode(int display, int mode)
{
  if (isValidDisplay(display))
    if (mode >= 0 && mode < m_displays[display]->m_videoModes.size())
      return true;

  return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// refresh: video FPS
// multiple: display FPS
bool DisplayManager::isRateMultipleOf(float refresh, float multiple, bool exact)
{
  long roundedRefresh = lrint(refresh);
  long roundedMultiple = lrint(multiple);

  if (roundedRefresh == 0)
      return false;

  long factor = roundedMultiple / roundedRefresh;
  float newRate = factor * refresh;
  if (newRate < 1)
    return false;

  float tolerance = exact ? 0.01 * factor : 1;

  return fabs(newRate - multiple) < tolerance;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManager::findBestMatch(int display, DMMatchMediaInfo& matchInfo)
{
  bool avoid_25_30 = SettingsComponent::Get().value(SETTINGS_SECTION_VIDEO, "refreshrate.avoid_25hz_30hz").toBool();

  // Grab current videomode information
  DMVideoModePtr currentVideoMode = getCurrentVideoMode(display);
  if (!currentVideoMode)
    return -1;

  // now we try to find the exact match in current resolution
  // then fill a list
  DMVideoModeWeightMap weights;

  DMVideoModeMap::const_iterator modeit = m_displays[display]->m_videoModes.constBegin();

  while (modeit != m_displays[display]->m_videoModes.constEnd())
  {
    DMVideoModePtr candidate = modeit.value();

    // avoid switching to 30 fps (prefer a multiple - 60Hz is ideal)
    // the intention is also to match 30/1.001
    if ((fabs(candidate->m_refreshRate - 30.0) < 0.5) ||
        (fabs(candidate->m_refreshRate - 25.0) < 0.5))
    {
      if (avoid_25_30)
      {
        QLOG_INFO() << "DisplayManager RefreshMatch : skipping rate " << candidate->m_refreshRate << "as requested";
        modeit++;
        continue;
      }
    }

    weights[candidate->m_id] = DMVideoModeWeightPtr(new DMVideoModeWeight);
    weights[candidate->m_id]->m_mode = candidate;
    weights[candidate->m_id]->m_weight = 0;

    // Weight Resolution match
    if ((candidate->m_width == currentVideoMode->m_width) &&
        (candidate->m_height == currentVideoMode->m_height) &&
        (candidate->m_bitsPerPixel == currentVideoMode->m_bitsPerPixel))
    {
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_RES;
    }

    // weight refresh rate
    // exact Match
    if (fabs(candidate->m_refreshRate - matchInfo.m_refreshRate) <= 0.01)
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_REFRESH_RATE_EXACT;

    // exact multiple refresh rate
    if (isRateMultipleOf(matchInfo.m_refreshRate, candidate->m_refreshRate, true))
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_REFRESH_RATE_MULTIPLE;

    // close refresh match (less than 1 hz diff to match all 23.xxx modes to 24p)
    if (fabs(candidate->m_refreshRate - matchInfo.m_refreshRate) <= 0.5)
    {
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_REFRESH_RATE_CLOSE;
    }

    // approx multiple refresh rate
    if (isRateMultipleOf(matchInfo.m_refreshRate, candidate->m_refreshRate, false))
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_REFRESH_RATE_MULTIPLE_CLOSE;

    // weight interlacing
    if (candidate->m_interlaced == matchInfo.m_interlaced)
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_INTERLACE;

    if (candidate->m_id == currentVideoMode->m_id)
      weights[candidate->m_id]->m_weight += MATCH_WEIGHT_CURRENT;

    modeit++;
  }

  // now grab the mode with the highest weight
  DMVideoModeWeightPtr chosen;
  float maxWeight = 0;

  DMVideoModeWeightMap::const_iterator weightit = weights.constBegin();
  while (weightit != weights.constEnd())
  {
    QLOG_DEBUG() << "Mode " << weightit.value()->m_mode->m_id << "("
                 << weightit.value()->m_mode->getPrettyName() << ") has weight "
                 << weightit.value()->m_weight;
    if (weightit.value()->m_weight > maxWeight)
    {
      chosen = weightit.value();
      maxWeight = chosen->m_weight;
    }

    weightit++;
  }

  if ((chosen) && (chosen->m_weight > MATCH_WEIGHT_RES))
  {
    QLOG_INFO() << "DisplayManager RefreshMatch : found a suitable mode : "
                << chosen->m_mode->getPrettyName();
    return chosen->m_mode->m_id;
  }

  QLOG_INFO() << "DisplayManager RefreshMatch : found no suitable videomode";
  return -1;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManager::findBestMode(int display)
{
  int bestMode = -1;

  for(auto mode : m_displays[display]->m_videoModes)
  {
    if (bestMode < 0)
    {
      bestMode = mode->m_id;
    }
    else
    {
      DMVideoModePtr best = m_displays[display]->m_videoModes[bestMode];
      DMVideoModePtr candidate = mode;

      // Highest priority: prefer non-interlaced modes.
      if (!best->m_interlaced && candidate->m_interlaced)
        continue;

      if (best->m_bitsPerPixel > candidate->m_bitsPerPixel)
        continue;

      if (best->m_width > candidate->m_width)
        continue;

      if (best->m_height > candidate->m_height)
        continue;

      if (best->m_refreshRate > candidate->m_refreshRate)
        continue;

      bestMode = candidate->m_id;
    }
  }

  return bestMode;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
int DisplayManager::getDisplayFromPoint(const QPoint& pt)
{
  return getDisplayFromPoint(pt.x(), pt.y());
}
