// SPDX-FileCopyrightText: 2023 The wlroots contributors
// SPDX-FileCopyrightText: 2023 KylinSoft Co., Ltd.
//
// SPDX-License-Identifier: Expat

#include <assert.h>
#include <drm_fourcc.h>
#include <stdlib.h>

#include <wlr/render/wlr_texture.h>

#include <kywc/log.h>

#include "render/opengl.h"
#include "render/pixel_format.h"

static const struct wlr_texture_impl texture_impl;

bool wlr_texture_is_opengl(struct wlr_texture *wlr_texture)
{
    return wlr_texture->impl == &texture_impl;
}

struct ky_opengl_texture *ky_opengl_texture_from_wlr_texture(struct wlr_texture *wlr_texture)
{
    assert(wlr_texture->impl == &texture_impl);
    struct ky_opengl_texture *texture = wl_container_of(wlr_texture, texture, wlr_texture);
    return texture;
}

static bool gl_texture_update_from_buffer(struct wlr_texture *wlr_texture,
                                          struct wlr_buffer *buffer,
                                          const pixman_region32_t *damage)
{
    struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture);

    if (texture->drm_format == DRM_FORMAT_INVALID) {
        return false;
    }

    void *data;
    uint32_t format;
    size_t stride;
    if (!wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format,
                                          &stride)) {
        return false;
    }

    if (format != texture->drm_format) {
        wlr_buffer_end_data_ptr_access(buffer);
        return false;
    }

    const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(texture->drm_format);
    assert(fmt);

    if (ky_pixel_format_pixels_per_block(fmt) != 1) {
        wlr_buffer_end_data_ptr_access(buffer);
        kywc_log(KYWC_ERROR, "Cannot update texture: block formats are not supported");
        return false;
    }

    if (!ky_pixel_format_check_stride(fmt, stride, buffer->width)) {
        wlr_buffer_end_data_ptr_access(buffer);
        return false;
    }

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(texture->renderer->egl, &prev_ctx);

    ky_opengl_push_debug(texture->renderer);

    glBindTexture(GL_TEXTURE_2D, texture->tex);

    int rects_len = 0;
    const pixman_box32_t *rects = pixman_region32_rectangles(damage, &rects_len);

    for (int i = 0; i < rects_len; i++) {
        pixman_box32_t rect = rects[i];

        glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / fmt->bytes_per_block);
        glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1);
        glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1);

        int width = rect.x2 - rect.x1;
        int height = rect.y2 - rect.y1;
        glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, fmt->gl_format,
                        fmt->gl_type, data);
    }

    glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
    glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);

    glBindTexture(GL_TEXTURE_2D, 0);

    ky_opengl_pop_debug(texture->renderer);

    ky_egl_restore_context(&prev_ctx);

    wlr_buffer_end_data_ptr_access(buffer);

    return true;
}

void ky_opengl_texture_destroy(struct ky_opengl_texture *texture)
{
    wl_list_remove(&texture->link);
    if (texture->buffer) {
        wlr_buffer_unlock(texture->buffer);
    } else {
        struct ky_egl_context prev_ctx;
        ky_egl_make_current(texture->renderer->egl, &prev_ctx);

        ky_opengl_push_debug(texture->renderer);

        glDeleteTextures(1, &texture->tex);
        glDeleteFramebuffers(1, &texture->fbo);

        ky_opengl_pop_debug(texture->renderer);

        ky_egl_restore_context(&prev_ctx);
    }
    free(texture);
}

static void handle_gl_texture_destroy(struct wlr_texture *wlr_texture)
{
    ky_opengl_texture_destroy(ky_opengl_texture_from_wlr_texture(wlr_texture));
}

static bool gl_texture_bind(struct ky_opengl_texture *texture)
{
    if (texture->fbo) {
        glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo);
    } else if (texture->buffer) {
        struct ky_opengl_buffer *buffer = ky_opengl_buffer_get(texture->renderer, texture->buffer);
        if (!buffer) {
            return false;
        }
        if (buffer->external_only) {
            return false;
        }

        GLuint fbo = ky_opengl_buffer_get_fbo(buffer);
        if (!fbo) {
            return false;
        }

        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    } else {
        glGenFramebuffers(1, &texture->fbo);
        glBindFramebuffer(GL_FRAMEBUFFER, texture->fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture->target, texture->tex,
                               0);

        GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
            kywc_log(KYWC_ERROR, "Failed to create FBO");
            glDeleteFramebuffers(1, &texture->fbo);
            texture->fbo = 0;

            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            return false;
        }
    }

    return true;
}

