//! PCI class-code decoding: turn the (class, subclass, prog-IF) triple a PCI function
//! reports in its configuration header into human-readable names. Every PCI function
//! carries a 24-bit class code — base class (config byte 0x0B), subclass (0x0A), and
//! programming interface (0x09) — that says *what it is* far more precisely than
//! danos's coarse `DeviceClass`: an ISA bridge, a SATA/AHCI controller, and an xHCI USB
//! controller are all just `pci_device` by class, and only this triple tells them
//! apart. Pure reference data (from the PCI spec; see https://wiki.osdev.org/PCI) — no
//! hardware access — so it is shared by kernel discovery (the device-tree dump) and any
//! user-space tool (a future lspci, driver matching).
//!
//! The taxonomy is named, not numbered (docs/coding-standards.md, "Named values"): the
//! base class is a `BaseClass` enum, and each class with defined subclasses gets a
//! namespace holding its `SubClass` enum (and, where the spec defines them, per-subclass
//! `ProgIf` enums) — the same shape as `usb-ids.zig`. Code that *means* a specific class
//! names it (`BaseClass.serial_bus`, `serial_bus.usb.ProgIf.xhci`) rather than writing a
//! bare 0x0C/0x03/0x30. The `className`/`subclassName`/`progIfName` functions still take
//! the raw bytes a function reports in its header, because that is what hardware hands us.

const std = @import("std");

/// The three bytes of a PCI class code, unpacked from the `0xCCSSPP` value discovery
/// records in `Device.ids.pci_class` (CC = base class, SS = subclass, PP = prog-IF).
pub const ClassCode = struct {
    base: u8, // class code    (config offset 0x0B)
    subclass: u8, // subclass    (0x0A)
    prog_if: u8, // programming interface (0x09)

    pub fn unpack(packed_code: u24) ClassCode {
        return .{
            .base = @intCast((packed_code >> 16) & 0xFF),
            .subclass = @intCast((packed_code >> 8) & 0xFF),
            .prog_if = @intCast(packed_code & 0xFF),
        };
    }

    /// Re-pack the triple into the `0xCCSSPP` form. Lets code name a whole class code
    /// from its parts — `pack(.{ .base = @intFromEnum(BaseClass.serial_bus), … })` —
    /// instead of writing the literal 0x0C0330.
    pub fn pack(self: ClassCode) u24 {
        return (@as(u24, self.base) << 16) | (@as(u24, self.subclass) << 8) | self.prog_if;
    }
};

/// Base class (config byte 0x0B). Non-exhaustive: an unlisted code is a real but
/// unnamed class, decoded as "Unknown" rather than rejected.
pub const BaseClass = enum(u8) {
    unclassified = 0x00,
    mass_storage = 0x01,
    network = 0x02,
    display = 0x03,
    multimedia = 0x04,
    memory = 0x05,
    bridge = 0x06,
    simple_communication = 0x07,
    base_system_peripheral = 0x08,
    input_device = 0x09,
    docking_station = 0x0A,
    processor = 0x0B,
    serial_bus = 0x0C,
    wireless = 0x0D,
    intelligent = 0x0E,
    satellite_communication = 0x0F,
    encryption = 0x10,
    signal_processing = 0x11,
    processing_accelerator = 0x12,
    non_essential_instrumentation = 0x13,
    co_processor = 0x40,
    unassigned = 0xFF,
    _,

    pub fn name(self: BaseClass) []const u8 {
        return switch (self) {
            .unclassified => "Unclassified",
            .mass_storage => "Mass Storage Controller",
            .network => "Network Controller",
            .display => "Display Controller",
            .multimedia => "Multimedia Controller",
            .memory => "Memory Controller",
            .bridge => "Bridge",
            .simple_communication => "Simple Communication Controller",
            .base_system_peripheral => "Base System Peripheral",
            .input_device => "Input Device Controller",
            .docking_station => "Docking Station",
            .processor => "Processor",
            .serial_bus => "Serial Bus Controller",
            .wireless => "Wireless Controller",
            .intelligent => "Intelligent Controller",
            .satellite_communication => "Satellite Communication Controller",
            .encryption => "Encryption Controller",
            .signal_processing => "Signal Processing Controller",
            .processing_accelerator => "Processing Accelerator",
            .non_essential_instrumentation => "Non-Essential Instrumentation",
            .co_processor => "Co-Processor",
            .unassigned => "Unassigned Class (Vendor specific)",
            _ => "Unknown",
        };
    }
};

