#include "monitor.hpp"

#ifdef _MSC_VER

    #include <algorithm>

    #include <cmath>

    #include <QtDebug>
    #include <QtGlobal>

    #include <highlevelmonitorconfigurationapi.h>

    #include "get_last_error.hpp"
    #include "log_tr.hpp"
    #include "utils.hpp"

Monitor::Monitor(Monitor&& other) noexcept
    : m_hMonitor(other.m_hMonitor)
    , m_physicalMonitor(other.m_physicalMonitor)
    , m_brightnessRange(other.m_brightnessRange)
    , m_errorOccurred(other.m_errorOccurred)
{
    other.m_hMonitor = 0;
    other.m_physicalMonitor.name.clear();
    other.m_physicalMonitor.handle = 0;
}

Monitor::~Monitor()
{
    qDebug(ltr("Destroying monitor '%1' with handle '0x%2' and physical handle '0x%3'")
               .arg(m_physicalMonitor.name)
               .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
               .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16));

    if(m_physicalMonitor.handle && !DestroyPhysicalMonitor(m_physicalMonitor.handle)) {
        qCritical(ltr("Error occurred while destroying monitor '%1' with handle '0x%2' and physical handle '0x%3', error: %4")
                      .arg(m_physicalMonitor.name)
                      .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
                      .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16)
                      .arg(getLastErrorString()));
    }
}

bool Monitor::setBrightness(float percentage)
{
    m_errorOccurred = true;

    qDebug(ltr("Setting brightness of monitor '%1' with handle '0x%2' and physical handle '0x%3' to %4")
               .arg(m_physicalMonitor.name)
               .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
               .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16)
               .arg(percentage));

    if(!m_brightnessRange) {
        qDebug(ltr("No brightness range known for monitor '%1' with handle '0x%2' and physical handle '0x%3'")
                   .arg(m_physicalMonitor.name)
                   .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
                   .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16));

        if(std::isnan(getBrightness()))
            return false;
    }

    const auto mappedBrightness = utils::map(percentage, 0.f, 1.f, m_brightnessRange->first, m_brightnessRange->second);
    const auto newBrightness = std::clamp<DWORD>(std::round(mappedBrightness), m_brightnessRange->first, m_brightnessRange->second);

    if(!SetMonitorBrightness(m_physicalMonitor.handle, newBrightness)) {
        qCritical(ltr("Failed to set brightness of monitor '%1' with handle '0x%2' and physical handle '0x%3', error: %4")
                      .arg(m_physicalMonitor.name)
                      .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
                      .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16)
                      .arg(getLastErrorString()));
        return false;
    }

    qDebug(ltr("Set brightness to %4").arg(newBrightness));

    m_errorOccurred = false;

    return true;
}

float Monitor::getBrightness()
{
    m_errorOccurred = true;

    qDebug(ltr("Getting brightness of monitor '%1' with handle '0x%2' and physical handle '0x%3'")
               .arg(m_physicalMonitor.name)
               .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
               .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16));

    DWORD minBrightness = 0;
    DWORD brightness = 0;
    DWORD maxBrightness = 0;

    if(!GetMonitorBrightness(m_physicalMonitor.handle, &minBrightness, &brightness, &maxBrightness)) {
        qCritical(ltr("Failed to get brightness of monitor '%1' with handle '0x%2' and physical handle '0x%3', error: %4")
                      .arg(m_physicalMonitor.name)
                      .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
                      .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16)
                      .arg(getLastErrorString()));
        return NAN;
    }

    qDebug(ltr("Got brightness %1 in range [%2, %3]").arg(brightness).arg(minBrightness).arg(maxBrightness));

    m_brightnessRange = std::pair{minBrightness, maxBrightness};
    m_errorOccurred = false;

    return std::clamp(utils::map(brightness, minBrightness, maxBrightness, 0.f, 1.f), 0.f, 1.f);
}

Monitor::operator bool() const
{
    return !m_errorOccurred;
}

Monitor::Monitor(HMONITOR hMonitor, PHYSICAL_MONITOR physicalMonitor)
    : m_hMonitor(hMonitor)
    , m_physicalMonitor(physicalMonitor.hPhysicalMonitor, physicalMonitor.szPhysicalMonitorDescription)
{
    qDebug(ltr("Creating monitor '%1' with handle '0x%2' and physical handle '0x%3'")
               .arg(m_physicalMonitor.name)
               .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
               .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16));
}

bool Monitor::hasBrightnessCapability() const
{
    qDebug(ltr("Getting capabilities of monitor '%1' with handle '0x%2' and physical handle '0x%3'")
               .arg(m_physicalMonitor.name)
               .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
               .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16));

    DWORD monitorCapabilities = 0;
    DWORD colorTemperatures = 0;

    if(!GetMonitorCapabilities(m_physicalMonitor.handle, &monitorCapabilities, &colorTemperatures)) {
        qCritical(ltr("Failed to get capabilities of monitor '%1' with handle '0x%2' and physical handle '0x%3', error: %4")
                      .arg(m_physicalMonitor.name)
                      .arg(reinterpret_cast<qulonglong>(m_hMonitor), 0, 16)
                      .arg(reinterpret_cast<qulonglong>(m_physicalMonitor.handle), 0, 16)
                      .arg(getLastErrorString()));
        return false;
    }

    return monitorCapabilities & MC_CAPS_BRIGHTNESS;
}

#endif
