/*
    SPDX-FileCopyrightText: 2016 Oleg Chernovskiy <kanedias@xaker.ru>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "remote_access_interface.h"
#include "display.h"
#include "global_p.h"
#include "logging.h"
#include "output_interface.h"
#include "remote_access_interface_p.h"
#include "resource_p.h"

#include <wayland-remote-access-server-protocol.h>

#include <QHash>
#include <QMutableHashIterator>

#include <functional>

namespace KWayland
{
namespace Server
{
class BufferHandle::Private // @see gbm_import_fd_data
{
public:
    // Note that on client side received fd number will be different
    // and meaningful only for client process!
    // Thus we can use server-side fd as an implicit unique id
    qint32 fd = 0; ///< also internal buffer id for client
    quint32 width = 0;
    quint32 height = 0;
    quint32 stride = 0;
    quint32 format = 0;
};

BufferHandle::BufferHandle()
    : d(new Private)
{
}

BufferHandle::~BufferHandle()
{
}

void BufferHandle::setFd(qint32 fd)
{
    d->fd = fd;
}

qint32 BufferHandle::fd() const
{
    return d->fd;
}

void BufferHandle::setSize(quint32 width, quint32 height)
{
    d->width = width;
    d->height = height;
}

quint32 BufferHandle::width() const
{
    return d->width;
}

quint32 BufferHandle::height() const
{
    return d->height;
}

void BufferHandle::setStride(quint32 stride)
{
    d->stride = stride;
}

quint32 BufferHandle::stride() const
{
    return d->stride;
}

void BufferHandle::setFormat(quint32 format)
{
    d->format = format;
}

quint32 BufferHandle::format() const
{
    return d->format;
}

/**
 * @brief helper struct for manual reference counting.
 * automatic counting via QSharedPointer is no-go here as we hold strong reference in sentBuffers.
 */
struct BufferHolder {
    const BufferHandle *buf;
    quint64 counter;
};

class RemoteAccessManagerInterface::Private : public Global::Private
{
public:
    Private(RemoteAccessManagerInterface *q, Display *d);
    ~Private() override;

    /**
     * @brief Send buffer ready notification to all connected clients
     * @param output wl_output interface to determine which screen sent this buf
     * @param buf buffer containing GBM-related params
     */
    void sendBufferReady(const OutputInterface *output, const BufferHandle *buf);
    /**
     * @brief Release all bound buffers associated with this resource
     * @param resource one of bound clients
     */
    void release(wl_resource *resource);

    /**
     * Clients of this interface.
     * This may be screenshot app, video capture app,
     * remote control app etc.
     */
    QList<wl_resource *> clientResources;

private:
    // methods
    static void unbind(wl_resource *resource);
    static Private *cast(wl_resource *r)
    {
        return reinterpret_cast<Private *>(wl_resource_get_user_data(r));
    }
    static void getBufferCallback(wl_client *client, wl_resource *resource, uint32_t buffer, int32_t internalBufId);
    static void releaseCallback(wl_client *client, wl_resource *resource);
    void bind(wl_client *client, uint32_t version, uint32_t id) override;

    /**
     * @brief Unreferences counter and frees buffer when it reaches zero
     * @param buf holder to decrease reference counter on
     * @return true if buffer was released, false otherwise
     */
    bool unref(BufferHolder &buf);

    // fields
    static const struct org_kde_kwin_remote_access_manager_interface s_interface;
    static const quint32 s_version;

    RemoteAccessManagerInterface *q;

    /**
     * Buffers that were sent but still not acked by server
     * Keys are fd numbers as they are unique
     **/
    QHash<qint32, BufferHolder> sentBuffers;
};

const quint32 RemoteAccessManagerInterface::Private::s_version = 1;

RemoteAccessManagerInterface::Private::Private(RemoteAccessManagerInterface *q, Display *d)
    : Global::Private(d, &org_kde_kwin_remote_access_manager_interface, s_version)
    , q(q)
{
}

void RemoteAccessManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
    // create new client resource
    auto c = display->getConnection(client);
    wl_resource *resource = c->createResource(&org_kde_kwin_remote_access_manager_interface, qMin(version, s_version), id);
    if (!resource) {
        wl_client_post_no_memory(client);
        return;
    }
    wl_resource_set_implementation(resource, &s_interface, this, unbind);

    // add newly created client resource to the list
    clientResources << resource;
}

void RemoteAccessManagerInterface::Private::sendBufferReady(const OutputInterface *output, const BufferHandle *buf)
{
    BufferHolder holder{buf, 0};
    // notify clients
    qCDebug(KWAYLAND_SERVER) << "Server buffer sent: fd" << buf->fd();
    for (auto res : clientResources) {
        auto client = wl_resource_get_client(res);
        auto boundScreens = output->clientResources(display->getConnection(client));

        // clients don't necessarily bind outputs
        if (boundScreens.isEmpty()) {
            continue;
        }

        // no reason for client to bind wl_output multiple times, send only to first one
        org_kde_kwin_remote_access_manager_send_buffer_ready(res, buf->fd(), boundScreens[0]);
        holder.counter++;
    }
    if (holder.counter == 0) {
        // buffer was not requested by any client
        Q_EMIT q->bufferReleased(buf);
        return;
    }
    // store buffer locally, clients will ask it later
    sentBuffers[buf->fd()] = holder;
}

