// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: GPL-1.0-or-later

#include <stdlib.h>

#include <wlr/backend.h>
#include <wlr/config.h>
#include <wlr/render/allocator.h>
#include <wlr/render/pixman.h>
#include <wlr/render/vulkan.h>
#include <wlr/types/wlr_linux_dmabuf_v1.h>

#include <kywc/log.h>

#include "render/opengl.h"
#include "render/profile.h"
#include "render/renderer.h"
#include "renderer_p.h"

struct wlr_renderer *ky_renderer_autocreate(struct wlr_backend *backend)
{
    struct wlr_renderer *renderer = NULL;
    const char *api = getenv("KYWC_RENDERER");
    if (api && strcmp(api, "pixman") == 0) {
        renderer = wlr_pixman_renderer_create();
        goto done;
    }

    /* get drm fd from backend */
    int drm_fd = wlr_backend_get_drm_fd(backend);
    if (drm_fd < 0) {
        kywc_log(KYWC_ERROR, "Cannot create hardware renderer: no DRM fd available");
    } else {
        if (api && strcmp(api, "vulkan") == 0) {
#if WLR_HAS_VULKAN_RENDERER
            renderer = wlr_vk_renderer_create_with_drm_fd(drm_fd);
#else
            kywc_log(KYWC_ERROR, "Cannot create Vulkan renderer: wlroots not compiled with Vulkan");
#endif
        } else {
            renderer = ky_opengl_renderer_create_with_drm_fd(drm_fd);
        }
    }

    if (renderer) {
        gbm_allocator_create(renderer);
    } else {
        kywc_log(KYWC_WARN, "Failed to create a hardware renderer");
        renderer = wlr_pixman_renderer_create();
    }

done:
    if (!renderer) {
        kywc_log(KYWC_ERROR, "Failed to create a pixman renderer");
        kywc_log(KYWC_ERROR, "Could not initialize renderer");
    } else {
        ky_renderer_config_init(renderer);
    }

    KY_PROFILE_RENDER_CREATE(renderer);

    return renderer;
}

bool ky_renderer_init_wl_display(struct wlr_renderer *renderer, struct wlr_backend *backend,
                                 struct wl_display *wl_display,
                                 struct wlr_linux_dmabuf_v1 **linux_dmabuf_v1)
{
    wlr_renderer_init_wl_shm(renderer, wl_display);
    single_pixel_buffer_manager_create(wl_display);

    if (!wlr_renderer_get_dmabuf_texture_formats(renderer)) {
        kywc_log(KYWC_WARN, "Unable to initialize dmabuf");
        return true;
    }

    *linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(wl_display, 4, renderer);

    if (!ky_wayland_buffer_create(wl_display, renderer)) {
        /* create wl_drm if not created in driver */
        int master_fd = wlr_backend_get_drm_fd(backend);
        if (master_fd >= 0) {
            wayland_drm_create(wl_display, renderer, master_fd);
        } else {
            kywc_log(KYWC_WARN, "Cannot get renderer DRM FD, disabling wl_drm");
        }
    }

    return true;
}

const struct wlr_drm_format *ky_renderer_get_render_format(struct wlr_renderer *renderer,
                                                           uint32_t fmt, bool single_plane)
{
    if (!renderer->impl->get_render_formats) {
        return NULL;
    }

    const struct wlr_drm_format_set *render_formats = NULL;
    if (single_plane) {
        render_formats = gbm_allocator_get_single_plane_formates(renderer);
    }
    if (!render_formats) {
        render_formats = renderer->impl->get_render_formats(renderer);
    }
    if (!render_formats) {
        kywc_log(KYWC_ERROR, "Failed to get render formats");
        return NULL;
    }

    const struct wlr_drm_format *render_format = wlr_drm_format_set_get(render_formats, fmt);
    if (!render_format) {
        kywc_log(KYWC_ERROR, "Renderer doesn't support format 0x%" PRIX32, fmt);
        return NULL;
    }

    return render_format;
}

struct wlr_buffer *ky_renderer_create_buffer(struct wlr_renderer *renderer,
                                             struct wlr_allocator *alloc, int width, int height,
                                             uint32_t fmt, bool single_plane)
{
    if (wlr_renderer_is_pixman(renderer)) {
        return shm_create_buffer(width, height, fmt);
    }

    const struct wlr_drm_format *format =
        ky_renderer_get_render_format(renderer, fmt, single_plane);
    if (!format) {
        return NULL;
    }

    struct wlr_buffer *buffer = gbm_allocator_create_buffer(width, height, format);
    if (!buffer) {
        buffer = wlr_allocator_create_buffer(alloc, width, height, format);
    }

    return buffer;
}

bool ky_renderer_is_software(struct wlr_renderer *renderer)
{
    if (wlr_renderer_is_pixman(renderer)) {
        return true;
    }

    /* return true if software opengl */
    if (wlr_renderer_is_opengl(renderer)) {
        struct ky_opengl_renderer *r = ky_opengl_renderer_from_wlr_renderer(renderer);
        return r->egl->is_software;
    }

    return false;
}

struct wlr_buffer *ky_renderer_upload_pixels(struct wlr_renderer *renderer,
                                             struct wlr_allocator *alloc, int width, int height,
                                             struct wlr_buffer *pixels)
{
    /* upload is meaningless when pixman */
    if (wlr_renderer_is_pixman(renderer)) {
        return NULL;
    }

    void *data = NULL;
    uint32_t drm_format;
    size_t stride;
    if (!wlr_buffer_begin_data_ptr_access(pixels, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data,
                                          &drm_format, &stride)) {
        return NULL;
    }
    wlr_buffer_end_data_ptr_access(pixels);

    struct wlr_buffer *buffer =
        ky_renderer_create_buffer(renderer, alloc, width, height, drm_format, false);
    if (!buffer) {
        return NULL;
    }

    struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, NULL);
    if (!pass) {
        wlr_buffer_drop(buffer);
        return NULL;
    }

    struct wlr_texture *tex = wlr_texture_from_buffer(renderer, pixels);
    struct wlr_render_texture_options options = {
        .dst_box = (struct wlr_box){ 0, 0, width, height },
        .texture = tex,
        .blend_mode = WLR_RENDER_BLEND_MODE_NONE,
    };
    wlr_render_pass_add_texture(pass, &options);
    wlr_texture_destroy(tex);
    wlr_render_pass_submit(pass);

    return buffer;
}
