// SPDX-FileCopyrightText: 2026 Ben Buhse <me@benbuhse.email>
//
// SPDX-License-Identifier: GPL-3.0-only

const Buffer = @This();

width: u31,
height: u31,
stride: u31,

busy: bool,
size: u31,
data: ?[]align(std.heap.page_size_min) u8,

wl_buffer: *wl.Buffer,
pixman_image: *pixman.Image,

/// Used to add Buffers to a BufferPool
node: std.DoublyLinkedList.Node = .{},

pub fn init(shm: *wl.Shm, width: u31, height: u31) !Buffer {
    // We use argb8888
    const stride = width * 4;
    const size: u31 = height * stride;

    log.debug("initializing a new buffer with size {d}", .{size});

    // Open a memory-backed file with sealing enabled
    const fd = switch (builtin.target.os.tag) {
        .linux => try posix.memfd_createZ("beansprout-shm-buffer", os.linux.MFD.CLOEXEC | os.linux.MFD.ALLOW_SEALING),
        .freebsd => try posix.memfd_createZ("beansprout-shm-buffer", std.c.MFD.CLOEXEC | std.c.MFD.ALLOW_SEALING),
        else => @compileError("target OS not supported"),
    };
    defer posix.close(fd);

    // Try to allocate it to the desired size
    try posix.ftruncate(fd, size);

    // mmap the memory file for the pixman image
    const data = mem.bytesAsSlice(
        u8,
        try posix.mmap(null, size, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0),
    );
    errdefer posix.munmap(data);

    // Seal the fd to prevent size changes. The compositor maps the same fd,
    // so without sealing it could access invalid memory if the client resized it.
    _ = try posix.fcntl(fd, seal.F_ADD_SEALS, seal.SEAL_GROW | seal.SEAL_SHRINK | seal.SEAL_SEAL);

    // Create a Wayland shm buffer for the same memory file.
    const pool = try shm.createPool(fd, size);
    defer pool.destroy();

    const wl_buffer = try pool.createBuffer(0, width, height, stride, .argb8888);
    errdefer wl_buffer.destroy();

    // Create the pixman image.
    const pixman_image = pixman.Image.createBitsNoClear(
        .a8r8g8b8,
        @as(c_int, @intCast(width)),
        @as(c_int, @intCast(height)),
        @as([*c]u32, @ptrCast(data)),
        @as(c_int, @intCast(stride)),
    ) orelse return error.FailedToCreatePixmanImage;

    // The pixman image and the Wayland buffer now share the same memory.
    return .{
        .width = width,
        .height = height,
        .stride = stride,
        .busy = true,
        .size = size,
        .wl_buffer = wl_buffer,
        .data = data,
        .pixman_image = pixman_image,
    };
}

/// Re-initialize this buffer with new dimensions, preserving its position in
/// any intrusive linked list. Equivalent to deinit + init but keeps node intact.
pub fn reinit(buffer: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void {
    const saved_node = buffer.node;
    buffer.deinit();
    buffer.* = try Buffer.init(shm, width, height);
    buffer.node = saved_node;
}

pub fn deinit(buffer: *Buffer) void {
    _ = buffer.pixman_image.unref();
    buffer.wl_buffer.destroy();
    if (buffer.data) |data| posix.munmap(data);
}

// We have to do this later because of the way init() works
pub fn setListener(buffer: *Buffer) void {
    buffer.wl_buffer.setListener(*Buffer, bufferListener, buffer);
}

fn bufferListener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void {
    switch (event) {
        .release => buffer.busy = false,
    }
}

pub fn borderedRectangle(
    buffer: Buffer,
    rect: utils.Rect,
    border_width: u31,
    scale: u31,
    background_color: *const pixman.Color,
    border_color: *const pixman.Color,
) void {
    const render_x: i16 = @intCast(rect.x * @as(i32, scale));
    const render_y: i16 = @intCast(rect.y * @as(i32, scale));
    const render_width: u16 = @intCast(rect.width * scale);
    const render_height: u16 = @intCast(rect.height * scale);
    const render_border_width: u16 = @intCast(border_width * scale);

    // Background fill
    _ = pixman.Image.fillRectangles(.src, buffer.pixman_image, background_color, 1, &[1]pixman.Rectangle16{.{ .x = render_x, .y = render_y, .width = render_width, .height = render_height }});

    // Border: top, bottom, left, right
    _ = pixman.Image.fillRectangles(.src, buffer.pixman_image, border_color, 4, &[4]pixman.Rectangle16{
        .{
            .x = render_x,
            .y = render_y,
            .width = render_width,
            .height = render_border_width,
        },
        .{
            .x = render_x,
            .y = render_y + @as(i16, @intCast(render_height -
                render_border_width)),
            .width = render_width,
            .height = render_border_width,
        },
        .{ .x = render_x, .y = render_y + @as(i16, @intCast(render_border_width)), .width = render_border_width, .height = render_height - 2 * render_border_width },
        .{ .x = render_x + @as(i16, @intCast(render_width - render_border_width)), .y = render_y + @as(i16, @intCast(render_border_width)), .width = render_border_width, .height = render_height - 2 * render_border_width },
    });
}

const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const os = std.os;
const posix = std.posix;

const wayland = @import("wayland");
const wl = wayland.client.wl;
const pixman = @import("pixman");

const utils = @import("utils.zig");

/// Sealing constants for memfd. Prevents the compositor from accessing
/// invalid memory by locking the fd's size after setup.
const seal = switch (builtin.target.os.tag) {
    .linux => struct {
        // Linux values are missing from stdlib right now,
        // just take the values from fcntl.h
        const F_ADD_SEALS: i32 = 1033;
        const SEAL_SEAL: usize = 0x0001;
        const SEAL_SHRINK: usize = 0x0002;
        const SEAL_GROW: usize = 0x0004;
    },
    .freebsd => struct {
        const F_ADD_SEALS = std.c.F.ADD_SEALS;
        const SEAL_SEAL = std.c.F.SEAL_SEAL;
        const SEAL_SHRINK = std.c.F.SEAL_SHRINK;
        const SEAL_GROW = std.c.F.SEAL_GROW;
    },
    else => @compileError("target OS not supported"),
};
comptime {
    if (@hasField(os.linux.F, "SEAL_SEAL")) {
        @compileError("SEAL_SEAL added to std.os.linux, get rid of the hardcoded values above.");
    }
}

const log = std.log.scoped(.Buffer);