#ifndef K_DOXYGEN
const struct org_kde_kwin_remote_access_manager_interface RemoteAccessManagerInterface::Private::s_interface = {getBufferCallback, releaseCallback};
#endif

void RemoteAccessManagerInterface::Private::getBufferCallback(wl_client *client, wl_resource *resource, uint32_t buffer, int32_t internalBufId)
{
    Private *p = cast(resource);

    // client asks for buffer we earlier announced, we must have it
    if (Q_UNLIKELY(!p->sentBuffers.contains(internalBufId))) { // no such buffer (?)
        wl_resource_post_no_memory(resource);
        return;
    }

    BufferHolder &bh = p->sentBuffers[internalBufId];
    auto rbuf = new RemoteBufferInterface(p->q, resource, bh.buf);
    rbuf->create(p->display->getConnection(client), wl_resource_get_version(resource), buffer);
    if (!rbuf->resource()) {
        wl_resource_post_no_memory(resource);
        delete rbuf;
        return;
    }

    QObject::connect(rbuf, &Resource::aboutToBeUnbound, p->q, [p, rbuf, resource, &bh] {
        if (!p->clientResources.contains(resource)) {
            // remote buffer destroy confirmed after client is already gone
            // all relevant buffers are already unreferenced
            return;
        }
        qCDebug(KWAYLAND_SERVER) << "Remote buffer returned, client" << wl_resource_get_id(resource) << ", id" << rbuf->id() << ", fd" << bh.buf->fd();
        if (p->unref(bh)) {
            p->sentBuffers.remove(bh.buf->fd());
        }
    });

    // send buffer params
    rbuf->passFd();
}

void RemoteAccessManagerInterface::Private::releaseCallback(wl_client *client, wl_resource *resource)
{
    Q_UNUSED(client);
    unbind(resource);
}

bool RemoteAccessManagerInterface::Private::unref(BufferHolder &bh)
{
    bh.counter--;
    if (!bh.counter) {
        // no more clients using this buffer
        qCDebug(KWAYLAND_SERVER) << "Buffer released, fd" << bh.buf->fd();
        Q_EMIT q->bufferReleased(bh.buf);
        return true;
    }

    return false;
}

void RemoteAccessManagerInterface::Private::unbind(wl_resource *resource)
{
    // we're unbinding, all sent buffers for this client are now effectively invalid
    Private *p = cast(resource);
    p->release(resource);
}

void RemoteAccessManagerInterface::Private::release(wl_resource *resource)
{
    // all holders should decrement their counter as one client is gone
    QMutableHashIterator<qint32, BufferHolder> itr(sentBuffers);
    while (itr.hasNext()) {
        BufferHolder &bh = itr.next().value();
        if (unref(bh)) {
            itr.remove();
        }
    }

    clientResources.removeAll(resource);
}

RemoteAccessManagerInterface::Private::~Private()
{
    // server deletes created interfaces, release all held buffers
    auto c = clientResources; // shadow copy
    for (auto res : c) {
        release(res);
    }
}

RemoteAccessManagerInterface::RemoteAccessManagerInterface(Display *display, QObject *parent)
    : Global(new Private(this, display), parent)
{
}

void RemoteAccessManagerInterface::sendBufferReady(const OutputInterface *output, const BufferHandle *buf)
{
    Private *priv = reinterpret_cast<Private *>(d.data());
    priv->sendBufferReady(output, buf);
}

bool RemoteAccessManagerInterface::isBound() const
{
    Private *priv = reinterpret_cast<Private *>(d.data());
    return !priv->clientResources.isEmpty();
}

class RemoteBufferInterface::Private : public Resource::Private
{
public:
    Private(RemoteAccessManagerInterface *ram, RemoteBufferInterface *q, wl_resource *pResource, const BufferHandle *buf);
    ~Private();

    void passFd();

private:
    static const struct org_kde_kwin_remote_buffer_interface s_interface;

    const BufferHandle *wrapped;
};

#ifndef K_DOXYGEN
const struct org_kde_kwin_remote_buffer_interface RemoteBufferInterface::Private::s_interface = {resourceDestroyedCallback};
#endif

RemoteBufferInterface::Private::Private(RemoteAccessManagerInterface *ram, RemoteBufferInterface *q, wl_resource *pResource, const BufferHandle *buf)
    : Resource::Private(q, ram, pResource, &org_kde_kwin_remote_buffer_interface, &s_interface)
    , wrapped(buf)
{
}

RemoteBufferInterface::Private::~Private()
{
}

void RemoteBufferInterface::Private::passFd()
{
    org_kde_kwin_remote_buffer_send_gbm_handle(resource, wrapped->fd(), wrapped->width(), wrapped->height(), wrapped->stride(), wrapped->format());
}

RemoteBufferInterface::RemoteBufferInterface(RemoteAccessManagerInterface *ram, wl_resource *pResource, const BufferHandle *buf)
    : Resource(new Private(ram, this, pResource, buf), ram)
{
}

RemoteBufferInterface::Private *RemoteBufferInterface::d_func() const
{
    return reinterpret_cast<Private *>(d.data());
}

void RemoteBufferInterface::passFd()
{
    d_func()->passFd();
}

}
}
