#include "main_window.hpp"

#include <algorithm>
#include <numeric>

#include <cmath>

#include <QCloseEvent>
#include <QGroupBox>
#include <QtDebug>
#include <QtGlobal>

#include "log_tr.hpp"
#include "monitor_control.hpp"
#include "sensor_driver.hpp"
#include "utils.hpp"

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
    qDebug(ltr("Creating main window"));

    m_ui.setupUi(this);
    m_manualBrightnessSlider = m_ui.centralWidget->findChild<QGroupBox*>("groupBox")->findChild<QSlider*>("horizontalSlider");

    if(m_manualBrightnessSlider) {
        connect(m_manualBrightnessSlider, &QSlider::valueChanged, [this](int value) {
            const auto mappedBrightness = utils::map(value, 0, 100, 0.f, 1.f);
            const auto newBrightness = std::clamp(mappedBrightness, 0.f, 1.f);
            qDebug(ltr("Overriding brightness with %1").arg(newBrightness));
            m_brightness = newBrightness;
        });
    }

    loadIcons();

    if(!QSystemTrayIcon::isSystemTrayAvailable()) {
        qWarning(ltr("System tray is not available -> tray icon will be disabled"));
    }
    else {
        qDebug(ltr("System tray is available"));

        m_trayIconMenu = new QMenu(this);
        m_showHideAction = m_trayIconMenu->addAction(tr("Show"), [this]() {
            if(m_visibility) {
                m_showHideAction->setText(tr("Show"));
                hide();
            }
            else {
                m_showHideAction->setText(tr("Hide"));
                show();
            }
            m_visibility = !m_visibility;
        });
        m_trayIconMenu->addSeparator();
        m_trayIconMenu->addAction(tr("Quit"), this, &QCoreApplication::quit);

        qDebug(ltr("Tray icon context menu initialized"));

        m_trayIcon = new QSystemTrayIcon(this);
        m_trayIcon->setIcon(*m_errorIcon);
        m_trayIcon->setContextMenu(m_trayIconMenu);
        m_trayIcon->show();
        m_trayIcon->setToolTip(tr("AdaptiveBrightness"));
        connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconClicked);

        qDebug(ltr("Tray icon initialized"));
    }

    m_timerThread = new QThread(this);
    m_timer = new QTimer(nullptr);
    m_timer->setInterval(1000);
    m_timer->moveToThread(m_timerThread);
    m_timerThread->connect(m_timer, &QTimer::timeout, this, &MainWindow::updateState, Qt::DirectConnection);
    m_timer->connect(m_timerThread, SIGNAL(started()), SLOT(start()));
    m_timerThread->start();

    qDebug(ltr("Tray icon update timer started"));

    m_sensors = enumerateSensors();
    m_monitors = enumerateMonitors();
}

MainWindow::~MainWindow()
{
    qDebug(ltr("Destroying main window"));

    m_timerThread->quit();
    if(!m_timerThread->wait()) {
        qCritical(ltr("Timer thread did not terminate cleanly"));
    }
}

void MainWindow::closeEvent(QCloseEvent* event)
{
    m_showHideAction->activate(QAction::Trigger);
    event->ignore();
}

