#include "ukuiwaylandinterface.h"

#include <QDebug>
#include <QApplication>
#include "ukui-window-management-client-protocol.h"
#include "ukui-shell-client-protocol.h"
#include "wayland-xdg-activation-v1-client-protocol.h"
#include "wayland-ext-idle-notify-v1-client-protocol.h"

void handle_global(void *data,struct wl_registry *wl_registry,uint32_t name, const char *interface,  uint32_t version)
{
    auto waylandInterface = reinterpret_cast<UkuiWaylandInterface *>(data);
    Q_ASSERT(wl_registry == *waylandInterface->registry());
    waylandInterface->handleGlobal(data,wl_registry,name,interface,version);
}
void handle_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name)
{

}

UkuiWaylandInterface::UkuiWaylandInterface(QObject *parent)
    : AbstractInterface(parent)
{
    m_connection = KWayland::Client::ConnectionThread::fromApplication(qApp);
    m_registry = new Registry(this);
    m_registry->create(m_connection->display());

    connect(m_registry, &KWayland::Client::Registry::shellAnnounced, this, [=](){
        const auto interface = m_registry->interface(KWayland::Client::Registry::Interface::Shell);
        if (interface.name != 0) {
            m_shell = m_registry->createShell(interface.name, interface.version, this);
        }
    });

    struct wl_registry_listener registry_listener = {
        .global = handle_global,
        .global_remove = handle_global_remove
    };
    wl_registry* registry = *m_registry;
    wl_registry_add_listener(registry, &registry_listener, this);
    wl_display_roundtrip(m_connection->display());

    m_registry->setup();
    m_connection->roundtrip();
}

UkuiWaylandInterface::~UkuiWaylandInterface()
{

}

WindowInfo UkuiWaylandInterface::requestInfo(WindowId wid)
{
    WindowInfo windowInfo;

    auto w = windowFor(wid);

    if (w) {
        if (isPlasmaDesktop(w->geometry())) {
            windowInfo.setIsValid(true);
            windowInfo.setIsPlasmaDesktop(true);
            windowInfo.setWid(wid);
            //! Window Abilities
            windowInfo.setIsClosable(false);
            windowInfo.setIsFullScreenable(false);
            windowInfo.setIsGroupable(false);
            windowInfo.setIsMaximizable(false);
            windowInfo.setIsMinimizable(false);
            windowInfo.setIsMovable(false);
            windowInfo.setIsResizable(false);
            windowInfo.setIsShadeable(false);
            windowInfo.setIsVirtualDesktopsChangeable(false);
            //! Window Abilities

        }
        else {
            windowInfo.setIsValid(true);
            windowInfo.setWid(wid);
            windowInfo.setIsActive(w->isActive());
            windowInfo.setIsMinimized(w->isMinimized());
            windowInfo.setIsMaxVert(w->isMaximized());
            windowInfo.setIsMaxHoriz(w->isMaximized());
            windowInfo.setIsFullscreen(w->isFullscreen());
            windowInfo.setIsShaded(w->isShaded());
            windowInfo.setIsOnAllDesktops(w->isOnAllDesktops());
            windowInfo.setIsOnAllActivities(true);
            windowInfo.setHasSkipTaskbar(w->skipTaskbar());
            windowInfo.setHasSkipSwitcher(w->skipSwitcher());
            windowInfo.setIsKeepAbove(w->isKeepAbove());
            //! Window Abilities
            windowInfo.setIsClosable(w->isCloseable());
            windowInfo.setIsFullScreenable(w->isFullscreenable());
            windowInfo.setIsMaximizable(w->isMaximizeable());
            windowInfo.setIsMinimizable(w->isMinimizeable());
            windowInfo.setIsMovable(w->isMovable());
            windowInfo.setIsResizable(w->isResizable());
            windowInfo.setIsShadeable(w->isShadeable());
            windowInfo.setIsVirtualDesktopsChangeable(w->isVirtualDesktopChangeable());
        }
    }
    else
    {
        windowInfo.setIsValid(false);
    }

    return windowInfo;
}

void UkuiWaylandInterface::requestActivate(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestActivate();
        m_connection->roundtrip();
        emit windowChanged(w->uuid());
    }
}