// --- Per-class subclass (and prog-IF) taxonomies --------------------------------------
// One namespace per base class that has defined subclasses, named after the class. Each
// holds an exhaustive `SubClass` enum (so an unlisted code decodes to the class default,
// not a wrong name), and, where the spec assigns them, per-subclass `ProgIf` enums.

pub const mass_storage = struct {
    pub const SubClass = enum(u8) {
        scsi_bus = 0x00,
        ide = 0x01,
        floppy = 0x02,
        ipi_bus = 0x03,
        raid = 0x04,
        ata = 0x05,
        serial_ata = 0x06,
        serial_attached_scsi = 0x07,
        non_volatile_memory = 0x08,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .scsi_bus => "SCSI Bus Controller",
                .ide => "IDE Controller",
                .floppy => "Floppy Disk Controller",
                .ipi_bus => "IPI Bus Controller",
                .raid => "RAID Controller",
                .ata => "ATA Controller",
                .serial_ata => "Serial ATA Controller",
                .serial_attached_scsi => "Serial Attached SCSI Controller",
                .non_volatile_memory => "Non-Volatile Memory Controller",
            };
        }
    };

    pub const serial_ata = struct {
        pub const ProgIf = enum(u8) {
            vendor_specific = 0x00,
            ahci = 0x01,
            serial_storage_bus = 0x02,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .vendor_specific => "Vendor Specific Interface",
                    .ahci => "AHCI 1.0",
                    .serial_storage_bus => "Serial Storage Bus",
                };
            }
        };
    };

    pub const non_volatile_memory = struct {
        pub const ProgIf = enum(u8) {
            nvmhci = 0x01,
            nvm_express = 0x02,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .nvmhci => "NVMHCI",
                    .nvm_express => "NVM Express",
                };
            }
        };
    };
};

pub const network = struct {
    pub const SubClass = enum(u8) {
        ethernet = 0x00,
        token_ring = 0x01,
        fddi = 0x02,
        atm = 0x03,
        isdn = 0x04,
        picmg_multi_computing = 0x06,
        infiniband = 0x07,
        fabric = 0x08,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .ethernet => "Ethernet Controller",
                .token_ring => "Token Ring Controller",
                .fddi => "FDDI Controller",
                .atm => "ATM Controller",
                .isdn => "ISDN Controller",
                .picmg_multi_computing => "PICMG 2.14 Multi Computing Controller",
                .infiniband => "Infiniband Controller",
                .fabric => "Fabric Controller",
            };
        }
    };
};

pub const display = struct {
    pub const SubClass = enum(u8) {
        vga_compatible = 0x00,
        xga = 0x01,
        three_dimensional = 0x02,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .vga_compatible => "VGA Compatible Controller",
                .xga => "XGA Controller",
                .three_dimensional => "3D Controller (Not VGA-Compatible)",
            };
        }
    };

    pub const vga_compatible = struct {
        pub const ProgIf = enum(u8) {
            vga = 0x00,
            compatible_8514 = 0x01,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .vga => "VGA Controller",
                    .compatible_8514 => "8514-Compatible Controller",
                };
            }
        };
    };
};

pub const multimedia = struct {
    pub const SubClass = enum(u8) {
        video = 0x00,
        audio = 0x01,
        telephony = 0x02,
        audio_device = 0x03,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .video => "Multimedia Video Controller",
                .audio => "Multimedia Audio Controller",
                .telephony => "Computer Telephony Device",
                .audio_device => "Audio Device",
            };
        }
    };
};