void MainWindow::loadIcons()
{
    qDebug(ltr("Loading icons"));

    constexpr auto NO_SENSOR_ICON_PATHS = std::array{
        ":/AdaptiveBrightness/resources/nosense_0.ico", ":/AdaptiveBrightness/resources/nosense_1.ico", ":/AdaptiveBrightness/resources/nosense_2.ico",
        ":/AdaptiveBrightness/resources/nosense_3.ico", ":/AdaptiveBrightness/resources/nosense_4.ico", ":/AdaptiveBrightness/resources/nosense_5.ico",
        ":/AdaptiveBrightness/resources/nosense_6.ico", ":/AdaptiveBrightness/resources/nosense_7.ico", ":/AdaptiveBrightness/resources/nosense_8.ico",
    };

    static_assert(NO_SENSOR_ICON_PATHS.size() == utils::array_size_v<decltype(m_noSensorStateIcons)>, "Number of paths does not match number of icons");

    constexpr auto SENSOR_ICON_PATHS = std::array{
        ":/AdaptiveBrightness/resources/sense_0.ico", ":/AdaptiveBrightness/resources/sense_1.ico", ":/AdaptiveBrightness/resources/sense_2.ico",
        ":/AdaptiveBrightness/resources/sense_3.ico", ":/AdaptiveBrightness/resources/sense_4.ico", ":/AdaptiveBrightness/resources/sense_5.ico",
        ":/AdaptiveBrightness/resources/sense_6.ico", ":/AdaptiveBrightness/resources/sense_7.ico", ":/AdaptiveBrightness/resources/sense_8.ico",
    };

    static_assert(SENSOR_ICON_PATHS.size() == utils::array_size_v<decltype(m_sensorStateIcons)>, "Number of paths does not match number of icons");

    constexpr auto ERROR_ICON_PATH = ":/AdaptiveBrightness/resources/error.ico";

    for(std::size_t i = 0; i < NO_SENSOR_ICON_PATHS.size(); ++i) {
        m_noSensorStateIcons[i] = std::make_unique<QIcon>(NO_SENSOR_ICON_PATHS[i]);
    }

    for(std::size_t i = 0; i < SENSOR_ICON_PATHS.size(); ++i) {
        m_sensorStateIcons[i] = std::make_unique<QIcon>(SENSOR_ICON_PATHS[i]);
    }

    m_errorIcon = std::make_unique<QIcon>(ERROR_ICON_PATH);

    qDebug(ltr("Icons loaded"));
}

void MainWindow::updateState()
{
    qDebug(ltr("Updating state"));

    bool sensorError = false;
    bool monitorError = false;
    size_t iconBrightness = 0;

    const auto getAverageSensorValue = [this](float& brightness) {
        if(m_sensors.empty()) {
            return false;
        }

        std::vector<float> values;

        for(auto& sensor: m_sensors) {
            const auto sensorValues = sensor.readValues();
            if(!sensor) {
                return false;
            }
            values.insert(values.end(), sensorValues.begin(), sensorValues.end());
        }

        brightness = std::accumulate(values.begin(), values.end(), 0.f) / values.size();
        return true;
    };

    const auto setAllMonitorsBrightness = [this](float& brightness) {
        if(m_monitors.empty()) {
            return false;
        }

        bool errorOccurred = false;
        for(auto& monitor: m_monitors) {
            if(!monitor.setBrightness(brightness)) {
                errorOccurred = true;
            }
        }
        return !errorOccurred;
    };

    if(!getAverageSensorValue(m_brightness)) {
        m_sensors = enumerateSensors();
        qInfo(ltr("At least one sensor became invalid, re-enumerating sensors found %1 valid sensor(s)").arg(m_sensors.size()));
        sensorError = true;
    }

    const auto roundedBrightness = std::round(utils::map(m_brightness, 0.f, 1.f, 0, 8));
    iconBrightness = std::clamp<size_t>(roundedBrightness, 0, 8);

    if(!setAllMonitorsBrightness(m_brightness)) {
        m_monitors = enumerateMonitors();
        qInfo(ltr("At least one monitor became invalid, re-enumerating monitors found %1 valid monitor(s)").arg(m_monitors.size()));
        monitorError = true;
    }

    if(!sensorError && !monitorError && m_trayIcon) {
        m_trayIcon->setIcon(*m_sensorStateIcons[iconBrightness]);
    }
    else if(sensorError && !monitorError && m_trayIcon) {
        m_trayIcon->setIcon(*m_noSensorStateIcons[iconBrightness]);
    }
    else if(m_trayIcon) {
        m_trayIcon->setIcon(*m_errorIcon);
    }

    if(m_trayIcon) {
        m_trayIcon->setToolTip(tr("AdaptiveBrightness - %1%").arg(m_brightness * 100, 0, 'f', 0));
    }

    if(m_manualBrightnessSlider) {
        m_manualBrightnessSlider->setValue(utils::map(m_brightness, 0.f, 1.f, 0, 100));
    }
}

void MainWindow::trayIconClicked(QSystemTrayIcon::ActivationReason reason)
{
    if(reason == QSystemTrayIcon::ActivationReason::Trigger) {
        m_showHideAction->trigger();
    }
}
