//! The display wire protocol — what a client says to the display service over its
//! well-known `.display` endpoint. extern-struct messages with an `Operation` tag, the
//! same shape as block/vfs/input protocols. The compositor owns the framebuffer and an
//! ordered stack of **layers**; a client creates layers, draws into them with these
//! operations, marks damage, and asks for a `present`. v1 surfaces are server-owned (a
//! client draws by command); shared-memory surfaces are a later milestone (docs/display.md).

const std = @import("std");

pub const Operation = enum(u32) {
    /// info() -> { width, height, pitch, format }: the display's current mode.
    info = 0,
    /// create_layer(x, y, width, height, z) -> { layer }: a new server-owned surface.
    create_layer = 1,
    /// configure_layer(layer, x, y, z, visible): move, restack, show, or hide a layer.
    configure_layer = 2,
    /// destroy_layer(layer): release a layer.
    destroy_layer = 3,
    /// fill_rect(layer, x, y, width, height, colour): fill a rectangle of a layer.
    fill_rect = 4,
    /// blit_tile(layer, x, y, width, height, <inline pixels>): copy a small pixel tile in.
    blit_tile = 5,
    /// damage(layer, x, y, width, height): mark a region dirty for the next present.
    damage = 6,
    /// present(): composite the dirty layers and flush to the screen.
    present = 7,
    /// attach_scanout(x=stride, y=refresh_hz, width, height, colour=format) + <surface
    /// capability>: a native scanout driver announces itself, handing over the shared scanout
    /// surface as an `ipc_call` send_cap. The compositor maps it, looks up the driver's
    /// `.scanout` present channel, and upgrades off the GOP floor (docs/display-v2.md V4).
    /// `x` is the surface's row stride in pixels, `y` the panel refresh rate from the
    /// driver's EDID read (0 = unknown; paces the compositor's frame clock), `colour` the
    /// DisplayFormat.
    attach_scanout = 8,
    /// set_mode(width, height): change the display resolution — only a native backend that
    /// reports `canModeSet` honours it; on the GOP floor it fails (docs/display-v2.md V5).
    set_mode = 9,
    /// get_modes() -> ModesReply: the resolutions the display can switch to (empty on GOP).
    get_modes = 10,
};

/// The fixed request header. A `blit_tile`'s pixel payload (width*height 32-bit pixels)
/// follows this header inline in the same message, up to `maximum_payload`.
pub const Request = extern struct {
    operation: u32,
    layer: u32 = 0, // create/configure/destroy/fill/blit/damage: the target layer
    x: u32 = 0,
    y: u32 = 0,
    width: u32 = 0,
    height: u32 = 0,
    z: u32 = 0, // create_layer / configure_layer: stacking order (higher = in front)
    colour: u32 = 0, // fill_rect: the fill colour (native pixel value)
    visible: u32 = 1, // configure_layer: 0 hides the layer
    reserved: u32 = 0,
};

pub const Reply = extern struct {
    status: i32, // 0 on success, negative on failure
    reserved: u32 = 0,
    // info():
    width: u32 = 0,
    height: u32 = 0,
    pitch: u32 = 0,
    format: u32 = 0, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx)
    // create_layer():
    layer: u32 = 0,
    reserved2: u32 = 0,
};

/// One selectable display mode.
pub const Mode = extern struct { width: u32, height: u32 };
pub const max_modes = 4;

/// The reply to `get_modes`: a small fixed list of resolutions the display can switch to.
pub const ModesReply = extern struct {
    status: i32,
    count: u32,
    modes: [max_modes]Mode,
};
pub const modes_reply_size: usize = @sizeOf(ModesReply);

/// The IPC message size — the kernel caps every message at `MESSAGE_MAXIMUM` (256 bytes,
/// system/kernel/ipc-synchronous.zig), so this matches it (a larger receive/reply buffer
/// is rejected with -E2BIG). A `blit_tile` therefore carries only a *small* tile inline —
/// `maximum_payload` bytes = up to 54 pixels, enough for a cursor or small sprite; larger
/// bitmaps are the deferred shared-memory surface path (docs/display.md).
pub const message_maximum: usize = 256;
pub const request_size: usize = @sizeOf(Request);
pub const reply_size: usize = @sizeOf(Reply);
pub const maximum_payload: usize = message_maximum - request_size;

/// Pack an 8-bit-per-channel colour into the display's native 32-bit pixel for `format`
/// (a device-abi `DisplayFormat`: 0 = rgbx, 1 = bgrx). Shared so a `colour` in a
/// `fill_rect` request means the same thing to the client that sends it and the
/// compositor that paints it. Little-endian memory, reserved byte 0: rgbx puts red in
/// the low byte, bgrx puts blue there.
pub fn pack(format: u32, r: u8, g: u8, b: u8) u32 {
    const rr: u32 = r;
    const gg: u32 = g;
    const bb: u32 = b;
    return switch (format) {
        1 => bb | (gg << 8) | (rr << 16), // bgrx
        else => rr | (gg << 8) | (bb << 16), // rgbx
    };
}

test "pack encodes native byte order for rgbx and bgrx" {
    // rgbx: red in the low byte, blue in byte 2.
    try std.testing.expectEqual(@as(u32, 0x0000_00AA), pack(0, 0xAA, 0, 0));
    try std.testing.expectEqual(@as(u32, 0x00AA_0000), pack(0, 0, 0, 0xAA));
    // bgrx: blue in the low byte, red in byte 2.
    try std.testing.expectEqual(@as(u32, 0x0000_00AA), pack(1, 0, 0, 0xAA));
    try std.testing.expectEqual(@as(u32, 0x00AA_0000), pack(1, 0xAA, 0, 0));
    try std.testing.expectEqual(@as(u32, 0x0000_3020), pack(0, 0x20, 0x30, 0)); // green in byte 1
}
