/*
 * This file is part of mpv.
 *
 * mpv 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) any later version.
 *
 * mpv 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 mpv.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <drm_fourcc.h>
#include <drm_mode.h>

#include "common/common.h"
#include "common/msg.h"
#include "drm_common.h"
#include "drm_prime.h"

int drm_prime_create_framebuffer(struct mp_log *log, int fd,
                                 AVDRMFrameDescriptor *descriptor, int width,
                                 int height, struct drm_prime_framebuffer *framebuffer,
                                 struct drm_prime_handle_refs *handle_refs)
{
    AVDRMLayerDescriptor *layer = NULL;
    uint32_t pitches[4] = { 0 };
    uint32_t offsets[4] = { 0 };
    uint32_t handles[4] = { 0 };
    uint64_t modifiers[4] = { 0 };
    uint32_t flags = 0;
    int ret;

    if (descriptor && descriptor->nb_layers) {
        *framebuffer = (struct drm_prime_framebuffer){0};

        for (int object = 0; object < descriptor->nb_objects; object++) {
            ret = drmPrimeFDToHandle(fd, descriptor->objects[object].fd,
                                     &framebuffer->gem_handles[object]);
            if (ret < 0) {
                mp_err(log, "Failed to retrieve the Prime Handle from handle %d (%d).\n",
                       object, descriptor->objects[object].fd);
                goto fail;
            }
        }

        layer = &descriptor->layers[0];

        for (int plane = 0; plane < AV_DRM_MAX_PLANES; plane++) {
            if (plane < layer->nb_planes) {
                int object = layer->planes[plane].object_index;
                pitches[plane] = layer->planes[plane].pitch;
                offsets[plane] = layer->planes[plane].offset;
                handles[plane] = framebuffer->gem_handles[object];
                modifiers[plane] = descriptor->objects[object].format_modifier;
            } else {
                pitches[plane] = 0;
                offsets[plane] = 0;
                handles[plane] = 0;
                modifiers[plane] = 0;
            }
        }

        if (modifiers[0] && modifiers[0] != DRM_FORMAT_MOD_INVALID) {
            flags = DRM_MODE_FB_MODIFIERS;
        }

        ret = drmModeAddFB2WithModifiers(fd, width, height, layer->format,
                                         handles, pitches, offsets, modifiers,
                                         &framebuffer->fb_id, flags);
        if (ret) {
            ret = drmModeAddFB2(fd, width, height, layer->format,
                                handles, pitches, offsets,
                                &framebuffer->fb_id, 0);
        }
        if (ret) {
            mp_err(log, "Failed to create framebuffer on layer %d: %s\n",
                   0, mp_strerror(errno));
            goto fail;
        }

        for (int object = 0; object < descriptor->nb_objects; object++) {
            drm_prime_add_handle_ref(handle_refs, framebuffer->gem_handles[object]);
        }
    }

    return 0;

fail:
    memset(framebuffer, 0, sizeof(*framebuffer));
    return -1;
}

void drm_prime_destroy_framebuffer(struct mp_log *log, int fd,
                                   struct drm_prime_framebuffer *framebuffer,
                                   struct drm_prime_handle_refs *handle_refs)
{
    if (framebuffer->fb_id)
        drmModeRmFB(fd, framebuffer->fb_id);

    for (int i = 0; i < AV_DRM_MAX_PLANES; i++) {
        if (framebuffer->gem_handles[i]) {
            drm_prime_remove_handle_ref(handle_refs,
                                        framebuffer->gem_handles[i]);
            if (!drm_prime_get_handle_ref_count(handle_refs,
                                                framebuffer->gem_handles[i])) {
                drmIoctl(fd, DRM_IOCTL_GEM_CLOSE, &framebuffer->gem_handles[i]);
            }
        }
    }

    memset(framebuffer, 0, sizeof(*framebuffer));
}

void drm_prime_init_handle_ref_count(void *talloc_parent,
    struct drm_prime_handle_refs *handle_refs)
{
    handle_refs->handle_ref_count = talloc_zero(talloc_parent, uint32_t);
    handle_refs->size = 1;
    handle_refs->ctx = talloc_parent;
}

void drm_prime_add_handle_ref(struct drm_prime_handle_refs *handle_refs,
                              uint32_t handle)
{
    if (handle) {
        if (handle > handle_refs->size) {
            MP_TARRAY_GROW(handle_refs->ctx, handle_refs->handle_ref_count,
                           handle - 1);
            uint32_t *p = handle_refs->handle_ref_count;
            memset(&p[handle_refs->size], 0, (handle - handle_refs->size) * sizeof(p[0]));
            handle_refs->size = handle;
        }
        handle_refs->handle_ref_count[handle - 1]++;
    }
}

void drm_prime_remove_handle_ref(struct drm_prime_handle_refs *handle_refs,
                                 uint32_t handle)
{
    if (handle) {
        if (handle <= handle_refs->size &&
             handle_refs->handle_ref_count[handle - 1])
        {
             handle_refs->handle_ref_count[handle - 1]--;
        }
    }
}

uint32_t drm_prime_get_handle_ref_count(struct drm_prime_handle_refs *handle_refs,
                                        uint32_t handle)
{
    if (handle) {
        if (handle <= handle_refs->size)
            return handle_refs->handle_ref_count[handle - 1];
    }
    return 0;
}
