/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Screen/framebuffer system calls
 * @license GPL-3.0-only
 */

// This is not part of POSIX or anything btw

#include <stdint.h>
#include <kernel.h>

extern struct boot_context boot_ctx;

/*
 * pack_rbga_to_fb - Convert rgb values to rgba pixel for framebuffer
 * @r: red value
 * @g: green value
 * @b: blue value
 *
 * This function uses the mask sizes and shifts of the Limine
 * framebuffer to convert raw rgb values into usable pixels.
 *
 * Return:
 * <pixel> - usable pixel
 */
static uint32_t pack_rgba_to_fb(uint8_t r, uint8_t g, uint8_t b)
{
    uint32_t pixel = 0;

    if (boot_ctx.fb->red_mask_size) {
        pixel |= ((uint32_t)(r >> (8 - boot_ctx.fb->red_mask_size)) << boot_ctx.fb->red_mask_shift);
    }
    if (boot_ctx.fb->green_mask_size) {
        pixel |= ((uint32_t)(g >> (8 - boot_ctx.fb->green_mask_size)) << boot_ctx.fb->green_mask_shift);
    }
    if (boot_ctx.fb->blue_mask_size) {
        pixel |= ((uint32_t)(b >> (8 - boot_ctx.fb->blue_mask_size)) << boot_ctx.fb->blue_mask_shift);
    }

    return pixel;
}

/*
 * sys_draw_fb - Draw a framebuffer subset
 * @src:      Source frame
 * @width:    width of the frame
 * @height:   height of the frame
 * @channels: how many channels (RGB, RGBA)
 *
 * This system call draws the frame @src, having some
 * @width and @height, in the top right of the Limine
 * framebuffer. (Used for DOOM)
 *
 * Return:
 * %0 - on success
 * On error, a negative value with an error code is returned.
 */
int sys_draw_fb(const uint8_t* src, int width, int height, int channels)
{
    if (!boot_ctx.fb || !src) {
        return -EINVAL;
    }
    if (channels != 4 || width <= 0 || height <= 0) {
        return -EINVAL;
    }
    if (boot_ctx.fb->bpp < 24) {
        return -EIO;
    }

    uint32_t* dst = (uint32_t*)boot_ctx.fb->address;
    uint64_t dst_w = boot_ctx.fb->width;
    uint64_t dst_h = boot_ctx.fb->height;
    uint64_t dst_pitch_px = boot_ctx.fb->pitch / 4;
    uint64_t scale = 2;
    uint64_t scaled_w = (uint64_t)width * scale;
    uint64_t scaled_h = (uint64_t)height * scale;

    if (scaled_w > dst_w || scaled_h > dst_h) {
        return -EINVAL;
    }

    uint64_t dst_x = dst_w - scaled_w;
    uint64_t dst_y = 0;

    for (uint64_t y = 0; y < (uint64_t)height; ++y) {
        const uint8_t* src_row = src + y * (uint64_t)width * 4ULL;
        uint32_t* dst_row0 = dst + (dst_y + y * scale) * dst_pitch_px + dst_x;
        uint32_t* dst_row1 = dst_row0 + dst_pitch_px;

        for (uint64_t x = 0; x < (uint64_t)width; ++x) {
            const uint8_t* p = src_row + x * 4ULL;
            uint32_t pixel = pack_rgba_to_fb(p[0], p[1], p[2]);
            uint64_t dx = x * scale;

            // Write 2x2 block. This is because source DOOM frame (320x200)
            // is quite small.
            dst_row0[dx + 0] = pixel;
            dst_row0[dx + 1] = pixel;
            dst_row1[dx + 0] = pixel;
            dst_row1[dx + 1] = pixel;
        }
    }

    return 0;
}