/********************************************************************
Copyright © 2019 Roman Gilg <subdiff@gmail.com>
Copyright © 2018 Fredrik Höglund <fredrik@kde.org>

Based on the libweston implementation,
Copyright © 2014, 2015 Collabora, Ltd.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "linuxdmabuf_v1_interface.h"

#include "drm_fourcc.h"
#include "global_p.h"
#include "wayland-linux-dmabuf-unstable-v1-server-protocol.h"
#include "wayland-server-protocol.h"

#include <KWayland/Server/kwaylandserver_export.h>

#include <QVector>

#include <array>
#include <assert.h>
#include <unistd.h>

namespace KWayland
{
namespace Server
{

class LinuxDmabufBuffer::Private
{
public:
    Private(LinuxDmabufBuffer *_q) : q(_q) {
        q->d = this;
    }
    virtual ~Private() = default;

    virtual uint32_t format() const = 0;
    virtual QSize size() const = 0;

    LinuxDmabufBuffer *q;
};

LinuxDmabufBuffer::LinuxDmabufBuffer()
{
}

uint32_t LinuxDmabufBuffer::format() const
{
    return d->format();
}

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

class LinuxDmabufUnstableV1Buffer::Private : LinuxDmabufBuffer::Private
{
public:
    Private(LinuxDmabufUnstableV1Buffer *_q)
        : LinuxDmabufBuffer::Private(_q)
        , q(_q)
    {
    }
    ~Private() override = default;

    uint32_t format() const override {
        return m_format;
    }
    QSize size() const override {
        return m_size;
    }

    uint32_t m_format;
    QSize m_size;

    LinuxDmabufUnstableV1Buffer *q;
};

LinuxDmabufUnstableV1Buffer::LinuxDmabufUnstableV1Buffer(uint32_t format, const QSize &size)
    : LinuxDmabufBuffer()
    , d(new LinuxDmabufUnstableV1Buffer::Private(this))
{
    d->m_format = format;
    d->m_size = size;
}

typedef LinuxDmabufUnstableV1Interface V1Iface;

class V1Iface::Private : public Global::Private
{
public:
    Private(V1Iface *q, Display *display);
    ~Private();

    static const struct wl_buffer_interface *bufferImplementation() { return &s_bufferImplementation; }
    V1Iface::Impl *impl;
    QHash<uint32_t, QSet<uint64_t> > supportedFormatsWithModifiers;
    V1Iface * const q;
    static const uint32_t s_version;

    void bind(wl_client *client, uint32_t version, uint32_t id) override final;
    void createParams(wl_client *client, wl_resource *resource, uint32_t id);

    static void unbind(wl_client *client, wl_resource *resource);
    static void createParamsCallback(wl_client *client, wl_resource *resource, uint32_t id);

private:
    class Params
    {
    public:
        Params(V1Iface::Private *dmabufInterface, wl_client *client, uint32_t version, uint32_t id);
        ~Params();

        void postNoMemory() { wl_resource_post_no_memory(m_resource); }

        wl_resource *resource() const { return m_resource; }

        void add(int fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint64_t modifier);
        void create(wl_client *client, uint32_t bufferId, const QSize &size, uint32_t format, uint32_t flags);

        static void destroy(wl_client *client, wl_resource *resource);
        static void add(wl_client *client, wl_resource *resource, int fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, uint32_t modifier_lo);
        static void create(wl_client *client, wl_resource *resource, int width, int height, uint32_t format, uint32_t flags);
        static void createImmed(wl_client *client, wl_resource *resource, uint32_t new_id, int width, int height, uint32_t format, uint32_t flags);

    private:
        static const struct zwp_linux_buffer_params_v1_interface s_interface;

        wl_resource *m_resource;
        V1Iface::Private *m_dmabufInterface;
        std::array<V1Iface::Plane, 4> m_planes;
        size_t m_planeCount = 0;
        bool m_createRequested = false;
    };

    static const struct zwp_linux_dmabuf_v1_interface s_implementation;
    static const struct wl_buffer_interface s_bufferImplementation;
};

void V1Iface::Private::Params::create(wl_client *client, wl_resource *resource,
                               int width, int height, uint32_t format, uint32_t flags)
{
    Q_UNUSED(client)

    V1Iface::Private::Params *params = static_cast<V1Iface::Private::Params *>(wl_resource_get_user_data(resource));
    assert(params->m_resource == resource);
    params->create(client, 0, QSize(width, height), format, flags);
}

void V1Iface::Private::Params::createImmed(wl_client *client, wl_resource *resource,
                                    uint32_t new_id, int width, int height,
                                    uint32_t format, uint32_t flags)
{
    Q_UNUSED(client)

    V1Iface::Private::Params *params = static_cast<V1Iface::Private::Params *>(wl_resource_get_user_data(resource));
    assert(params->m_resource == resource);
    params->create(client, new_id, QSize(width, height), format, flags);
}

#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zwp_linux_dmabuf_v1_interface V1Iface::Private::s_implementation = {
    [](wl_client *, wl_resource *resource) { wl_resource_destroy(resource); }, // unbind
    createParamsCallback
};

const struct wl_buffer_interface V1Iface::Private::s_bufferImplementation = {
    [](wl_client *, wl_resource *resource) { wl_resource_destroy(resource); } // destroy
};

#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct zwp_linux_buffer_params_v1_interface V1Iface::Private::Params::s_interface = {
    destroy,
    add,
    create,
    createImmed
};
#endif

V1Iface::Private::Params::Params(V1Iface::Private *dmabufInterface, wl_client *client, uint32_t version, uint32_t id)
    : m_dmabufInterface(dmabufInterface)
{
    m_resource = wl_resource_create(client, &zwp_linux_buffer_params_v1_interface, version, id);
    if (!m_resource) {
        return;
    }

    wl_resource_set_implementation(m_resource, &s_interface, this,
                                   [](wl_resource *resource) {
                                       delete static_cast<V1Iface::Private::Params *>(wl_resource_get_user_data(resource));
                                   });

    for (auto &plane : m_planes) {
        plane.fd = -1;
        plane.offset = 0;
        plane.stride = 0;
        plane.modifier = 0;
    }
}

V1Iface::Private::Params::~Params()
{
    // Close the file descriptors
    for (auto &plane : m_planes) {
        if (plane.fd != -1) {
            ::close(plane.fd);
        }
    }
}

void V1Iface::Private::Params::create(wl_client *client, uint32_t bufferId, const QSize &size, uint32_t format, uint32_t flags)
{
    // Validate the parameters
    // -----------------------
    const uint32_t width = size.width();
    const uint32_t height = size.height();

    if (m_createRequested) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
                               "params was already used to create a wl_buffer");
        return;
    }
    m_createRequested = true;

    if (m_planeCount == 0) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
                               "no dmabuf has been added to the params");
        return;
    }

    // Check for holes in the dmabufs set (e.g. [0, 1, 3])
    for (uint32_t i = 0; i < m_planeCount; i++) {
        if (m_planes[i].fd != -1)
            continue;

        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE,
                               "no dmabuf has been added for plane %i", i);
        return;
    }

    if (width < 1 || height < 1) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS,
                               "invalid width %d or height %d", width, height);
        return;
    }

    for (uint32_t i = 0; i < m_planeCount; i++) {
        auto &plane = m_planes[i];

        if (uint64_t(plane.offset) + plane.stride > UINT32_MAX) {
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                   "size overflow for plane %i", i);
            return;
        }

        if (i == 0 && uint64_t(plane.offset) + plane.stride * height > UINT32_MAX) {
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                   "size overflow for plane %i", i);
            return;
        }

        // Don't report an error as it might be caused by the kernel not supporting seeking on dmabuf
        off_t size = ::lseek(plane.fd, 0, SEEK_END);
        if (size == -1)
            continue;

        if (plane.offset >= size) {
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                   "invalid offset %i for plane %i",
                                   plane.offset, i);
            return;
        }

        if (plane.offset + plane.stride > size) {
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                   "invalid stride %i for plane %i",
                                   plane.stride, i);
            return;
        }

        // Only valid for first plane as other planes might be
        // sub-sampled according to fourcc format
        if (i == 0 && plane.offset + plane.stride * height > size) {
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS,
                                   "invalid buffer stride or height for plane %i", i);
            return;
        }
    }

    // Import the buffer
    // -----------------
    QVector<V1Iface::Plane> planes;
    planes.reserve(m_planeCount);
    for (uint32_t i = 0; i < m_planeCount; i++)
        planes << m_planes[i];

    LinuxDmabufUnstableV1Buffer *buffer = m_dmabufInterface->impl->importBuffer(planes,
                                                                                format,
                                                                                size,
                                                                                (V1Iface::Flags) flags);
    if (buffer) {
        // The buffer has ownership of the file descriptors now
        for (auto &plane : m_planes) {
            plane.fd = -1;
        }

        wl_resource *resource = wl_resource_create(client, &wl_buffer_interface, 1, bufferId);
        if (!resource ) {
            postNoMemory();
            delete buffer;
            return;
        }

        wl_resource_set_implementation(resource, m_dmabufInterface->q->bufferImplementation(), buffer,
                                       [](wl_resource *resource) { // Destructor
                                            delete static_cast<LinuxDmabufUnstableV1Buffer *>(wl_resource_get_user_data(resource));
                                       });

        // XXX Do we need this?
        //buffer->setResource(resource);

        // Send a 'created' event when the request is not for an immediate import, i.e. bufferId is zero
        if (bufferId == 0) {
            zwp_linux_buffer_params_v1_send_created(m_resource, resource);
        }
    } else {
        if (bufferId == 0) {
            zwp_linux_buffer_params_v1_send_failed(m_resource);
        } else {
            // since the behavior is left implementation defined by the
            // protocol in case of create_immed failure due to an unknown cause,
            // we choose to treat it as a fatal error and immediately kill the
            // client instead of creating an invalid handle and waiting for it
            // to be used.
            wl_resource_post_error(m_resource,
                                   ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER,
                                   "importing the supplied dmabufs failed");
        }
    }
}

void V1Iface::Private::Params::add(int fd, uint32_t plane_idx, uint32_t offset, uint32_t stride, uint64_t modifier)
{
    if (m_createRequested) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED,
                               "params was already used to create a wl_buffer");
        ::close(fd);
        return;
    }

    if (plane_idx >= m_planes.size()) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX,
                               "plane index %u is too high", plane_idx);
        ::close(fd);
        return;
    }

    auto &plane = m_planes[plane_idx];

    if (plane.fd != -1) {
        wl_resource_post_error(m_resource,
                               ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET,
                               "a dmabuf has already been added for plane %u",
                               plane_idx);
        ::close(fd);
        return;
    }

    plane.fd = fd;
    plane.offset = offset;
    plane.stride = stride;
    plane.modifier = modifier;

    m_planeCount++;
}

void V1Iface::Private::Params::destroy(wl_client *client, wl_resource *resource)
{
    Q_UNUSED(client)
    wl_resource_destroy(resource);
}

void V1Iface::Private::Params::add(wl_client *client, wl_resource *resource,
                            int fd, uint32_t plane_idx,
                            uint32_t offset, uint32_t stride,
                            uint32_t modifier_hi, uint32_t modifier_lo)
{
    Q_UNUSED(client)

    V1Iface::Private::Params *params = static_cast<V1Iface::Private::Params *>(wl_resource_get_user_data(resource));
    assert(params->m_resource == resource);
    params->add(fd, plane_idx, offset, stride, (uint64_t(modifier_hi) << 32) | modifier_lo);
}

const uint32_t V1Iface::Private::s_version = 3;
#endif

V1Iface::Private::Private(V1Iface *q, Display *display)
    : Global::Private(display, &zwp_linux_dmabuf_v1_interface, s_version),
      q(q)
{
}

V1Iface::Private::~Private()
{
    delete impl;
}

void V1Iface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
    wl_resource *resource = wl_resource_create(client, &zwp_linux_dmabuf_v1_interface, std::min(s_version, version), id);
    if (!resource) {
        wl_client_post_no_memory(client);
        return;
    }

    wl_resource_set_implementation(resource, &s_implementation, this, nullptr);

    // Send formats & modifiers
    // ------------------------

    QHash<uint32_t, QSet<uint64_t>>::const_iterator it = supportedFormatsWithModifiers.constBegin();
    while (it != supportedFormatsWithModifiers.constEnd()) {
        QSet<uint64_t> modifiers = it.value();
        if (modifiers.isEmpty()) {
            modifiers << DRM_FORMAT_MOD_INVALID;
        }

        for (uint64_t modifier : qAsConst(modifiers)) {
            if (version >= ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
                const uint32_t modifier_lo = modifier & 0xFFFFFFFF;
                const uint32_t modifier_hi = modifier >> 32;
                zwp_linux_dmabuf_v1_send_modifier(resource, it.key(), modifier_hi, modifier_lo);
            } else if (modifier == DRM_FORMAT_MOD_LINEAR || modifier == DRM_FORMAT_MOD_INVALID) {
                zwp_linux_dmabuf_v1_send_format(resource, it.key());
            }
        }
        it++;
    }
}

void V1Iface::Private::createParams(wl_client *client, wl_resource *resource, uint32_t id)
{
    Params *params = new Params(this, client, wl_resource_get_version(resource), id);
    if (!params->resource()) {
        wl_resource_post_no_memory(resource);
        delete params;
    }
}

void V1Iface::Private::createParamsCallback(wl_client *client, wl_resource *resource, uint32_t id)
{
    V1Iface::Private *global = static_cast<V1Iface::Private *>(wl_resource_get_user_data(resource));
    global->createParams(client, resource, id);
}


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

V1Iface::~LinuxDmabufUnstableV1Interface() = default;

void V1Iface::setImpl(V1Iface::Impl *impl)
{
    d_func()->impl = impl;
}

void V1Iface::setSupportedFormatsWithModifiers(QHash<uint32_t, QSet<uint64_t> > set)
{
    d_func()->supportedFormatsWithModifiers = set;
}

const struct wl_buffer_interface *V1Iface::bufferImplementation()
{
    return V1Iface::Private::bufferImplementation();
}

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

}
}
