//! The display wire protocol — what a client says to the display service over
//! `/protocol/display`. 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).
//!
//! **`Header.target` is the layer** on every verb that names one — the field that used to be
//! `Request.layer`. `info`, `present`, `set_mode`, `get_modes` and `attach_scanout` address
//! the compositor itself, so they leave it 0.
//!
//! Every verb carries its own request type. The single overloaded 40-byte request this
//! protocol used to have is gone, and with it the field abuse it invited: `attach_scanout`
//! spent `x` on a stride, `y` on a refresh rate and `colour` on a pixel format, which no
//! reader could have guessed and no compiler could have caught.

const envelope = @import("envelope");
const std = @import("std");

/// The answer to `info()`: the display's current mode.
pub const Info = extern struct {
    width: u32 = 0,
    height: u32 = 0,
    pitch: u32 = 0, // bytes per row (may exceed width*4)
    format: u32 = 0, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx)
};

/// `create_layer(...)`: a new server-owned surface. Coordinates are signed — a layer may sit
/// partly off-screen.
pub const CreateLayer = extern struct {
    x: i32,
    y: i32,
    width: u32,
    height: u32,
    z: u32 = 0, // stacking order (higher = nearer the front)
    visible: u32 = 1,
};

/// The layer a `create_layer` established — the integer later packets put in `Header.target`.
pub const Created = extern struct { layer: u32 };

/// `configure_layer(...)` on `Header.target`: move, restack, show, or hide it.
pub const ConfigureLayer = extern struct {
    x: i32,
    y: i32,
    z: u32 = 0,
    visible: u32 = 1, // 0 hides the layer
};

/// `fill_rect(...)` on `Header.target`: fill a layer-local rectangle with a native pixel value.
pub const FillRect = extern struct {
    x: i32,
    y: i32,
    width: u32,
    height: u32,
    colour: u32,
};

/// `blit_tile(...)` on `Header.target`: copy a `width`×`height` tile of native pixels
/// (row-major, little-endian) into the layer. The pixels ride inline as the packet's tail,
/// up to `maximum_payload`.
pub const BlitTile = extern struct {
    x: i32,
    y: i32,
    width: u32,
    height: u32,
};

/// `damage(...)` on `Header.target`: mark a layer-local region dirty for the next present.
pub const Damage = extern struct {
    x: i32,
    y: i32,
    width: u32,
    height: u32,
};

/// `attach_scanout(...)` + the shared surface as the call's capability: a native scanout
/// driver announces itself. The compositor maps the surface, opens the driver's
/// `/protocol/scanout` present channel, and upgrades off the GOP floor (docs/display-v2.md
/// V4). Each field says what it is, which the old shared request could not.
pub const AttachScanout = extern struct {
    /// The surface's row stride in pixels (it is sized to the driver's largest mode).
    stride: u32,
    /// The active mode within that surface.
    width: u32,
    height: u32,
    /// A device-abi DisplayFormat value.
    format: u32,
    /// The panel refresh rate from the driver's EDID read (0 = unknown); it paces the
    /// compositor's frame clock.
    refresh_hz: u32 = 0,
};

/// `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).
pub const SetMode = extern struct { width: u32, height: u32 };

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

/// The answer to `get_modes`: the resolutions the display can switch to (empty on GOP).
pub const Modes = extern struct {
    count: u32 = 0,
    _padding: u32 = 0,
    modes: [max_modes]Mode = @splat(.{ .width = 0, .height = 0 }),
};

pub const Protocol = envelope.Define(.{
    .name = "display",
    .version = 1,
    .operations = &.{
        .{ .name = "info", .reply = Info },
        .{ .name = "create_layer", .request = CreateLayer, .reply = Created },
        .{ .name = "configure_layer", .request = ConfigureLayer },
        .{ .name = "destroy_layer" },
        .{ .name = "fill_rect", .request = FillRect },
        .{ .name = "blit_tile", .request = BlitTile },
        .{ .name = "damage", .request = Damage },
        .{ .name = "present" },
        .{ .name = "attach_scanout", .request = AttachScanout },
        .{ .name = "set_mode", .request = SetMode },
        .{ .name = "get_modes", .reply = Modes },
    },
});

pub const Operation = Protocol.Operation;
pub const message_maximum: usize = Protocol.message_maximum;

/// The largest inline pixel tile a `blit_tile` may carry: the call floor less the header and
/// this verb's own fixed part — 224 bytes, up to 56 pixels, enough for a cursor or a small
/// sprite. Larger bitmaps are the deferred shared-memory surface path (docs/display.md).
/// Per-verb rather than protocol-wide, because with per-operation requests there is no
/// single "request size" to subtract any more.
pub const maximum_payload: usize = envelope.packet_maximum - envelope.prefix_size - @sizeOf(BlitTile);

/// 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
}

test "the layer rides the header, and the blit tile grew with the split" {
    var buffer: [message_maximum]u8 = undefined;
    const pixels = [_]u8{0xFF} ** 16;
    const packet = Protocol.encodeRequest(.blit_tile, 3, .{ .x = 1, .y = 2, .width = 2, .height = 2 }, &pixels, &buffer).?;
    try std.testing.expectEqual(@as(u64, 3), envelope.headerOf(packet).?.target);
    try std.testing.expectEqual(@as(i32, 1), Protocol.decodeRequest(.blit_tile, packet).?.x);
    try std.testing.expectEqual(@as(usize, 16), Protocol.requestTail(.blit_tile, packet).len);
    // 216 bytes under the old 40-byte shared request; the header plus this
    // verb's own four fields is 32.
    try std.testing.expectEqual(@as(usize, 224), maximum_payload);
}