static bool gl_texture_read_pixels(struct wlr_texture *wlr_texture,
                                   const struct wlr_texture_read_pixels_options *options)
{
    struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture);

    struct wlr_box src;
    wlr_texture_read_pixels_options_get_src_box(options, wlr_texture, &src);

    const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(options->format);
    if (fmt == NULL || !ky_opengl_pixel_format_is_supported(texture->renderer, fmt)) {
        kywc_log(KYWC_ERROR, "Cannot read pixels: unsupported pixel format 0x%" PRIX32,
                 options->format);
        return false;
    }

    if (fmt->gl_format == GL_BGRA_EXT && !texture->renderer->exts.EXT_read_format_bgra) {
        kywc_log(KYWC_ERROR, "Cannot read pixels: missing GL_EXT_read_format_bgra extension");
        return false;
    }

    if (ky_pixel_format_pixels_per_block(fmt) != 1) {
        kywc_log(KYWC_ERROR, "Cannot read pixels: block formats are not supported");
        return false;
    }

    struct ky_egl_context prev_ctx;
    if (!ky_egl_make_current(texture->renderer->egl, &prev_ctx)) {
        return false;
    }

    if (!gl_texture_bind(texture)) {
        return false;
    }

    ky_opengl_push_debug(texture->renderer);
    // Make sure any pending drawing is finished before we try to read it
    glFinish();

    glGetError(); // Clear the error flag

    unsigned char *p = wlr_texture_read_pixel_options_get_data(options);

    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    uint32_t pack_stride = ky_pixel_format_min_stride(fmt, src.width);
    if (pack_stride == options->stride && options->dst_x == 0) {
        // Under these particular conditions, we can read the pixels with only
        // one glReadPixels call
        glReadPixels(src.x, src.y, src.width, src.height, fmt->gl_format, fmt->gl_type, p);
    } else {
        // Unfortunately GLES2 doesn't support GL_PACK_ROW_LENGTH, so we have to read
        // the lines out row by row
        for (int32_t i = 0; i < src.height; ++i) {
            uint32_t y = src.y + i;
            glReadPixels(src.x, y, src.width, 1, fmt->gl_format, fmt->gl_type,
                         p + i * options->stride);
        }
    }

    ky_opengl_pop_debug(texture->renderer);

    return glGetError() == GL_NO_ERROR;
}

static uint32_t gl_texture_preferred_read_format(struct wlr_texture *wlr_texture)
{
    struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture);
    uint32_t fmt = DRM_FORMAT_INVALID;

    struct ky_egl_context prev_ctx;
    if (!ky_egl_make_current(texture->renderer->egl, &prev_ctx)) {
        return fmt;
    }

    if (!gl_texture_bind(texture)) {
        goto out;
    }

    ky_opengl_push_debug(texture->renderer);

    GLint gl_format = -1, gl_type = -1, alpha_size = -1;
    glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format);
    glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type);
    glGetIntegerv(GL_ALPHA_BITS, &alpha_size);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ky_opengl_pop_debug(texture->renderer);

    const struct ky_pixel_format *format =
        ky_pixel_format_from_gl(gl_format, gl_type, alpha_size > 0);
    if (format != NULL) {
        return format->drm_format;
    }

    if (texture->renderer->exts.EXT_read_format_bgra) {
        return DRM_FORMAT_XRGB8888;
    }
    return DRM_FORMAT_XBGR8888;

out:
    ky_egl_restore_context(&prev_ctx);
    return fmt;
}

static const struct wlr_texture_impl texture_impl = {
    .update_from_buffer = gl_texture_update_from_buffer,
    .read_pixels = gl_texture_read_pixels,
    .preferred_read_format = gl_texture_preferred_read_format,
    .destroy = handle_gl_texture_destroy,
};

struct ky_opengl_texture *ky_opengl_texture_create(struct ky_opengl_renderer *renderer,
                                                   uint32_t width, uint32_t height)
{
    struct ky_opengl_texture *texture = calloc(1, sizeof(*texture));
    if (texture == NULL) {
        kywc_log_errno(KYWC_ERROR, "Allocation failed");
        return NULL;
    }
    wlr_texture_init(&texture->wlr_texture, &renderer->wlr_renderer, &texture_impl, width, height);
    texture->renderer = renderer;
    wl_list_insert(&renderer->textures, &texture->link);
    return texture;
}

