// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: %{CURRENT_YEAR} %{AUTHOR} <%{EMAIL}>

#pragma once

#include <QObject>
#include <QQmlEngine>

#include <deque>
#include <iostream>
#include <print>

class QQuickWindow;
class ColorManagementGlobal;
class ColorManagementFeedback;
class ColorManagementSurface;

#include <QWaylandClientExtension>

#include <qpa/qplatformwindow_p.h>
#include <qwayland-color-management-v1.h>

class ColorManagementGlobal : public QWaylandClientExtensionTemplate<ColorManagementGlobal>, public QtWayland::wp_color_manager_v1
{
public:
    explicit ColorManagementGlobal()
        : QWaylandClientExtensionTemplate<ColorManagementGlobal>(1)
    {
        initialize();
        connect(this, &ColorManagementGlobal::activeChanged, this, [this]() {
            if (!isActive()) {
                wp_color_manager_v1_destroy(object());
            }
        });
    }

    void wp_color_manager_v1_supported_feature(uint32_t feature) override
    {
        // a well behaved program would check supported features before using them
    }
};

class ImageDescriptionInfo : public QObject, public QtWayland::wp_image_description_info_v1
{
    Q_OBJECT
public:
    explicit ImageDescriptionInfo(::wp_image_description_info_v1 *info)
        : QtWayland::wp_image_description_info_v1(info)
    {
    }

    ~ImageDescriptionInfo()
    {
        wp_image_description_info_v1_destroy(object());
    }

    Q_SIGNAL void done();
    void wp_image_description_info_v1_done() override
    {
        std::string_view tfName;
        switch (m_tf) {
        case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22:
            tfName = "gamma 2.2 (sRGB)";
            break;
        case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ:
            tfName = "PQ";
            break;
        default:
            tfName = "unknown";
        }
        const auto str = std::format(R"(
Primaries:
  r: {:.3f},{:.3f}
  g: {:.3f},{:.3f}
  b: {:.3f},{:.3f}
  w: {:.3f},{:.3f}
Transfer function: {}, [{:.2f}, {:.2f}] nits
Reference luminance: {}nits
Target luminance: [{:.2f}, {:.2f}] nits
        )",
                                     m_containerRed.x(),
                                     m_containerRed.y(),
                                     m_containerGreen.x(),
                                     m_containerGreen.y(),
                                     m_containerBlue.x(),
                                     m_containerBlue.y(),
                                     m_containerWhite.x(),
                                     m_containerWhite.y(),
                                     tfName,
                                     m_minLuminance,
                                     m_maxLuminance,
                                     m_referenceLuminance,
                                     m_targetMinLuminance,
                                     m_targetMaxLuminance);
        m_description = QString::fromStdString(str);
        Q_EMIT done();
    }
    void wp_image_description_info_v1_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override
    {
        m_containerRed = QPointF(r_x, r_y) / 1'000'000.0;
        m_containerGreen = QPointF(g_x, g_y) / 1'000'000.0;
        m_containerBlue = QPointF(b_x, b_y) / 1'000'000.0;
        m_containerWhite = QPointF(w_x, w_y) / 1'000'000.0;
    }
    void wp_image_description_info_v1_primaries_named(uint32_t primaries) override
    {
    }
    void wp_image_description_info_v1_tf_power(uint32_t eexp) override
    {
    }
    void wp_image_description_info_v1_tf_named(uint32_t tf) override
    {
        m_tf = tf;
    }
    void wp_image_description_info_v1_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override
    {
        m_minLuminance = min_lum / 10'000.0;
        m_maxLuminance = max_lum;
        m_referenceLuminance = reference_lum;
    }
    void wp_image_description_info_v1_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y)
        override
    {
        m_targetRed = QPointF(r_x, r_y) / 1'000'000.0;
        m_targetGreen = QPointF(g_x, g_y) / 1'000'000.0;
        m_targetBlue = QPointF(b_x, b_y) / 1'000'000.0;
        m_targetWhite = QPointF(w_x, w_y) / 1'000'000.0;
    }
    void wp_image_description_info_v1_target_luminance(uint32_t min_lum, uint32_t max_lum) override
    {
        m_targetMinLuminance = min_lum / 10'000.0;
        m_targetMaxLuminance = max_lum;
    }
    void wp_image_description_info_v1_target_max_cll(uint32_t max_cll) override
    {
    }
    void wp_image_description_info_v1_target_max_fall(uint32_t max_fall) override
    {
    }

    QString m_description;
    uint32_t m_tf = 0;
    QPointF m_containerRed;
    QPointF m_containerGreen;
    QPointF m_containerBlue;
    QPointF m_containerWhite;
    QPointF m_targetRed;
    QPointF m_targetGreen;
    QPointF m_targetBlue;
    QPointF m_targetWhite;
    double m_minLuminance;
    double m_maxLuminance;
    double m_referenceLuminance;
    double m_targetMinLuminance;
    double m_targetMaxLuminance;
};

