//! User-space input helpers: the client and publisher sides of the input service, so a
//! program listening for input events — or a driver broadcasting them — doesn't hand-roll
//! the IPC. Layered over `ipc` (endpoints, capability passing, `send`) and the shared
//! `input-protocol` wire format, the way `device.zig` layers over the raw `device_*` calls.
//! See system/services/input/input.zig.
//!
//! The service carries several device classes (keyboard, mouse, joystick/gamepad). A
//! **source** publishes its class with the matching method:
//!     var source = input.connectSource() orelse return;
//!     _ = source.publishKeyboardEvent(.{ .kind = ..., .keycode = ..., ... });
//!     _ = source.publishMouseEvent(.{ ... });
//!     _ = source.publishJoystickEvent(.{ ... });
//!
//! A **subscriber** either takes one class with a typed helper —
//!     var keys = input.subscribeKeyboard() orelse return;
//!     while (true) { const key = keys.next() orelse continue; ... }
//! — or takes several at once and inspects the tagged envelope:
//!     var listener = input.subscribeAll() orelse return;
//!     while (true) {
//!         const event = listener.next() orelse continue;
//!         if (event.asKeyboard()) |k| { ... } else if (event.asMouse()) |m| { ... }
//!     }

const std = @import("std");
const abi = @import("abi");
const ipc = @import("ipc.zig");
const system = @import("system.zig");
const protocol = @import("input-protocol");

pub const DeviceKind = protocol.DeviceKind;
pub const InputEvent = protocol.InputEvent;
pub const KeyEvent = protocol.KeyEvent;
pub const MouseEvent = protocol.MouseEvent;
pub const JoystickEvent = protocol.JoystickEvent;
pub const EventKind = protocol.EventKind;
pub const MouseEventKind = protocol.MouseEventKind;
pub const JoystickEventKind = protocol.JoystickEventKind;
pub const Keycode = protocol.Keycode;

/// Interest masks re-exported so a caller can `subscribe(input.device_keyboard |
/// input.device_mouse)`.
pub const device_keyboard = protocol.device_keyboard;
pub const device_mouse = protocol.device_mouse;
pub const device_joystick = protocol.device_joystick;
pub const device_all = protocol.device_all;

/// Look up the input service, retrying while it is still coming up. Both a subscriber and
/// a source race the service's registration at boot, so both wait for it here rather than
/// failing. Returns the service endpoint handle, or null if it never appears.
fn lookupService() ?ipc.Handle {
    var attempts: usize = 0;
    while (attempts < 100) : (attempts += 1) {
        if (ipc.lookup(.input)) |handle| return handle;
        system.sleep(50);
    }
    return null;
}

// --- subscribing ------------------------------------------------------------

/// A subscription to the input service: our own endpoint, which the service pushes events
/// to. `next` returns each event as a tagged `InputEvent`; use `asKeyboard`/`asMouse`/
/// `asJoystick` to decode. Created with `subscribe`/`subscribeAll`; for a single device
/// class prefer the typed helpers (`subscribeKeyboard`, ...), which return decoded events.
pub const Subscriber = struct {
    /// The endpoint the service delivers events to (created and owned by us; its handle
    /// was handed to the service as a capability at subscribe time).
    endpoint: ipc.Handle,
    receive: [protocol.event_size]u8 = undefined,

    /// Block until the next event is pushed, and return it. Events arrive as asynchronous
    /// buffered messages (`ipc_send` from the service), so nothing is owed in reply — the
    /// empty reply this issues is a harmless no-op. Returns null for any non-event wake-up
    /// (there should be none), so callers can loop.
    pub fn next(self: *Subscriber) ?InputEvent {
        const got = ipc.replyWait(self.endpoint, &.{}, &self.receive, null);
        if (!got.isMessage() or got.len < protocol.event_size) return null;
        return std.mem.bytesToValue(InputEvent, self.receive[0..protocol.event_size]);
    }
};

/// Subscribe to the input classes named in `device_mask` (an OR of `device_*`, or
/// `device_all`). Creates an endpoint for the service to push to and hands it over as a
/// capability. Returns a `Subscriber` to loop `next` on, or null on failure.
pub fn subscribe(device_mask: u32) ?Subscriber {
    const service = lookupService() orelse return null;
    const endpoint = ipc.createIpcEndpoint() orelse return null;

    var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.subscribe), .device_mask = device_mask };
    var reply: [protocol.reply_size]u8 = undefined;
    const result = ipc.callCap(service, std.mem.asBytes(&request), &reply, endpoint) catch return null;
    if (result.len < protocol.reply_size) return null;
    if (std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status != 0) return null;
    return .{ .endpoint = endpoint };
}

/// Subscribe to every input class (keyboard, mouse, joystick) on one stream.
pub fn subscribeAll() ?Subscriber {
    return subscribe(device_all);
}

/// A subscriber filtered to keyboard events, whose `next` returns a decoded `KeyEvent`.
pub const KeyboardSubscriber = struct {
    inner: Subscriber,
    pub fn next(self: *KeyboardSubscriber) ?KeyEvent {
        return (self.inner.next() orelse return null).asKeyboard();
    }
};

/// A subscriber filtered to mouse events, whose `next` returns a decoded `MouseEvent`.
pub const MouseSubscriber = struct {
    inner: Subscriber,
    pub fn next(self: *MouseSubscriber) ?MouseEvent {
        return (self.inner.next() orelse return null).asMouse();
    }
};

