/*
 * Copyright (C) 2026, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 *
 *  Authors: iaom <zhangpengfei@kylinos.cn>
 */

#include "kywc-output-manager.h"

#include "wayland-pointer_p.h"
#include "wayland-kywc-output-v1-client-protocol.h"
#include <QDebug>
#include <QSize>
#include <QPoint>

namespace UkuiQuick::WaylandClient {

class KywcOutputMode::Private
{
public:
    Private(KywcOutputMode *q);

    WaylandPointer<kywc_output_mode_v1, kywc_output_mode_v1_destroy> mode;
    KywcOutputMode *q;
    QSize m_size;
    int m_refreshRate = 0;
    bool m_preferred = false;
    void init_listener(kywc_output_mode_v1 *mode);

    static struct kywc_output_mode_v1_listener s_modeListener;
    static void sizeCallback(void *data, kywc_output_mode_v1 *mode, int32_t width, int32_t height);
    static void refreshCallback(void *data, kywc_output_mode_v1 *mode, int32_t refresh);
    static void preferredCallback(void *data, kywc_output_mode_v1 *mode);
    static void finishedCallback(void *data, kywc_output_mode_v1 *mode);
};

kywc_output_mode_v1_listener KywcOutputMode::Private::s_modeListener = {
    .size = sizeCallback,
    .refresh = refreshCallback,
    .preferred = preferredCallback,
    .finished = finishedCallback
};

class KywcOutputManager::Private
{
public:
    Private(KywcOutputManager *q);

    WaylandPointer<kywc_output_manager_v1, kywc_output_manager_v1_destroy> manager;
    EventQueue *queue = nullptr;
    KywcOutputManager *q;
    QList<KywcOutput *> outputs;
    KywcOutput *primaryOutput = nullptr;
    void init_listener();

    static struct kywc_output_manager_v1_listener s_managerListener;
    static void outputCallback(void *data, kywc_output_manager_v1 *manager, kywc_output_v1 *output, const char *uuid);
    static void primaryCallback(void *data, kywc_output_manager_v1 *manager, kywc_output_v1 *output);
    static void doneCallback(void *data, kywc_output_manager_v1 *manager);
    static void finishedCallback(void *data, kywc_output_manager_v1 *manager);
};

kywc_output_manager_v1_listener KywcOutputManager::Private::s_managerListener = {
    .output = outputCallback,
    .primary = primaryCallback,
    .done = doneCallback,
    .finished = finishedCallback
};

class KywcOutput::Private
{
public:
    Private(KywcOutput *q);

    WaylandPointer<kywc_output_v1, kywc_output_v1_destroy> output;
    KywcOutput *q;
    QString m_name;
    QString m_make;
    QString m_model;
    QString m_serialNumber;
    QString m_description;
    QSize m_physicalSize;
    uint32_t m_capabilities = 0;
    bool m_enabled = false;
    QPoint m_position;
    int m_transform = 0;
    qreal m_scale = 1.0;
    uint32_t m_power = 0;
    uint32_t m_brightness = 0;
    uint32_t m_colorTemp = 0;
    QString m_uuid;
    QList<KywcOutputMode *> modes;
    KywcOutputMode *currentMode = nullptr;
    void init_listener(kywc_output_v1 *output);

