// Rumpk Layer 0: UART Driver
// Minimal serial output for QEMU 'virt' machine
// Supports PL011 (ARM64) and 16550A (RISC-V)

const std = @import("std");
const builtin = @import("builtin");

// ARM64 PL011 Constants
const PL011_BASE: usize = 0x09000000;
const PL011_DR: usize = 0x00;
const PL011_FR: usize = 0x18;
const PL011_TXFF: u32 = 1 << 5;

// RISC-V 16550A Constants
const NS16550A_BASE: usize = 0x10000000;
const NS16550A_THR: usize = 0x00; // Transmitter Holding Register
const NS16550A_LSR: usize = 0x05; // Line Status Register
const NS16550A_THRE: u8 = 1 << 5; // Transmitter Holding Register Empty

pub fn init() void {
    // QEMU devices are usually pre-initialized by firmware (EDK2/OpenSBI)
}

const NS16550A_IER: usize = 0x01; // Interrupt Enable Register

pub fn init_riscv() void {
    // Disable Interrupts to rely on Polling (prevents Interrupt Storms if Handler is missing)
    const ier: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_IER);
    ier.* = 0x00;
}

fn write_char_arm64(c: u8) void {
    const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
    const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);

    while ((fr.* & PL011_TXFF) != 0) {}
    dr.* = c;
}

fn write_char_riscv64(c: u8) void {
    const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR);
    const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);

    // Wait for THRE (Transmitter Holding Register Empty)
    while ((lsr.* & NS16550A_THRE) == 0) {}
    thr.* = c;
}

fn write_char(c: u8) void {
    switch (builtin.cpu.arch) {
        .aarch64 => write_char_arm64(c),
        .riscv64 => write_char_riscv64(c),
        else => {}, // Do nothing on others
    }
}

pub fn write_bytes(bytes: []const u8) void {
    for (bytes) |b| {
        if (b == '\n') {
            write_char('\r');
        }
        write_char(b);
    }
}

pub fn read_byte() ?u8 {
    switch (builtin.cpu.arch) {
        .aarch64 => {
            const dr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_DR);
            const fr: *volatile u32 = @ptrFromInt(PL011_BASE + PL011_FR);
            if ((fr.* & (1 << 4)) == 0) { // RXFE (Receive FIFO Empty) is bit 4, so if 0, it's NOT empty
                return @truncate(dr.*);
            }
        },
        .riscv64 => {
            const thr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_THR);
            const lsr: *volatile u8 = @ptrFromInt(NS16550A_BASE + NS16550A_LSR);
            if ((lsr.* & 0x01) != 0) { // Data Ready
                return thr.*;
            }
        },
        else => {},
    }
    return null;
}

pub fn puts(s: []const u8) void {
    write_bytes(s);
}

pub fn putc(c: u8) void {
    if (c == '\n') {
        write_char('\r');
    }
    write_char(c);
}

pub fn print(s: []const u8) void {
    puts(s);
}

pub const Writer = struct {
    pub const Error = error{};
    pub fn write(self: Writer, bytes: []const u8) Error!usize {
        _ = self;
        write_bytes(bytes);
        return bytes.len;
    }
    pub fn print(self: Writer, comptime fmt: []const u8, args: anytype) Error!void {
        return std.fmt.format(self, fmt, args);
    }
};

pub fn print_hex(value: usize) void {
    const hex_chars = "0123456789ABCDEF";
    write_bytes("0x");
    var i: usize = 0;
    while (i < 16) : (i += 1) {
        const shift: u6 = @intCast((15 - i) * 4);
        const nibble = (value >> shift) & 0xF;
        write_char(hex_chars[nibble]);
    }
}