/// A subscriber filtered to joystick/gamepad events, whose `next` returns a decoded
/// `JoystickEvent`.
pub const JoystickSubscriber = struct {
    inner: Subscriber,
    pub fn next(self: *JoystickSubscriber) ?JoystickEvent {
        return (self.inner.next() orelse return null).asJoystick();
    }
};

/// Subscribe to keyboard events only; `next` returns decoded `KeyEvent`s.
pub fn subscribeKeyboard() ?KeyboardSubscriber {
    return .{ .inner = subscribe(device_keyboard) orelse return null };
}

/// Subscribe to mouse events only; `next` returns decoded `MouseEvent`s.
pub fn subscribeMouse() ?MouseSubscriber {
    return .{ .inner = subscribe(device_mouse) orelse return null };
}

/// Subscribe to joystick/gamepad events only; `next` returns decoded `JoystickEvent`s.
pub fn subscribeJoystick() ?JoystickSubscriber {
    return .{ .inner = subscribe(device_joystick) orelse return null };
}

// --- publishing -------------------------------------------------------------

/// A connection to the input service for a source (a keyboard/mouse/joystick driver) that
/// publishes events. Each `publish*Event` is a short synchronous call the service answers
/// at once; its own fan-out to subscribers is asynchronous, so publishing never blocks on
/// a slow subscriber.
pub const Publisher = struct {
    service: ipc.Handle,

    fn publish(self: Publisher, event: InputEvent) bool {
        var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.publish), .event = event };
        var reply: [protocol.reply_size]u8 = undefined;
        const len = ipc.call(self.service, std.mem.asBytes(&request), &reply) catch return false;
        if (len < protocol.reply_size) return false;
        return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0;
    }

    /// Broadcast a keyboard event to every subscriber that took keyboard events.
    pub fn publishKeyboardEvent(self: Publisher, event: KeyEvent) bool {
        return self.publish(InputEvent.fromKeyboard(event));
    }
    /// Broadcast a mouse event to every subscriber that took mouse events.
    pub fn publishMouseEvent(self: Publisher, event: MouseEvent) bool {
        return self.publish(InputEvent.fromMouse(event));
    }
    /// Broadcast a joystick/gamepad event to every subscriber that took joystick events.
    pub fn publishJoystickEvent(self: Publisher, event: JoystickEvent) bool {
        return self.publish(InputEvent.fromJoystick(event));
    }
};

/// Connect to the input service as an event source, waiting for it to come up. Returns a
/// `Publisher`, or null if the service never registered.
pub fn connectSource() ?Publisher {
    return .{ .service = lookupService() orelse return null };
}

// --- synthetic scaffolding --------------------------------------------------

/// Synthetic key events, shared by the demo source and the keyboard driver's placeholder
/// stream while real scancode decoding is still a follow-up. `step` rolls through A..E,
/// emitting for each key a `key_down`, then a `key_press` carrying the character, then a
/// `key_up`. Scaffolding, not wire protocol — hence it lives with the helpers.
pub fn syntheticKeyEvent(step: usize) KeyEvent {
    const Key = struct { code: Keycode, character: u32 };
    const keys = [_]Key{
        .{ .code = .a, .character = 'A' },
        .{ .code = .b, .character = 'B' },
        .{ .code = .c, .character = 'C' },
        .{ .code = .d, .character = 'D' },
        .{ .code = .e, .character = 'E' },
    };
    const key = keys[(step / 3) % keys.len];
    return switch (step % 3) {
        0 => .{ .kind = @intFromEnum(EventKind.key_down), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
        1 => .{ .kind = @intFromEnum(EventKind.key_press), .keycode = @intFromEnum(key.code), .character = key.character, .modifiers = 0 },
        else => .{ .kind = @intFromEnum(EventKind.key_up), .keycode = @intFromEnum(key.code), .character = 0, .modifiers = 0 },
    };
}

/// Synthetic mouse events (placeholder until real PS/2 packet decoding). `step` alternates
/// a small diagonal motion with a left-button click.
pub fn syntheticMouseEvent(step: usize) MouseEvent {
    return switch (step % 3) {
        0 => .{ .kind = @intFromEnum(MouseEventKind.motion), .button = 0, .dx = 1, .dy = 1, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
        1 => .{ .kind = @intFromEnum(MouseEventKind.button_down), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = protocol.mouse_button_left },
        else => .{ .kind = @intFromEnum(MouseEventKind.button_up), .button = protocol.mouse_button_left, .dx = 0, .dy = 0, .scroll_x = 0, .scroll_y = 0, .buttons = 0 },
    };
}

/// Synthetic joystick/gamepad events (placeholder until a real controller driver). `step`
/// sweeps axis 0 and toggles button 0.
pub fn syntheticJoystickEvent(step: usize) JoystickEvent {
    return switch (step % 3) {
        0 => .{ .kind = @intFromEnum(JoystickEventKind.axis), .control = 0, .value = 16384, .buttons = 0 },
        1 => .{ .kind = @intFromEnum(JoystickEventKind.button_down), .control = 0, .value = 0, .buttons = 1 },
        else => .{ .kind = @intFromEnum(JoystickEventKind.button_up), .control = 0, .value = 0, .buttons = 0 },
    };
}