class CompositorImageDescription : public QObject, public QtWayland::wp_image_description_v1
{
    Q_OBJECT
public:
    explicit CompositorImageDescription(::wp_image_description_v1 *descr)
        : QtWayland::wp_image_description_v1(descr)
        , info(wp_image_description_v1_get_information(descr))
    {
        connect(&info, &ImageDescriptionInfo::done, this, &CompositorImageDescription::done);
    }

    ~CompositorImageDescription()
    {
        wp_image_description_v1_destroy(object());
    }

    Q_SIGNAL void done();

    ImageDescriptionInfo info;
};

class PendingImageDescription : public QtWayland::wp_image_description_v1
{
public:
    explicit PendingImageDescription(QQuickWindow *window,
                                     ColorManagementSurface *surface,
                                     ::wp_image_description_v1 *descr,
                                     uint32_t renderIntent = WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
    ~PendingImageDescription();

    void wp_image_description_v1_ready(uint32_t identity) override;

private:
    QPointer<QQuickWindow> m_window;
    QPointer<ColorManagementSurface> m_surface;
    uint32_t m_renderIntent;
};

class ColorManagementFeedback : public QObject, public QtWayland::wp_color_management_surface_feedback_v1
{
    Q_OBJECT
public:
    explicit ColorManagementFeedback(::wp_color_management_surface_feedback_v1 *obj)
        : QtWayland::wp_color_management_surface_feedback_v1(obj)
        , m_preferred(std::make_unique<CompositorImageDescription>(get_preferred()))
    {
    }

    ~ColorManagementFeedback()
    {
        wp_color_management_surface_feedback_v1_destroy(object());
    }

    Q_SIGNAL void preferredChanged();
    void wp_color_management_surface_feedback_v1_preferred_changed(uint32_t identity) override
    {
        if (m_preferred) {
            auto newPreferred = std::make_unique<CompositorImageDescription>(get_preferred());
            connect(newPreferred.get(), &CompositorImageDescription::done, this, &ColorManagementFeedback::handlePending);
            connect(newPreferred.get(), &CompositorImageDescription::done, this, &ColorManagementFeedback::preferredChanged);
            m_pending.push_back(std::move(newPreferred));
        } else {
            m_preferred = std::make_unique<CompositorImageDescription>(get_preferred());
            connect(m_preferred.get(), &CompositorImageDescription::done, this, &ColorManagementFeedback::preferredChanged);
        }
    }

    std::unique_ptr<CompositorImageDescription> m_preferred;

private:
    void handlePending()
    {
        m_preferred = std::move(m_pending.front());
        m_pending.pop_front();
    }
    std::deque<std::unique_ptr<CompositorImageDescription>> m_pending;
};

class ColorManagementSurface : public QObject, public QtWayland::wp_color_management_surface_v1
{
    Q_OBJECT
public:
    explicit ColorManagementSurface(ColorManagementGlobal *global,
                                    QQuickWindow *window,
                                    ::wp_color_management_surface_v1 *obj,
                                    std::unique_ptr<ColorManagementFeedback> &&feedback)
        : QtWayland::wp_color_management_surface_v1(obj)
        , m_global(global)
        , m_window(window)
        , m_feedback(std::move(feedback))
    {
    }

    ~ColorManagementSurface()
    {
        wp_color_management_surface_v1_destroy(object());
    }

    void setImageDescriptionMode(int mode);
    void setPQ(int referenceLuminance);

    ColorManagementGlobal *m_global;
    QQuickWindow *m_window;
    std::unique_ptr<ColorManagementFeedback> m_feedback;
};

class App : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    QML_SINGLETON

    Q_PROPERTY(QString preferredDescription READ preferredDescription NOTIFY preferredChanged)
public:
    explicit App();
    ~App() override;

    Q_INVOKABLE void setMainWindow(QQuickWindow *window);
    Q_INVOKABLE void setPQ(QQuickWindow *window, int referenceLuminance);
    Q_INVOKABLE void setImageDescription(QQuickWindow *window, int mode);

    QString preferredDescription() const;
    Q_SIGNAL void preferredChanged();

private:
    void doSetImageDescription(QQuickWindow *window);
    bool eventFilter(QObject *watched, QEvent *event) override;

    const std::unique_ptr<ColorManagementGlobal> m_global;
    struct WindowData {
        std::optional<int> mode;
        int referenceLuminance;
    };
    std::unordered_map<QQuickWindow *, WindowData> m_data;
    std::unordered_map<QQuickWindow *, std::unique_ptr<ColorManagementSurface>> m_surfaces;
    QQuickWindow *m_mainWindow = nullptr;
};
