//! ACPI / PnP hardware-ID (`_HID`) names: the flat analog of pci-class.zig for
//! `acpi_device` nodes. Unlike PCI, ACPI has no class/subclass/prog-IF taxonomy — a
//! device's identity *is* its `_HID` string (`PNP0303` simply means "PS/2 keyboard"),
//! so this is a plain id <-> name registry rather than a hierarchical decoder.
//! The well-known PnP/ACPI IDs; vendor-specific ids (e.g. `QEMU0002`, `INTC1234`) have
//! no standard name and decode to nothing. Pure reference data, so it is shared by
//! kernel discovery (the device-tree dump) and any user-space driver or tool.
//!
//! Code that means a specific device names the `HardwareId` variant instead of its
//! `_HID` string — `HardwareId.ps2_keyboard.hid()` reads without a registry lookup,
//! where a bare `"PNP0303"` does not.

const std = @import("std");

/// The common standard PnP/ACPI hardware IDs, as named values. Prefix ranges hint at
/// the grouping (PNP03xx keyboards, PNP0Fxx pointing devices, PNP0Cxx ACPI
/// power/thermal, PNP0Axx buses), but there is no formal hierarchy — hence a flat
/// enum over a flat registry.
pub const HardwareId = enum {
    programmable_interrupt_controller,
    system_timer,
    high_precision_event_timer,
    dma_controller,
    ps2_keyboard,
    parallel_port,
    ecp_parallel_port,
    serial_port,
    floppy_disk_controller,
    system_speaker,
    pci_bus,
    generic_container,
    /// The second id the ACPI spec assigns the same "Generic Container Device" name.
    generic_container_extended,
    pci_express_root_bridge,
    real_time_clock,
    system_board,
    motherboard_reserved_resources,
    math_coprocessor,
    acpi_system_board,
    embedded_controller,
    control_method_battery,
    fan,
    power_button,
    lid,
    sleep_button,
    pci_interrupt_link,
    microsoft_ps2_mouse,
    ps2_mouse,
    ac_adapter,
    processor_device,
    processor_aggregator,
    processor_container,

    const Entry = struct { hid: []const u8, name: []const u8 };

    /// The registry row for this id: its `_HID` string and human-readable name.
    fn entry(self: HardwareId) Entry {
        return switch (self) {
            .programmable_interrupt_controller => .{ .hid = "PNP0000", .name = "Programmable Interrupt Controller (PIC)" },
            .system_timer => .{ .hid = "PNP0100", .name = "System Timer (PIT)" },
            .high_precision_event_timer => .{ .hid = "PNP0103", .name = "High Precision Event Timer (HPET)" },
            .dma_controller => .{ .hid = "PNP0200", .name = "DMA Controller" },
            .ps2_keyboard => .{ .hid = "PNP0303", .name = "PS/2 Keyboard" },
            .parallel_port => .{ .hid = "PNP0400", .name = "Standard LPT Parallel Port" },
            .ecp_parallel_port => .{ .hid = "PNP0401", .name = "ECP Parallel Port" },
            .serial_port => .{ .hid = "PNP0501", .name = "16550A-compatible Serial Port" },
            .floppy_disk_controller => .{ .hid = "PNP0700", .name = "PC Floppy Disk Controller" },
            .system_speaker => .{ .hid = "PNP0800", .name = "System Speaker" },
            .pci_bus => .{ .hid = "PNP0A03", .name = "PCI Bus" },
            .generic_container => .{ .hid = "PNP0A05", .name = "Generic Container Device" },
            .generic_container_extended => .{ .hid = "PNP0A06", .name = "Generic Container Device" },
            .pci_express_root_bridge => .{ .hid = "PNP0A08", .name = "PCI Express Root Bridge" },
            .real_time_clock => .{ .hid = "PNP0B00", .name = "Real-Time Clock (RTC)" },
            .system_board => .{ .hid = "PNP0C01", .name = "System Board" },
            .motherboard_reserved_resources => .{ .hid = "PNP0C02", .name = "Motherboard Reserved Resources" },
            .math_coprocessor => .{ .hid = "PNP0C04", .name = "Math Coprocessor" },
            .acpi_system_board => .{ .hid = "PNP0C08", .name = "ACPI System Board" },
            .embedded_controller => .{ .hid = "PNP0C09", .name = "ACPI Embedded Controller" },
            .control_method_battery => .{ .hid = "PNP0C0A", .name = "ACPI Control Method Battery" },
            .fan => .{ .hid = "PNP0C0B", .name = "ACPI Fan" },
            .power_button => .{ .hid = "PNP0C0C", .name = "ACPI Power Button" },
            .lid => .{ .hid = "PNP0C0D", .name = "ACPI Lid" },
            .sleep_button => .{ .hid = "PNP0C0E", .name = "ACPI Sleep Button" },
            .pci_interrupt_link => .{ .hid = "PNP0C0F", .name = "PCI Interrupt Link Device" },
            .microsoft_ps2_mouse => .{ .hid = "PNP0F03", .name = "Microsoft PS/2 Mouse" },
            .ps2_mouse => .{ .hid = "PNP0F13", .name = "PS/2 Mouse" },
            .ac_adapter => .{ .hid = "ACPI0003", .name = "AC Adapter" },
            .processor_device => .{ .hid = "ACPI0007", .name = "Processor Device" },
            .processor_aggregator => .{ .hid = "ACPI000C", .name = "Processor Aggregator" },
            .processor_container => .{ .hid = "ACPI0010", .name = "Processor Container" },
        };
    }

    /// This id's `_HID` string (e.g. `.ps2_keyboard` -> "PNP0303").
    pub fn hid(self: HardwareId) []const u8 {
        return self.entry().hid;
    }

    /// This id's human-readable name (e.g. `.ps2_keyboard` -> "PS/2 Keyboard").
    pub fn description(self: HardwareId) []const u8 {
        return self.entry().name;
    }

    /// The named value for a `_HID` string, or null if it is not a known standard
    /// id (vendor-specific ids are not in the registry).
    pub fn fromHid(hid_string: []const u8) ?HardwareId {
        for (std.enums.values(HardwareId)) |id| {
            if (std.mem.eql(u8, id.hid(), hid_string)) return id;
        }
        return null;
    }
};

/// The human-readable name for a `_HID` string, or "" if it is not a known standard
/// id (vendor-specific ids have no registry name — callers just print the raw HID).
pub fn description(hid: []const u8) []const u8 {
    return (HardwareId.fromHid(hid) orelse return "").description();
}

test "decodes standard PnP/ACPI ids and leaves the rest alone" {
    const eq = std.testing.expectEqualStrings;
    try eq("PS/2 Keyboard", description("PNP0303"));
    try eq("PS/2 Mouse", description("PNP0F13"));
    try eq("PCI Express Root Bridge", description("PNP0A08"));
    try eq("Real-Time Clock (RTC)", description("PNP0B00"));
    try eq("", description("QEMU0002")); // vendor-specific: no standard name
    try eq("", description("")); // no HID at all
}

test "named values round-trip through their _HID strings" {
    const testing = std.testing;
    try testing.expectEqualStrings("PNP0303", HardwareId.ps2_keyboard.hid());
    try testing.expectEqual(@as(?HardwareId, .ps2_mouse), HardwareId.fromHid("PNP0F13"));
    try testing.expectEqual(@as(?HardwareId, null), HardwareId.fromHid("QEMU0002"));
    for (std.enums.values(HardwareId)) |id| {
        try testing.expectEqual(@as(?HardwareId, id), HardwareId.fromHid(id.hid()));
    }
}
