//! The input wire protocol — the message format spoken between the user-space input
//! service ([input.zig](input.zig)) and the two kinds of process that reach it: a
//! **source** (a keyboard, mouse, or joystick/gamepad driver) that publishes events, and a
//! **subscriber** (any program) that subscribes and is then pushed each event.
//!
//! The service handles several device classes over one endpoint. Each class has its own
//! typed event (`KeyEvent`, `MouseEvent`, `JoystickEvent`); they all travel in a common
//! `InputEvent` envelope tagged with a `DeviceKind`, so the fan-out path is one code path
//! and a subscriber can take a mix of devices on a single stream. A subscriber declares
//! which classes it wants with a `device_mask`, and the service routes accordingly.
//!
//! Two message shapes ride over the endpoint, tagged by `Operation`, like the
//! [VFS protocol](../vfs/protocol.zig):
//!
//! - **subscribe / publish**: a synchronous `ipc_call` carrying a `Request`. `subscribe`
//!   hands the service the subscriber's own endpoint as a capability (`send_cap`) and a
//!   `device_mask`; `publish` carries an `InputEvent`. The reply is a `Reply`.
//! - **delivery**: the service pushes each `InputEvent` to every interested subscriber with
//!   the asynchronous `ipc_send` — no reply owed, and a dead subscriber can never stall the
//!   broadcast. Received in the subscriber's buffer with `Received.isMessage()` set.
//!
//! This is a danos-native contract, shared by the input service, the `runtime.input`
//! client helpers, and every source/subscriber. Everything fits one IPC message.

const std = @import("std");

/// The classes of input device the service fans out. Each names a typed event and a bit in
/// the subscription mask.
pub const DeviceKind = enum(u32) {
    keyboard = 0,
    mouse = 1,
    joystick = 2, // joysticks and gamepads/controllers
};

/// Subscription-interest bits (`Request.device_mask`) — which device classes a subscriber
/// wants. OR them together, or use `device_all`.
pub const device_keyboard: u32 = 1 << 0;
pub const device_mouse: u32 = 1 << 1;
pub const device_joystick: u32 = 1 << 2;
pub const device_all: u32 = device_keyboard | device_mouse | device_joystick;

/// The subscription bit for a `DeviceKind` value (as it appears in `InputEvent.device`).
/// An unknown device maps to 0, so it matches no subscriber.
pub fn deviceBit(device: u32) u32 {
    return switch (device) {
        @intFromEnum(DeviceKind.keyboard) => device_keyboard,
        @intFromEnum(DeviceKind.mouse) => device_mouse,
        @intFromEnum(DeviceKind.joystick) => device_joystick,
        else => 0,
    };
}

// --- keyboard ---------------------------------------------------------------

/// What happened to a key. `key_down`/`key_up` are the physical make/break; `key_press`
/// is the higher-level "a character was produced", carrying it in `KeyEvent.character`.
pub const EventKind = enum(u32) {
    key_down = 0,
    key_up = 1,
    key_press = 2,
};

/// One keyboard event. `keycode` names the physical key (layout-independent); `character`
/// is the Unicode scalar for `key_press` (else 0); `modifiers` is an OR of `modifier_*`.
pub const KeyEvent = extern struct {
    kind: u32, // an EventKind
    keycode: u32, // a Keycode
    character: u32, // Unicode scalar for key_press, else 0
    modifiers: u32, // OR of modifier_*
};

pub const modifier_shift: u32 = 1 << 0;
pub const modifier_control: u32 = 1 << 1;
pub const modifier_alt: u32 = 1 << 2;

