//! 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 ipc = @import("ipc");
const time = @import("time");
const logging = @import("logging");
const device_manager_protocol = @import("device-manager-protocol");

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

pub fn main() void {
    var manager: ?ipc.Handle = null;
    var tries: u32 = 0;
    while (manager == null and tries < 200) : (tries += 1) {
        manager = ipc.lookup(.device_manager);
        if (manager == null) time.sleepMillis(20);
    }
    const h = manager orelse {
        _ = logging.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: [device_manager_protocol.message_maximum]u8 = undefined;
    var count: u32 = 0;
    var length: usize = 0;
    tries = 0;
    while (tries < 20) : (tries += 1) {
        const request = device_manager_protocol.Enumerate{};
        length = ipc.call(h, std.mem.asBytes(&request), &reply) catch 0;
        if (length >= @sizeOf(device_manager_protocol.EnumerateReply)) {
            count = std.mem.bytesToValue(device_manager_protocol.EnumerateReply, reply[0..@sizeOf(device_manager_protocol.EnumerateReply)]).count;
            if (count != 0) break;
        }
        time.sleepMillis(100);
    }
    writeLine("device-list: {d} devices\n", .{count});
    var offset: usize = @sizeOf(device_manager_protocol.EnumerateReply);
    var index: u32 = 0;
    while (index < count and offset + @sizeOf(device_manager_protocol.ChildEntry) <= length) : (index += 1) {
        const entry = std.mem.bytesToValue(device_manager_protocol.ChildEntry, reply[offset..][0..@sizeOf(device_manager_protocol.ChildEntry)]);
        writeLine("device-list: device {d} port {d} identity {d}\n", .{ entry.parent, entry.bus_address, entry.identity });
        offset += @sizeOf(device_manager_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 = ipc.createIpcEndpoint() orelse {
        _ = logging.write("device-list: no endpoint\n");
        return;
    };
    const subscribe = device_manager_protocol.Subscribe{};
    _ = ipc.callCap(h, std.mem.asBytes(&subscribe), &reply, endpoint) catch {
        _ = logging.write("device-list: subscribe failed\n");
        return;
    };
    _ = logging.write("device-list: subscribed\n");

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