void UkuiWaylandInterface::requestClose(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestClose();
        m_connection->roundtrip();
    }
}

void UkuiWaylandInterface::requestToggleKeepAbove(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestToggleKeepAbove();
        m_connection->roundtrip();
        emit windowChanged(w->uuid());
    }
}

void UkuiWaylandInterface::requestToggleMinimized(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestToggleMinimized();
        m_connection->roundtrip();
    }
}

void UkuiWaylandInterface::requestToggleMaximized(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestToggleMaximized();
        m_connection->roundtrip();
    }
}

QIcon UkuiWaylandInterface::iconFor(WindowId wid)
{
    auto window = windowFor(wid);

    if (window) {
        return window->icon();
    }

    return QIcon();
}

QString UkuiWaylandInterface::titleFor(WindowId wid)
{
    auto window = windowFor(wid);

    if (window) {
        return window->title();
    }
    return QString();
}

QString UkuiWaylandInterface::windowGroupFor(WindowId wid)
{
    auto window = windowFor(wid);
    if (window)
    {
        m_connection->roundtrip();
        return window->appId();
    }
    else
        return QString();
}

quint32 UkuiWaylandInterface::pid(WindowId wid)
{
    quint32 pid = 0;
    auto window = windowFor(wid);
    if (window)
    {
        m_connection->roundtrip();
        return window->pid();
    }
    else
        return pid;
}

void UkuiWaylandInterface::showCurrentDesktop()
{
    if(m_ukuiWindowManager)
    {
        m_ukuiWindowManager->showDesktop();
        m_connection->roundtrip();
    }
}

void UkuiWaylandInterface::hideCurrentDesktop()
{
    if(m_ukuiWindowManager)
    {
        m_ukuiWindowManager->hideDesktop();
        m_connection->roundtrip();
    }
}

bool UkuiWaylandInterface::windowCanBeDragged(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        WindowInfo winfo = requestInfo(wid);
        return (winfo.isValid()
                && w->isMovable()
                && !winfo.isMinimized()
                && inCurrentDesktopActivity(winfo)
                && !winfo.isPlasmaDesktop());
    }

    return false;
}

bool UkuiWaylandInterface::windowCanBeMaximized(WindowId wid)
{
    auto w = windowFor(wid);

    if (w) {
        WindowInfo winfo = requestInfo(wid);
        return (winfo.isValid()
                && w->isMaximizeable()
                && !winfo.isMinimized()
                && inCurrentDesktopActivity(winfo)
                && !winfo.isPlasmaDesktop());
    }

    return false;
}

WindowId UkuiWaylandInterface::activeWindow()
{
    if (!m_ukuiWindowManager) {
        return 0;
    }
    m_connection->roundtrip();
    auto wid = m_ukuiWindowManager->activeWindow();
    return wid ? QVariant(wid->uuid()) : "";
}
bool UkuiWaylandInterface::removeHeaderBar(QWindow *window)
{
    if(!window)
        return false;
    if (!m_ukuiShell)
        return false;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return false;
    if(!m_surfaces.contains(window))
    {
        m_surfaces.insert(window,surface);
    }

    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return false;
    if(!m_ukuiShellSurfaces.contains(window))
    {
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);
    }
    ukuiShellSurface->setSurfaceProperty(UkuiShellSurface::SurfaceProperty::NoTitleBar,1);

    window->installEventFilter(this);
    return true;
}

bool UkuiWaylandInterface::setWindowRadius(QWindow *window, int radius)
{
    if(!window || radius < 0)
        return false;

    if (!m_ukuiShell)
        return false;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return false;
    if(!m_surfaces.contains(window))
    {
        m_surfaces.insert(window,surface);
    }

    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return false;
    if(!m_ukuiShellSurfaces.contains(window))
    {
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);
    }
    ukuiShellSurface->setSurfaceProperty(UkuiShellSurface::SurfaceProperty::WindowRadius,radius);

    window->installEventFilter(this);
    return true;

}