/// The danos-native keycode namespace: USB HID keyboard-page usages (page 0x07), the
/// numbering the PS/2 scancode decoder emits and the xkeyboard-config layout tables are
/// indexed by. Non-exhaustive, so an unnamed usage still travels as a valid value.
pub const Keycode = enum(u32) {
    unknown = 0,
    // letters
    a = 4,
    b = 5,
    c = 6,
    d = 7,
    e = 8,
    f = 9,
    g = 10,
    h = 11,
    i = 12,
    j = 13,
    k = 14,
    l = 15,
    m = 16,
    n = 17,
    o = 18,
    p = 19,
    q = 20,
    r = 21,
    s = 22,
    t = 23,
    u = 24,
    v = 25,
    w = 26,
    x = 27,
    y = 28,
    z = 29,
    // digit row
    one = 30,
    two = 31,
    three = 32,
    four = 33,
    five = 34,
    six = 35,
    seven = 36,
    eight = 37,
    nine = 38,
    zero = 39,
    // control and whitespace
    enter = 40,
    escape = 41,
    backspace = 42,
    tab = 43,
    spacebar = 44,
    // punctuation
    minus = 45,
    equal = 46,
    left_bracket = 47,
    right_bracket = 48,
    backslash = 49,
    non_us_hash = 50,
    semicolon = 51,
    apostrophe = 52,
    grave = 53,
    comma = 54,
    period = 55,
    slash = 56,
    caps_lock = 57,
    // function row
    f1 = 58,
    f2 = 59,
    f3 = 60,
    f4 = 61,
    f5 = 62,
    f6 = 63,
    f7 = 64,
    f8 = 65,
    f9 = 66,
    f10 = 67,
    f11 = 68,
    f12 = 69,
    print_screen = 70,
    scroll_lock = 71,
    pause = 72,
    // navigation
    insert = 73,
    home = 74,
    page_up = 75,
    delete = 76,
    end = 77,
    page_down = 78,
    right_arrow = 79,
    left_arrow = 80,
    down_arrow = 81,
    up_arrow = 82,
    // keypad
    num_lock = 83,
    keypad_slash = 84,
    keypad_asterisk = 85,
    keypad_minus = 86,
    keypad_plus = 87,
    keypad_enter = 88,
    keypad_one = 89,
    keypad_two = 90,
    keypad_three = 91,
    keypad_four = 92,
    keypad_five = 93,
    keypad_six = 94,
    keypad_seven = 95,
    keypad_eight = 96,
    keypad_nine = 97,
    keypad_zero = 98,
    keypad_period = 99,
    non_us_backslash = 100,
    application = 101,
    // modifiers
    left_control = 224,
    left_shift = 225,
    left_alt = 226,
    left_gui = 227,
    right_control = 228,
    right_shift = 229,
    right_alt = 230,
    right_gui = 231,
    _,
};

// --- mouse ------------------------------------------------------------------

/// What a mouse event reports. `motion` carries relative `dx`/`dy`; `button_down`/`up`
/// name a button in `button`; `scroll` carries `scroll_x`/`scroll_y`.
pub const MouseEventKind = enum(u32) {
    motion = 0,
    button_down = 1,
    button_up = 2,
    scroll = 3,
};

pub const mouse_button_left: u32 = 1 << 0;
pub const mouse_button_right: u32 = 1 << 1;
pub const mouse_button_middle: u32 = 1 << 2;

/// One mouse event. Relative motion (`dx`/`dy`) and wheel (`scroll_*`) are signed;
/// `buttons` is the current pressed-button bitmask (`mouse_button_*`).
pub const MouseEvent = extern struct {
    kind: u32, // a MouseEventKind
    button: u32, // the mouse_button_* bit for button_down/up, else 0
    dx: i32, // relative X motion (.motion)
    dy: i32, // relative Y motion (.motion)
    scroll_x: i32, // horizontal wheel (.scroll)
    scroll_y: i32, // vertical wheel (.scroll)
    buttons: u32, // current pressed-button bitmask
};

// --- joystick / gamepad -----------------------------------------------------

/// What a joystick/gamepad event reports. `axis` carries a signed `value` on axis
/// `control`; `button_down`/`up` name a button index in `control`.
pub const JoystickEventKind = enum(u32) {
    axis = 0,
    button_down = 1,
    button_up = 2,
};