static struct wlr_texture *gl_texture_from_pixels(struct wlr_renderer *wlr_renderer,
                                                  uint32_t drm_format, uint32_t stride,
                                                  uint32_t width, uint32_t height, const void *data)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(drm_format);
    if (fmt == NULL) {
        kywc_log(KYWC_ERROR, "Unsupported pixel format 0x%" PRIX32, drm_format);
        return NULL;
    }

    if (ky_pixel_format_pixels_per_block(fmt) != 1) {
        kywc_log(KYWC_ERROR, "Cannot upload texture: block formats are not supported");
        return NULL;
    }

    if (!ky_pixel_format_check_stride(fmt, stride, width)) {
        return NULL;
    }

    struct ky_opengl_texture *texture = ky_opengl_texture_create(renderer, width, height);
    if (texture == NULL) {
        return NULL;
    }
    texture->target = GL_TEXTURE_2D;
    texture->has_alpha = fmt->has_alpha;
    texture->drm_format = fmt->drm_format;

    GLint internal_format = fmt->gl_internalformat;
    if (!internal_format) {
        /* on OpenGL ES there is a requirement that internalFormat == format */
        internal_format = fmt->gl_format;
    }
    /* fix the internal_format in OpenGL */
    if (!renderer->egl->is_gles) {
        if (internal_format == GL_BGRA_EXT || internal_format == GL_ABGR_EXT) {
            internal_format = GL_RGBA;
        } else if (internal_format == GL_BGR) {
            internal_format = GL_RGB;
        }
    }

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(renderer->egl, &prev_ctx);

    ky_opengl_push_debug(renderer);

    glGenTextures(1, &texture->tex);
    glBindTexture(GL_TEXTURE_2D, texture->tex);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / fmt->bytes_per_block);
    glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, fmt->gl_format, fmt->gl_type,
                 data);
    glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);

    glBindTexture(GL_TEXTURE_2D, 0);

    ky_opengl_pop_debug(renderer);

    ky_egl_restore_context(&prev_ctx);

    return &texture->wlr_texture;
}

static struct wlr_texture *gl_texture_from_dmabuf(struct ky_opengl_renderer *renderer,
                                                  struct wlr_buffer *wlr_buffer,
                                                  struct wlr_dmabuf_attributes *attribs)
{
    /* for glEGLImageTargetTexture2DOES */
    if (!renderer->exts.OES_egl_image) {
        return NULL;
    }

    struct ky_opengl_buffer *buffer = ky_opengl_buffer_get_or_create(renderer, wlr_buffer);
    if (!buffer) {
        return NULL;
    }

    struct ky_opengl_texture *texture =
        ky_opengl_texture_create(renderer, attribs->width, attribs->height);
    if (!texture) {
        return NULL;
    }

    const struct ky_pixel_format *fmt = ky_pixel_format_from_drm(attribs->format);

    texture->target = buffer->external_only ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
    texture->buffer = buffer->buffer;
    texture->drm_format = DRM_FORMAT_INVALID; // texture can't be written anyways
    texture->has_alpha = fmt ? fmt->has_alpha : true;

    struct ky_egl_context prev_ctx;
    ky_egl_make_current(renderer->egl, &prev_ctx);

    bool invalid;
    if (!buffer->tex) {
        glGenTextures(1, &buffer->tex);
        invalid = true;
    } else {
        // External changes are immediately made visible by the GL implementation
        invalid = !buffer->external_only;
    }

    if (invalid) {
        glBindTexture(texture->target, buffer->tex);
        glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glEGLImageTargetTexture2DOES(texture->target, buffer->image);
        glBindTexture(texture->target, 0);
    }

    ky_opengl_pop_debug(renderer);
    ky_egl_restore_context(&prev_ctx);

    texture->tex = buffer->tex;
    wlr_buffer_lock(texture->buffer);
    return &texture->wlr_texture;
}

struct wlr_texture *ky_opengl_texture_from_buffer(struct wlr_renderer *wlr_renderer,
                                                  struct wlr_buffer *buffer)
{
    struct ky_opengl_renderer *renderer = ky_opengl_renderer_from_wlr_renderer(wlr_renderer);

    void *data;
    uint32_t format;
    size_t stride;
    struct wlr_dmabuf_attributes dmabuf;
    if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) {
        return gl_texture_from_dmabuf(renderer, buffer, &dmabuf);
    } else if (wlr_buffer_is_wayland_buffer(buffer)) {
        return wlr_texture_from_wayland_buffer(renderer, buffer);
    } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data,
                                                &format, &stride)) {
        struct wlr_texture *tex = gl_texture_from_pixels(wlr_renderer, format, stride,
                                                         buffer->width, buffer->height, data);
        wlr_buffer_end_data_ptr_access(buffer);
        return tex;
    } else {
        return NULL;
    }
}

void ky_opengl_texture_get_attribs(struct wlr_texture *wlr_texture,
                                   struct ky_opengl_texture_attribs *attribs)
{
    struct ky_opengl_texture *texture = ky_opengl_texture_from_wlr_texture(wlr_texture);
    *attribs = (struct ky_opengl_texture_attribs){
        .target = texture->target,
        .tex = texture->tex,
        .has_alpha = texture->has_alpha,
    };
}