    static struct kywc_output_v1_listener s_outputListener;
    static void nameCallback(void *data, kywc_output_v1 *output, const char *name);
    static void makeCallback(void *data, kywc_output_v1 *output, const char *make);
    static void modelCallback(void *data, kywc_output_v1 *output, const char *model);
    static void serialNumberCallback(void *data, kywc_output_v1 *output, const char *serial);
    static void descriptionCallback(void *data, kywc_output_v1 *output, const char *description);
    static void physicalSizeCallback(void *data, kywc_output_v1 *output, int32_t width, int32_t height);
    static void capabilitiesCallback(void *data, kywc_output_v1 *output, uint32_t flags);
    static void enabledCallback(void *data, kywc_output_v1 *output, int32_t enabled);
    static void modeCallback(void *data, kywc_output_v1 *output, kywc_output_mode_v1 *mode);
    static void currentModeCallback(void *data, kywc_output_v1 *output, kywc_output_mode_v1 *mode);
    static void positionCallback(void *data, kywc_output_v1 *output, int32_t x, int32_t y);
    static void transformCallback(void *data, kywc_output_v1 *output, int32_t transform);
    static void scaleCallback(void *data, kywc_output_v1 *output, wl_fixed_t scale);
    static void powerCallback(void *data, kywc_output_v1 *output, uint32_t power);
    static void brightnessCallback(void *data, kywc_output_v1 *output, uint32_t brightness);
    static void colorTempCallback(void *data, kywc_output_v1 *output, uint32_t colorTemp);
    static void finishedCallback(void *data, kywc_output_v1 *output);
};

kywc_output_v1_listener KywcOutput::Private::s_outputListener = {
    .name = nameCallback,
    .make = makeCallback,
    .model = modelCallback,
    .serial_number = serialNumberCallback,
    .description = descriptionCallback,
    .physical_size = physicalSizeCallback,
    .mode = modeCallback,
    .capabilities = capabilitiesCallback,
    .enabled = enabledCallback,
    .current_mode = currentModeCallback,
    .position = positionCallback,
    .transform = transformCallback,
    .scale = scaleCallback,
    .power = powerCallback,
    .brightness = brightnessCallback,
    .color_temp = colorTempCallback,
    .finished = finishedCallback
};

KywcOutputMode::Private::Private(KywcOutputMode *q) : q(q)
{
}

KywcOutputMode::KywcOutputMode(QObject *parent) : QObject(parent), d(new Private(this))
{
}

KywcOutputMode::~KywcOutputMode()
{
    release();
}

void KywcOutputMode::setup(kywc_output_mode_v1 *mode)
{
    Q_ASSERT(!d->mode);
    Q_ASSERT(mode);
    d->mode.setup(mode);
    d->init_listener(mode);
}

void KywcOutputMode::release()
{
    d->mode.release();
}

void KywcOutputMode::destroy()
{
    d->mode.destroy();
}

bool KywcOutputMode::isValid() const
{
    return d->mode.isValid();
}

QSize KywcOutputMode::size() const
{
    return d->m_size;
}

int KywcOutputMode::refreshRate() const
{
    return d->m_refreshRate;
}

bool KywcOutputMode::isPreferred() const
{
    return d->m_preferred;
}

void KywcOutputMode::Private::init_listener(kywc_output_mode_v1 *mode)
{
    kywc_output_mode_v1_add_listener(mode, &s_modeListener, this);
}

void KywcOutputMode::Private::sizeCallback(void *data, kywc_output_mode_v1 *mode, int32_t width, int32_t height)
{
    auto p = reinterpret_cast<KywcOutputMode::Private *>(data);
    if (p->mode != mode) {
        return;
    }
    p->m_size = QSize(width, height);
    Q_EMIT p->q->sizeChanged(p->m_size);
}

void KywcOutputMode::Private::refreshCallback(void *data, kywc_output_mode_v1 *mode, int32_t refresh)
{
    auto p = reinterpret_cast<KywcOutputMode::Private *>(data);
    if (p->mode != mode) {
        return;
    }
    p->m_refreshRate = refresh;
    Q_EMIT p->q->refreshRateChanged(p->m_refreshRate);
}

void KywcOutputMode::Private::preferredCallback(void *data, kywc_output_mode_v1 *mode)
{
    auto p = reinterpret_cast<KywcOutputMode::Private *>(data);
    if (p->mode != mode) {
        return;
    }
    p->m_preferred = true;
    Q_EMIT p->q->preferredChanged(true);
}

void KywcOutputMode::Private::finishedCallback(void *data, kywc_output_mode_v1 *mode)
{
    auto p = reinterpret_cast<KywcOutputMode::Private *>(data);
    if (p->mode != mode) {
        return;
    }
    Q_EMIT p->q->finished();
}

KywcOutputManager::Private::Private(KywcOutputManager *q) : q(q)
{
}

KywcOutputManager::KywcOutputManager(QObject *parent) : QObject(parent), d(new Private(this))
{
}

KywcOutputManager::~KywcOutputManager()
{
    release();
}

bool KywcOutputManager::isValid() const
{
    return d->manager.isValid();
}

void KywcOutputManager::release()
{
    if (!d->manager) {
        return;
    }
    Q_EMIT interfaceAboutToBeReleased();
    d->manager.release();
}

void KywcOutputManager::destroy()
{
    if (!d->manager) {
        return;
    }
    Q_EMIT interfaceAboutToBeDestroyed();
    d->manager.destroy();
}

void KywcOutputManager::setup(kywc_output_manager_v1 *manager)
{
    Q_ASSERT(!d->manager);
    Q_ASSERT(manager);
    d->manager.setup(manager);
    d->init_listener();
}

void KywcOutputManager::setEventQueue(EventQueue *queue)
{
    d->queue = queue;
}

EventQueue *KywcOutputManager::eventQueue() const
{
    return d->queue;
}

void KywcOutputManager::stop()
{
    if (!d->manager) {
        return;
    }
    kywc_output_manager_v1_stop(d->manager);
}

QList<KywcOutput*> KywcOutputManager::outputs() const
{
    return d->outputs;
}

KywcOutput* KywcOutputManager::primaryOutput() const
{
    return d->primaryOutput;
}

void KywcOutputManager::Private::init_listener()
{
    kywc_output_manager_v1_add_listener(manager, &s_managerListener, this);
}

void KywcOutputManager::Private::outputCallback(void *data, kywc_output_manager_v1 *manager, kywc_output_v1 *output, const char *uuid)
{
    auto p = reinterpret_cast<KywcOutputManager::Private *>(data);
    if (p->manager != manager) {
        return;
    }

    auto out = new KywcOutput(p->q);
    out->d->m_uuid = QString::fromUtf8(uuid);
    p->outputs.append(out);
    out->setup(output);

    Q_EMIT p->q->outputCreated(out, out->d->m_uuid);
}

void KywcOutputManager::Private::primaryCallback(void *data, kywc_output_manager_v1 *manager, kywc_output_v1 *output)
{
    auto p = reinterpret_cast<KywcOutputManager::Private *>(data);
    if (p->manager != manager) {
        return;
    }

    KywcOutput *primary = nullptr;
    for (auto out : p->outputs) {
        if (out->d->output == output) {
            primary = out;
            break;
        }
    }

    if (primary) {
        p->primaryOutput = primary;
        Q_EMIT p->q->primaryOutputChanged(primary);
    }
}

void KywcOutputManager::Private::doneCallback(void *data, kywc_output_manager_v1 *manager)
{
    Q_UNUSED(manager)
    auto p = reinterpret_cast<KywcOutputManager::Private *>(data);
    Q_EMIT p->q->done();
}

void KywcOutputManager::Private::finishedCallback(void *data, kywc_output_manager_v1 *manager)
{
    auto p = reinterpret_cast<KywcOutputManager::Private *>(data);
    if (p->manager != manager) {
        return;
    }
    Q_EMIT p->q->removed();
}

KywcOutput::Private::Private(KywcOutput *q) : q(q)
{
}

KywcOutput::KywcOutput(QObject *parent) : QObject(parent), d(new Private(this))
{
}

KywcOutput::~KywcOutput()
{
    release();
}

void KywcOutput::setup(kywc_output_v1 *output)
{
    Q_ASSERT(!d->output);
    Q_ASSERT(output);
    d->output.setup(output);
    d->init_listener(output);
}

void KywcOutput::release()
{
    d->output.release();
}

void KywcOutput::destroy()
{
    d->output.destroy();
}

bool KywcOutput::isValid() const
{
    return d->output.isValid();
}

QString KywcOutput::name() const
{
    return d->m_name;
}

QString KywcOutput::make() const
{
    return d->m_make;
}

QString KywcOutput::model() const
{
    return d->m_model;
}

QString KywcOutput::serialNumber() const
{
    return d->m_serialNumber;
}

QString KywcOutput::description() const
{
    return d->m_description;
}

QSize KywcOutput::physicalSize() const
{
    return d->m_physicalSize;
}

uint32_t KywcOutput::capabilities() const
{
    return d->m_capabilities;
}

bool KywcOutput::isEnabled() const
{
    return d->m_enabled;
}

QPoint KywcOutput::position() const
{
    return d->m_position;
}

int KywcOutput::transform() const
{
    return d->m_transform;
}

qreal KywcOutput::scale() const
{
    return d->m_scale;
}

uint32_t KywcOutput::power() const
{
    return d->m_power;
}

uint32_t KywcOutput::brightness() const
{
    return d->m_brightness;
}

uint32_t KywcOutput::colorTemp() const
{
    return d->m_colorTemp;
}

QString KywcOutput::uuid() const
{
    return d->m_uuid;
}

QList<KywcOutputMode*> KywcOutput::supportedModes() const
{
    return d->modes;
}

KywcOutputMode* KywcOutput::currentMode() const
{
    return d->currentMode;
}

void KywcOutput::Private::init_listener(kywc_output_v1 *output)
{
    kywc_output_v1_add_listener(output, &s_outputListener, this);
}

void KywcOutput::Private::nameCallback(void *data, kywc_output_v1 *output, const char *name)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_name = QString::fromUtf8(name);
    Q_EMIT p->q->nameChanged(p->m_name);
}