void UkuiWaylandInterface::activateWindow(QWindow *window1, QWindow *window2)
{
    if(!window1 || !window2)
        return;

    if(!m_seat)
        return;
    if(!m_xdgActivation)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window1);
    if (!surface)
        return;
    if(!m_surfaces.contains(window1))
    {
        m_surfaces.insert(window1,surface);
    }
    XdgActivationToken *token = m_xdgActivation->requestXdgActivationToken(m_seat,surface,0,"");
    connect(token, &XdgActivationToken::done,this,[=](const QString&str){
        auto surface2 = KWayland::Client::Surface::fromWindow(window2);
        if (!surface2)
            return;
        if(!m_surfaces.contains(window2))
        {
            m_surfaces.insert(window2,surface2);
        }
        m_xdgActivation->activateWindow(str, surface2);
    });
}

void UkuiWaylandInterface::setIdleInterval(int msec)
{
    if(!m_notifier || !m_seat)
        return;
     ExtIdleNotification* notification = m_notifier->requestExtIdleNotification(m_seat, msec);
     if(!notification)
         return;
     connect(notification,&ExtIdleNotification::idled,this,[=](){
         Q_EMIT idled();
     });
     connect(notification,&ExtIdleNotification::resumed,this,[=](){
         Q_EMIT resumed();
     });
}

QRect UkuiWaylandInterface::windowGeometry(const WindowId &windowId)
{
    UkuiWindow* w = windowFor(windowId);
    return w->geometry();
}

void UkuiWaylandInterface::setPanelAutoHide(QWindow *window, bool autoHide)
{
    if(!window)
        return;
    if (!m_ukuiShell)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return;
    if(!m_surfaces.contains(window))
    {
        m_surfaces.insert(window,surface);
    }

    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return;
    if(!m_ukuiShellSurfaces.contains(window))
    {
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);
    }
    ukuiShellSurface->setPanelAutoHide(autoHide);


    window->installEventFilter(this);
}


void UkuiWaylandInterface::setGeometry(QWindow *window, const QRect &rect)
{
    if(!window)
        return;
    if (!m_ukuiShell)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return;
    if(!m_surfaces.contains(window))
    {
        m_surfaces.insert(window,surface);
    }

    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return;
    if(!m_ukuiShellSurfaces.contains(window))
    {
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);
    }
    ukuiShellSurface->setPosition(rect.topLeft());
    window->resize(rect.size());


    window->installEventFilter(this);
}

void UkuiWaylandInterface::setSkipTaskBar(QWindow *window, bool skip)
{
    if(!window)
        return;
    if (!m_ukuiShell)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return;
    if(!m_surfaces.contains(window))
        m_surfaces.insert(window,surface);
    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return;
    if(!m_ukuiShellSurfaces.contains(window))
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);

    ukuiShellSurface->setSkipTaskbar(skip);

    window->installEventFilter(this);
}

void UkuiWaylandInterface::setSkipSwitcher(QWindow *window, bool skip)
{
    if(!window)
        return;
    if (!m_ukuiShell)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return;
    if(!m_surfaces.contains(window))
        m_surfaces.insert(window,surface);
    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return;
    if(!m_ukuiShellSurfaces.contains(window))
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);

    ukuiShellSurface->setSkipSwitcher(skip);
    window->installEventFilter(this);
}

bool UkuiWaylandInterface::skipTaskBar(const WindowId &wid)
{
    auto window = windowFor(wid);

    return window ? window->skipTaskbar() : false;
}

bool UkuiWaylandInterface::skipSwitcher(const WindowId &wid)
{
    auto window = windowFor(wid);

    return window ? window->skipSwitcher() : false;
}

bool UkuiWaylandInterface::isShowingDesktop()
{
    bool flag = false;
    if(m_ukuiWindowManager)
    {
        flag = m_ukuiWindowManager->isShowingDesktop();
    }
    return flag;
}

void UkuiWaylandInterface::setOnAllDesktops(const WindowId &wid)
{
    auto w = windowFor(wid);

    if (w && m_desktops.count() > 1)
    {
        if (w->isOnAllDesktops())
        {
            w->requestEnterVirtualDesktop(m_currentDesktop);
        }
    }
}

NET::WindowType UkuiWaylandInterface::windowType(WindowId wid)
{
    return NET::WindowType::Normal;
}