pub const memory = struct {
    pub const SubClass = enum(u8) {
        ram = 0x00,
        flash = 0x01,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .ram => "RAM Controller",
                .flash => "Flash Controller",
            };
        }
    };
};

pub const bridge = struct {
    pub const SubClass = enum(u8) {
        host = 0x00,
        isa = 0x01,
        eisa = 0x02,
        mca = 0x03,
        pci_to_pci = 0x04,
        pcmcia = 0x05,
        nubus = 0x06,
        cardbus = 0x07,
        raceway = 0x08,
        pci_to_pci_semi_transparent = 0x09,
        infiniband_to_pci = 0x0A,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .host => "Host Bridge",
                .isa => "ISA Bridge",
                .eisa => "EISA Bridge",
                .mca => "MCA Bridge",
                .pci_to_pci => "PCI-to-PCI Bridge",
                .pcmcia => "PCMCIA Bridge",
                .nubus => "NuBus Bridge",
                .cardbus => "CardBus Bridge",
                .raceway => "RACEway Bridge",
                .pci_to_pci_semi_transparent => "PCI-to-PCI Bridge (Semi-Transparent)",
                .infiniband_to_pci => "InfiniBand-to-PCI Host Bridge",
            };
        }
    };

    pub const pci_to_pci = struct {
        pub const ProgIf = enum(u8) {
            normal_decode = 0x00,
            subtractive_decode = 0x01,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .normal_decode => "Normal Decode",
                    .subtractive_decode => "Subtractive Decode",
                };
            }
        };
    };
};

pub const simple_communication = struct {
    pub const SubClass = enum(u8) {
        serial = 0x00,
        parallel = 0x01,
        multiport_serial = 0x02,
        modem = 0x03,
        gpib = 0x04,
        smart_card = 0x05,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .serial => "Serial Controller",
                .parallel => "Parallel Controller",
                .multiport_serial => "Multiport Serial Controller",
                .modem => "Modem",
                .gpib => "IEEE 488.1/2 (GPIB) Controller",
                .smart_card => "Smart Card Controller",
            };
        }
    };

    pub const serial = struct {
        pub const ProgIf = enum(u8) {
            compatible_8250 = 0x00,
            compatible_16450 = 0x01,
            compatible_16550 = 0x02,
            compatible_16650 = 0x03,
            compatible_16750 = 0x04,
            compatible_16850 = 0x05,
            compatible_16950 = 0x06,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .compatible_8250 => "8250-Compatible (Generic XT)",
                    .compatible_16450 => "16450-Compatible",
                    .compatible_16550 => "16550-Compatible",
                    .compatible_16650 => "16650-Compatible",
                    .compatible_16750 => "16750-Compatible",
                    .compatible_16850 => "16850-Compatible",
                    .compatible_16950 => "16950-Compatible",
                };
            }
        };
    };
};

pub const base_system_peripheral = struct {
    pub const SubClass = enum(u8) {
        pic = 0x00,
        dma = 0x01,
        timer = 0x02,
        rtc = 0x03,
        pci_hot_plug = 0x04,
        sd_host = 0x05,
        iommu = 0x06,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .pic => "PIC",
                .dma => "DMA Controller",
                .timer => "Timer",
                .rtc => "RTC Controller",
                .pci_hot_plug => "PCI Hot-Plug Controller",
                .sd_host => "SD Host Controller",
                .iommu => "IOMMU",
            };
        }
    };
};

pub const input_device = struct {
    pub const SubClass = enum(u8) {
        keyboard = 0x00,
        digitizer_pen = 0x01,
        mouse = 0x02,
        scanner = 0x03,
        gameport = 0x04,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .keyboard => "Keyboard Controller",
                .digitizer_pen => "Digitizer Pen",
                .mouse => "Mouse Controller",
                .scanner => "Scanner Controller",
                .gameport => "Gameport Controller",
            };
        }
    };
};