void KywcOutput::Private::makeCallback(void *data, kywc_output_v1 *output, const char *make)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_make = QString::fromUtf8(make);
    Q_EMIT p->q->makeChanged(p->m_make);
}

void KywcOutput::Private::modelCallback(void *data, kywc_output_v1 *output, const char *model)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_model = QString::fromUtf8(model);
    Q_EMIT p->q->modelChanged(p->m_model);
}

void KywcOutput::Private::serialNumberCallback(void *data, kywc_output_v1 *output, const char *serial)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_serialNumber = QString::fromUtf8(serial);
    Q_EMIT p->q->serialNumberChanged(p->m_serialNumber);
}

void KywcOutput::Private::descriptionCallback(void *data, kywc_output_v1 *output, const char *description)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_description = QString::fromUtf8(description);
    Q_EMIT p->q->descriptionChanged(p->m_description);
}

void KywcOutput::Private::physicalSizeCallback(void *data, kywc_output_v1 *output, int32_t width, int32_t height)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_physicalSize = QSize(width, height);
    Q_EMIT p->q->physicalSizeChanged(p->m_physicalSize);
}

void KywcOutput::Private::capabilitiesCallback(void *data, kywc_output_v1 *output, uint32_t flags)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_capabilities = flags;
    Q_EMIT p->q->capabilitiesChanged(p->m_capabilities);
}

