//! device-list — the `ps` analog for the device tree (docs/device-manager.md
//! M18.3): asks the device manager for the tree over IPC, prints it, then
//! subscribes and prints every published add/remove event. The manager is the
//! one answer to "what devices exist" for user space; nothing here touches a
//! device_* system call.

const std = @import("std");
const runtime = @import("runtime");
const protocol = runtime.device_manager_protocol;

fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
    var line: [96]u8 = undefined;
    _ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
}

pub fn main() void {
    var manager: ?runtime.ipc.Handle = null;
    var tries: u32 = 0;
    while (manager == null and tries < 200) : (tries += 1) {
        manager = runtime.ipc.lookup(.device_manager);
        if (manager == null) runtime.system.sleep(20);
    }
    const h = manager orelse {
        _ = runtime.system.write("device-list: no device manager\n");
        return;
    };

    // The snapshot — polled briefly, because at boot the bus drivers may still
    // be scanning: an empty first answer usually just means "too early".
    var reply: [protocol.message_maximum]u8 = undefined;
    var count: u32 = 0;
    var length: usize = 0;
    tries = 0;
    while (tries < 20) : (tries += 1) {
        const request = protocol.Enumerate{};
        length = runtime.ipc.call(h, std.mem.asBytes(&request), &reply) catch 0;
        if (length >= @sizeOf(protocol.EnumerateReply)) {
            count = std.mem.bytesToValue(protocol.EnumerateReply, reply[0..@sizeOf(protocol.EnumerateReply)]).count;
            if (count != 0) break;
        }
        runtime.system.sleep(100);
    }
    writeLine("device-list: {d} devices\n", .{count});
    var offset: usize = @sizeOf(protocol.EnumerateReply);
    var index: u32 = 0;
    while (index < count and offset + @sizeOf(protocol.ChildEntry) <= length) : (index += 1) {
        const entry = std.mem.bytesToValue(protocol.ChildEntry, reply[offset..][0..@sizeOf(protocol.ChildEntry)]);
        writeLine("device-list: device {d} port {d} identity {d}\n", .{ entry.parent, entry.bus_address, entry.identity });
        offset += @sizeOf(protocol.ChildEntry);
    }

    // The subscription: our endpoint rides as the call's capability; events
    // arrive as buffered messages carrying the same structs the bus sends.
    const endpoint = runtime.ipc.createIpcEndpoint() orelse {
        _ = runtime.system.write("device-list: no endpoint\n");
        return;
    };
    const subscribe = protocol.Subscribe{};
    _ = runtime.ipc.callCap(h, std.mem.asBytes(&subscribe), &reply, endpoint) catch {
        _ = runtime.system.write("device-list: subscribe failed\n");
        return;
    };
    _ = runtime.system.write("device-list: subscribed\n");

    var receive: [protocol.message_maximum]u8 = undefined;
    while (true) {
        const got = runtime.ipc.replyWait(endpoint, &.{}, &receive, null);
        if (!got.isMessage() or got.len < 1) continue;
        switch (receive[0]) {
            @intFromEnum(protocol.Operation.child_added) => {
                if (got.len < protocol.child_added_size) continue;
                const event = std.mem.bytesToValue(protocol.ChildAdded, receive[0..protocol.child_added_size]);
                writeLine("device-list: added (device {d} port {d})\n", .{ event.parent, event.bus_address });
            },
            @intFromEnum(protocol.Operation.child_removed) => {
                if (got.len < protocol.child_removed_size) continue;
                const event = std.mem.bytesToValue(protocol.ChildRemoved, receive[0..protocol.child_removed_size]);
                writeLine("device-list: removed (device {d} port {d})\n", .{ event.parent, event.bus_address });
            },
            else => {},
        }
    }
}