void UkuiWaylandInterface::setPanelTakefocus(QWindow *window, bool flag)
{
    if(!window)
        return;
    if (!m_ukuiShell)
        return;

    auto surface = KWayland::Client::Surface::fromWindow(window);
    if (!surface)
        return;
    if(!m_surfaces.contains(window))
    {
        m_surfaces.insert(window,surface);
    }

    auto ukuiShellSurface = m_ukuiShell->createSurface(surface, window);
    if (!ukuiShellSurface)
        return;
    if(!m_ukuiShellSurfaces.contains(window))
    {
        m_ukuiShellSurfaces.insert(window,ukuiShellSurface);
    }
    ukuiShellSurface->setPanelTakesFocus(flag);
    window->installEventFilter(this);
}

void UkuiWaylandInterface::demandAttention(const WindowId &wid)
{
    auto w = windowFor(wid);

    if (w) {
        w->requestDemandAttention();
        m_connection->roundtrip();
    }
}

bool UkuiWaylandInterface::eventFilter(QObject *obj, QEvent *ev)
{
    auto window = qobject_cast<QWindow*>(obj);
    if(window && ev->type() == QEvent::Hide)
    {
        if(m_ukuiShellSurfaces.contains(window))
        {
            auto ukuiShellSurface = m_ukuiShellSurfaces.value(window);
            if(ukuiShellSurface)
            {
                ukuiShellSurface->release();
                ukuiShellSurface->destroy();
            }
            m_ukuiShellSurfaces.remove(window);
        }
        if(m_surfaces.contains(window))
        {
            auto surface = m_surfaces.value(window);
            if(surface)
            {
                surface->release();
                surface->destroy();
            }
            m_surfaces.remove(window);
        }
    }
    return QObject::eventFilter(obj,ev);
}

bool UkuiWaylandInterface::ukuiProtocolReady()
{
    return m_ukuiShell && m_ukuiWindowManager;
}

Registry *UkuiWaylandInterface::registry()
{
    return m_registry;
}

UkuiWindow *UkuiWaylandInterface::windowFor(WindowId wid)
{
    auto it = std::find_if(m_ukuiWindowManager->windows().constBegin(), m_ukuiWindowManager->windows().constEnd(), [&wid](UkuiWindow * w) noexcept {
            return w->isValid() && w->uuid() == wid;
});

    if (it == m_ukuiWindowManager->windows().constEnd()) {
        return nullptr;
    }

    return *it;
}

void UkuiWaylandInterface::handleGlobal(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version)
{
    if(strcmp(interface, ext_idle_notifier_v1_interface.name) == 0)
    {
        ext_idle_notifier_v1* notifier = (ext_idle_notifier_v1*)wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, version);
        if(notifier)
        {
            m_notifier = new ExtIdleNotifier(this);
            m_notifier->setup(notifier);
        }
    }
    if (strcmp(interface, wl_seat_interface.name) == 0)
    {
        m_seat =  (wl_seat *) wl_registry_bind(registry, name, &wl_seat_interface, version);

    }
    if (strcmp(interface, xdg_activation_v1_interface.name) == 0)
    {
        xdg_activation_v1 *xdg_activation =  (xdg_activation_v1 *) wl_registry_bind(registry, name, &xdg_activation_v1_interface, version);
        if(xdg_activation)
        {
            m_xdgActivation = new XdgActivation(this);
            m_xdgActivation->setup(xdg_activation);
        }
    }
    if (strcmp(interface, ukui_shell_interface.name) == 0)
    {
        ukui_shell *shell =  (ukui_shell *) wl_registry_bind(registry, name, &ukui_shell_interface, version);
        if(shell)
        {
            m_ukuiShell = new UkuiShell(this);
            m_ukuiShell->setup(shell);
        }
    }
    if(strcmp(interface, ukui_window_management_interface.name) == 0)
    {
        ukui_window_management  *wm =
                (ukui_window_management *) wl_registry_bind(registry, name, &ukui_window_management_interface, version);
        if(wm)
        {
            m_ukuiWindowManager = new UkuiWindowManagement(this);
            m_ukuiWindowManager->setup(wm);

            connect(m_ukuiWindowManager, &UkuiWindowManagement::showingDesktopChanged,this,&AbstractInterface::isShowingDesktopChanged);
            connect(m_ukuiWindowManager, &UkuiWindowManagement::windowCreated, this, &UkuiWaylandInterface::ukuiWindowCreatedProxy);
            connect(m_ukuiWindowManager, &UkuiWindowManagement::activeWindowChanged, this, [&]() noexcept
            {
                        UkuiWindow* w = m_ukuiWindowManager->activeWindow();
                        if(w)
                        {
                            emit activeWindowChanged(w ? w->uuid() : "");
                        }
            }, Qt::QueuedConnection);
            connect(m_ukuiWindowManager, &UkuiWindowManagement::windowCreated,this,
                [this](UkuiWindow *window)
            {
                if (!m_windows.contains(window->uuid()))
                {
                    m_windows.insert(window->uuid(), this->requestInfo(window->uuid()));
                }
                emit windowAdded(window->uuid());
            });
        }
    }
}