void KywcOutput::Private::enabledCallback(void *data, kywc_output_v1 *output, int32_t enabled)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_enabled = (enabled != 0);
    Q_EMIT p->q->enabledChanged(p->m_enabled);
}

void KywcOutput::Private::modeCallback(void *data, kywc_output_v1 *output, kywc_output_mode_v1 *mode)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }

    auto modeObj = new KywcOutputMode(p->q);
    p->modes.append(modeObj);
    modeObj->setup(mode);
}

void KywcOutput::Private::currentModeCallback(void *data, kywc_output_v1 *output, kywc_output_mode_v1 *mode)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }

    KywcOutputMode *current = nullptr;
    for (auto m : p->modes) {
        if (m->d->mode == mode) {
            current = m;
            break;
        }
    }

    if (current) {
        p->currentMode = current;
    }
}

void KywcOutput::Private::positionCallback(void *data, kywc_output_v1 *output, int32_t x, int32_t y)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_position = QPoint(x, y);
    Q_EMIT p->q->positionChanged(p->m_position);
}

void KywcOutput::Private::transformCallback(void *data, kywc_output_v1 *output, int32_t transform)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_transform = transform;
    Q_EMIT p->q->transformChanged(p->m_transform);
}

void KywcOutput::Private::scaleCallback(void *data, kywc_output_v1 *output, wl_fixed_t scale)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_scale = wl_fixed_to_double(scale);
    Q_EMIT p->q->scaleChanged(p->m_scale);
}

void KywcOutput::Private::powerCallback(void *data, kywc_output_v1 *output, uint32_t power)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_power = power;
    Q_EMIT p->q->powerChanged(p->m_power);
}

void KywcOutput::Private::brightnessCallback(void *data, kywc_output_v1 *output, uint32_t brightness)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_brightness = brightness;
    Q_EMIT p->q->brightnessChanged(p->m_brightness);
}

void KywcOutput::Private::colorTempCallback(void *data, kywc_output_v1 *output, uint32_t colorTemp)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    p->m_colorTemp = colorTemp;
    Q_EMIT p->q->colorTempChanged(p->m_colorTemp);
}

void KywcOutput::Private::finishedCallback(void *data, kywc_output_v1 *output)
{
    auto p = reinterpret_cast<KywcOutput::Private *>(data);
    if (p->output != output) {
        return;
    }
    Q_EMIT p->q->finished();
}

}