// SPDX-FileCopyrightText: 2024 Aleix Pol Gonzalez <aleix.pol_gonzalez@mercedes-benz.com>
// SPDX-License-Identifier: MIT

#include "zonemanager.h"
#include "zoneitemattached.h"

#include <QGuiApplication>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
#include <QPlatformSurfaceEvent>
#include <qpa/qplatformnativeinterface.h>
#include <qwayland-wayland.h>

#include <kwinzonesclientlogging.h>

Q_GLOBAL_STATIC(ZoneManager, s_manager)

ZoneManager::ZoneManager()
    : QWaylandClientExtensionTemplate<ZoneManager>(1)
{
    initialize();
    connect(qGuiApp, &QGuiApplication::screenRemoved, this, [this] (QScreen *screen) {
        m_zones.remove(screen);
    });
}

ZoneZone *ZoneManager::fetchZone(QScreen *screen)
{
    ZoneZone *&ret = m_zones[screen];
    if (!ret) {
        auto output = (::wl_output *)QGuiApplication::platformNativeInterface()->nativeResourceForScreen("output", screen);
        Q_ASSERT(output);
        ret = new ZoneZone(s_manager->get_zone(output));
    }
    return ret;
}

bool ZoneManager::isActive()
{
    return s_manager->isInitialized();
}

ZoneItem::ZoneItem(QWindow *window)
    : QtWayland::xx_zone_item_v1()
    , m_window(window)
{
    Q_ASSERT(m_window);
    Q_ASSERT(m_window->isTopLevel());
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
    connect(window, &QWindow::visibilityChanged, this, &ZoneItem::manageSurface, Qt::QueuedConnection);
#else
    window->installEventFilter(this);
#endif
    if (window->isVisible()) {
        manageSurface();
    }
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
bool ZoneItem::eventFilter(QObject *watched, QEvent *event)
{
    auto window = qobject_cast<QWindow *>(watched);
    if (!window) {
        return false;
    }
    if (event->type() == QEvent::PlatformSurface) {
        auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
        Q_ASSERT(waylandWindow);
        connect(waylandWindow, &QNativeInterface::Private::QWaylandWindow::surfaceRoleCreated,
                this, &ZoneItem::manageSurface);
        window->removeEventFilter(this);
    }
    return false;
}
#endif

void ZoneItem::manageSurface()
{
    if (!m_window->isVisible()) {
        if (isInitialized()) {
            destroy();
        }
        return;
    }

    auto tl = (::xdg_toplevel *)QGuiApplication::platformNativeInterface()->nativeResourceForWindow("xdg_toplevel", m_window);
    if (!tl) {
        qCDebug(KWINZONES_CLIENT) << "No xdg_toplevel yet!" << m_window << tl;
        return;
    }

    ::xx_zone_item_v1 *item = s_manager->get_zone_item(tl);
    init(item);
    xx_zone_item_v1_set_user_data(item, (QtWayland::xx_zone_item_v1 *) this);
    Q_ASSERT(isInitialized());
    initZone();
}

void ZoneItem::initZone()
{
    Q_ASSERT(object());

    if (!m_zone) {
        setZone(s_manager->fetchZone(m_window->screen()));
        return;
    }

    m_zone->add_item(object());

    if (m_requestedPosition) {
        set_position(m_requestedPosition->x(), m_requestedPosition->y());
    }
}

void ZoneItem::setZone(ZoneZone* zone)
{
    if (m_zone == zone) {
        return;
    }
    if (m_zone && object()) {
        m_zone->remove_item(object());
    }
    m_zone = zone;
    if (m_zone && object()) {
        initZone();
    }
    Q_EMIT zoneChanged(m_zone);
}

ZoneZone *ZoneItem::zone()
{
    return m_zone;
}

void ZoneItem::requestPosition(const QPoint &point)
{
    m_requestedPosition = point;
    Q_EMIT requestedPositionChanged();
    if (!isInitialized()) {
        return;
    }

    qCDebug(KWINZONES_CLIENT) << "requesting in" << zone() << "geometry" << point;
    set_position(point.x(), point.y());
}

ZoneItemAttached* ZoneItem::get()
{
    if (!m_attached) {
        m_attached = new ZoneItemAttached(this);
    }
    return m_attached;
}

void ZoneItem::updatePosition(ZoneZone* zone, const QPoint& position)
{
    if (zone != m_zone) {
        qWarning() << "Something weird happened" << zone << m_zone << position;
    }
    m_pos = position;
    Q_EMIT positionChanged();
}

QPoint ZoneItem::position() const
{
    return m_pos;
}

ZoneZone::ZoneZone(::xx_zone_v1* zone)
    : QtWayland::xx_zone_v1(zone)
{
}

void ZoneZone::xx_zone_v1_item_entered(xx_zone_item_v1* item)
{
    if (!item) [[unlikely]] {
        qCDebug(KWINZONES_CLIENT) << "unknown item entered";
        return;
    }
    qCDebug(KWINZONES_CLIENT) << "item entered" << QtWayland::xx_zone_item_v1::fromObject(item) << item;
}