pub const serial_bus = struct {
    pub const SubClass = enum(u8) {
        firewire = 0x00,
        access_bus = 0x01,
        ssa = 0x02,
        usb = 0x03,
        fibre_channel = 0x04,
        smbus = 0x05,
        infiniband = 0x06,
        ipmi = 0x07,
        sercos = 0x08,
        canbus = 0x09,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .firewire => "FireWire (IEEE 1394) Controller",
                .access_bus => "ACCESS Bus Controller",
                .ssa => "SSA",
                .usb => "USB Controller",
                .fibre_channel => "Fibre Channel",
                .smbus => "SMBus Controller",
                .infiniband => "InfiniBand Controller",
                .ipmi => "IPMI Interface",
                .sercos => "SERCOS Interface (IEC 61491)",
                .canbus => "CANbus Controller",
            };
        }
    };

    pub const usb = struct {
        pub const ProgIf = enum(u8) {
            uhci = 0x00,
            ohci = 0x10,
            ehci = 0x20,
            xhci = 0x30,
            unspecified = 0x80,
            device = 0xFE,

            pub fn name(self: ProgIf) []const u8 {
                return switch (self) {
                    .uhci => "UHCI Controller",
                    .ohci => "OHCI Controller",
                    .ehci => "EHCI (USB2) Controller",
                    .xhci => "XHCI (USB3) Controller",
                    .unspecified => "Unspecified",
                    .device => "USB Device (not a host controller)",
                };
            }
        };
    };
};

pub const wireless = struct {
    pub const SubClass = enum(u8) {
        irda = 0x00,
        consumer_ir = 0x01,
        rf = 0x10,
        bluetooth = 0x11,
        broadband = 0x12,
        ethernet_802_1a = 0x20,
        ethernet_802_1b = 0x21,

        pub fn name(self: SubClass) []const u8 {
            return switch (self) {
                .irda => "iRDA Compatible Controller",
                .consumer_ir => "Consumer IR Controller",
                .rf => "RF Controller",
                .bluetooth => "Bluetooth Controller",
                .broadband => "Broadband Controller",
                .ethernet_802_1a => "Ethernet Controller (802.1a)",
                .ethernet_802_1b => "Ethernet Controller (802.1b)",
            };
        }
    };
};

// --- Raw-byte decoding (what a function reports in its header) -------------------------

/// The name of an exhaustive class-code enum member, or null if `value` is not one — the
/// bridge from a raw config byte to a named taxonomy above.
fn enumName(comptime Enum: type, value: u8) ?[]const u8 {
    return (std.enums.fromInt(Enum, value) orelse return null).name();
}

/// Name of the base class (byte 0x0B), e.g. `0x06` -> "Bridge".
pub fn className(base: u8) []const u8 {
    return @as(BaseClass, @enumFromInt(base)).name();
}

/// Name of the subclass within its base class, e.g. `(0x06, 0x01)` -> "ISA Bridge".
/// Subclass `0x80` is "Other" by PCI convention; anything unlisted is "Unknown".
pub fn subclassName(base: u8, subclass: u8) []const u8 {
    const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
        .mass_storage => enumName(mass_storage.SubClass, subclass),
        .network => enumName(network.SubClass, subclass),
        .display => enumName(display.SubClass, subclass),
        .multimedia => enumName(multimedia.SubClass, subclass),
        .memory => enumName(memory.SubClass, subclass),
        .bridge => enumName(bridge.SubClass, subclass),
        .simple_communication => enumName(simple_communication.SubClass, subclass),
        .base_system_peripheral => enumName(base_system_peripheral.SubClass, subclass),
        .input_device => enumName(input_device.SubClass, subclass),
        .serial_bus => enumName(serial_bus.SubClass, subclass),
        .wireless => enumName(wireless.SubClass, subclass),
        else => null,
    };
    return named orelse defaultSubclass(subclass);
}