void UkuiWaylandInterface::handleGlobalRemove(void *data, wl_registry *registry, uint32_t name)
{
}

void UkuiWaylandInterface::ukuiWindowCreatedProxy(UkuiWindow *w)
{
    if (!w->isValid())
        return;

    if ((w->appId() == "ukui-panel"))
        return;

    trackUkuiWindow(w);
}

void UkuiWaylandInterface::trackUkuiWindow(UkuiWindow *w)
{
    if(!w || w->appId() == "ukui-panel")
        return;

    connect(w, &UkuiWindow::titleChanged, this, [=](){emit titleChanged(w->uuid());});
    connect(w, &UkuiWindow::fullscreenChanged, this, [=](){emit fullscreenChanged(w->uuid());});
    connect(w, &UkuiWindow::iconChanged, this, [=](){emit iconChanged(w->uuid());});
    connect(w, &UkuiWindow::activeChanged, this, [=](){emit activeChanged(w->uuid());});
    connect(w, &UkuiWindow::keepAboveChanged, this, [=](){emit keepAboveChanged(w->uuid());});
    connect(w, &UkuiWindow::minimizedChanged, this, [=](){emit minimizedChanged(w->uuid());});
    connect(w, &UkuiWindow::maximizedChanged, this, [=](){emit maximizedChanged(w->uuid());});
    connect(w, &UkuiWindow::onAllDesktopsChanged, this, [=](){emit onAllDesktopsChanged(w->uuid());});
    connect(w, &UkuiWindow::demandsAttentionChanged, this, [=](){emit demandsAttentionChanged(w->uuid());});
    connect(w, &UkuiWindow::skipTaskbarChanged, this, [=](){emit skipTaskbarChanged(w->uuid());});
    connect(w, &UkuiWindow::skipSwitcherChanged, this, [=](){emit skipSwitcherChanged(w->uuid());});
    connect(w, &UkuiWindow::geometryChanged, this, [=](){emit geometryChanged(w->uuid());});
}

void UkuiWaylandInterface::untrackUkuiWindow(UkuiWindow *w)
{
    disconnect(w, &UkuiWindow::titleChanged, 0, 0);
    disconnect(w, &UkuiWindow::iconChanged, 0, 0);
    disconnect(w, &UkuiWindow::activeChanged, 0, 0);
    disconnect(w, &UkuiWindow::keepAboveChanged, 0, 0);
    disconnect(w, &UkuiWindow::keepBelowChanged, 0, 0);
    disconnect(w, &UkuiWindow::minimizedChanged, 0, 0);
    disconnect(w, &UkuiWindow::maximizedChanged, 0, 0);
    disconnect(w, &UkuiWindow::onAllDesktopsChanged, 0, 0);
    disconnect(w, &UkuiWindow::demandsAttentionChanged, 0, 0);
    disconnect(w, &UkuiWindow::skipTaskbarChanged,  0, 0);
    disconnect(w, &UkuiWindow::skipSwitcherChanged, 0, 0);
    disconnect(w, &UkuiWindow::geometryChanged, 0, 0);
}