/// One joystick/gamepad event. `control` is the axis index (`.axis`) or button index
/// (button events); `value` is the axis position (signed, e.g. -32768..32767) for `.axis`;
/// `buttons` is the current pressed-button bitmask.
pub const JoystickEvent = extern struct {
    kind: u32, // a JoystickEventKind
    control: u32, // axis index (.axis) or button index (button events)
    value: i32, // axis value for .axis, else 0
    buttons: u32, // current pressed-button bitmask
};

// --- the common envelope ----------------------------------------------------

/// The largest per-device event, so `InputEvent` can hold any of them inline.
pub const max_event_size: usize = @max(@sizeOf(KeyEvent), @max(@sizeOf(MouseEvent), @sizeOf(JoystickEvent)));

/// The tagged envelope broadcast to subscribers: a `DeviceKind` plus the raw bytes of the
/// matching per-device event. Decode it with `asKeyboard`/`asMouse`/`asJoystick` (each
/// returns null unless `device` matches), or build one with the `from*` constructors.
pub const InputEvent = extern struct {
    device: u32, // a DeviceKind
    _padding: u32 = 0,
    data: [max_event_size]u8 = [_]u8{0} ** max_event_size,

    pub fn asKeyboard(self: InputEvent) ?KeyEvent {
        if (self.device != @intFromEnum(DeviceKind.keyboard)) return null;
        return std.mem.bytesToValue(KeyEvent, self.data[0..@sizeOf(KeyEvent)]);
    }
    pub fn asMouse(self: InputEvent) ?MouseEvent {
        if (self.device != @intFromEnum(DeviceKind.mouse)) return null;
        return std.mem.bytesToValue(MouseEvent, self.data[0..@sizeOf(MouseEvent)]);
    }
    pub fn asJoystick(self: InputEvent) ?JoystickEvent {
        if (self.device != @intFromEnum(DeviceKind.joystick)) return null;
        return std.mem.bytesToValue(JoystickEvent, self.data[0..@sizeOf(JoystickEvent)]);
    }

    pub fn fromKeyboard(event: KeyEvent) InputEvent {
        return pack(.keyboard, std.mem.asBytes(&event));
    }
    pub fn fromMouse(event: MouseEvent) InputEvent {
        return pack(.mouse, std.mem.asBytes(&event));
    }
    pub fn fromJoystick(event: JoystickEvent) InputEvent {
        return pack(.joystick, std.mem.asBytes(&event));
    }

    fn pack(device: DeviceKind, bytes: []const u8) InputEvent {
        var self = InputEvent{ .device = @intFromEnum(device) };
        @memcpy(self.data[0..bytes.len], bytes);
        return self;
    }
};

// --- request / reply --------------------------------------------------------

/// Which side of a request this is.
pub const Operation = enum(u32) {
    subscribe = 0, // register the caller's endpoint (send_cap) for the classes in device_mask
    publish = 1, // a source submits `event` to broadcast to interested subscribers
};

/// Request header. For `subscribe`, `device_mask` is the OR of `device_*` bits the caller
/// wants (0 means all) and the caller's receive endpoint travels as the call's capability;
/// `event` is ignored. For `publish`, `event` is the event to broadcast.
pub const Request = extern struct {
    operation: u32, // an Operation
    device_mask: u32 = 0, // subscribe: interested device classes (0 => all)
    event: InputEvent = .{ .device = 0 },
};

/// Reply header. `status` is 0 on success or a negative errno.
pub const Reply = extern struct {
    status: i32,
    _padding: u32 = 0,
};

pub const request_size: usize = @sizeOf(Request);
pub const reply_size: usize = @sizeOf(Reply);
pub const event_size: usize = @sizeOf(InputEvent);

comptime {
    // The delivery path posts a bare InputEvent through ipc_send, so it must fit an
    // endpoint's async payload slot (POST_MAXIMUM is 64).
    if (event_size > 64) @compileError("InputEvent must fit the ipc_send payload (POST_MAXIMUM)");
}