fn defaultSubclass(subclass: u8) []const u8 {
    return if (subclass == 0x80) "Other" else "Unknown";
}

/// Name of the programming interface, for the subclasses that define standard ones
/// (IDE modes, SATA/AHCI, NVMe, PCI-bridge decode, UART generation, USB host type).
/// Returns "" when the prog-IF carries no standard meaning for this class/subclass —
/// callers just print the hex byte in that case.
pub fn progIfName(base: u8, subclass: u8, prog_if: u8) []const u8 {
    const named: ?[]const u8 = switch (@as(BaseClass, @enumFromInt(base))) {
        .mass_storage => switch (std.enums.fromInt(mass_storage.SubClass, subclass) orelse return "") {
            .serial_ata => enumName(mass_storage.serial_ata.ProgIf, prog_if),
            .non_volatile_memory => enumName(mass_storage.non_volatile_memory.ProgIf, prog_if),
            else => null,
        },
        .display => switch (std.enums.fromInt(display.SubClass, subclass) orelse return "") {
            .vga_compatible => enumName(display.vga_compatible.ProgIf, prog_if),
            else => null,
        },
        .bridge => switch (std.enums.fromInt(bridge.SubClass, subclass) orelse return "") {
            .pci_to_pci => enumName(bridge.pci_to_pci.ProgIf, prog_if),
            else => null,
        },
        .simple_communication => switch (std.enums.fromInt(simple_communication.SubClass, subclass) orelse return "") {
            .serial => enumName(simple_communication.serial.ProgIf, prog_if),
            else => null,
        },
        .serial_bus => switch (std.enums.fromInt(serial_bus.SubClass, subclass) orelse return "") {
            .usb => enumName(serial_bus.usb.ProgIf, prog_if),
            else => null,
        },
        else => null,
    };
    return named orelse "";
}

test "decodes the common class codes" {
    const eq = std.testing.expectEqualStrings;

    const isa = ClassCode.unpack(0x06_01_00);
    try std.testing.expectEqual(@as(u8, 0x06), isa.base);
    try std.testing.expectEqual(@as(u8, 0x01), isa.subclass);
    try eq("Bridge", className(isa.base));
    try eq("ISA Bridge", subclassName(isa.base, isa.subclass));

    const ahci = ClassCode.unpack(0x01_06_01);
    try eq("Mass Storage Controller", className(ahci.base));
    try eq("Serial ATA Controller", subclassName(ahci.base, ahci.subclass));
    try eq("AHCI 1.0", progIfName(ahci.base, ahci.subclass, ahci.prog_if));

    const xhci = ClassCode.unpack(0x0C_03_30);
    try eq("Serial Bus Controller", className(xhci.base));
    try eq("USB Controller", subclassName(xhci.base, xhci.subclass));
    try eq("XHCI (USB3) Controller", progIfName(xhci.base, xhci.subclass, xhci.prog_if));
}

test "unlisted codes fall back without a wrong name" {
    const eq = std.testing.expectEqualStrings;
    try eq("Unknown", className(0x77)); // no such base class
    try eq("Other", subclassName(0x01, 0x80)); // 0x80 is the PCI "Other" convention
    try eq("Unknown", subclassName(0x01, 0x7A)); // unlisted mass-storage subclass
    try eq("", progIfName(0x01, 0x06, 0x7F)); // no standard SATA prog-IF for 0x7F
    try eq("", progIfName(0x02, 0x00, 0x00)); // class with no prog-IF taxonomy at all
}

test "named parts pack to the raw triple" {
    const xhci = ClassCode{
        .base = @intFromEnum(BaseClass.serial_bus),
        .subclass = @intFromEnum(serial_bus.SubClass.usb),
        .prog_if = @intFromEnum(serial_bus.usb.ProgIf.xhci),
    };
    try std.testing.expectEqual(@as(u24, 0x0C_03_30), xhci.pack());
}
