// MARKUS MAIWALD (ARCHITECT) | VOXIS FORGE (AI)
// Phase 3.5c: The Retina - VirtIO-GPU Driver
// Blasts pixels from RAM canvas to host display.

const std = @import("std");
const fb = @import("framebuffer.zig");
const uart = @import("uart.zig");

// =========================================================
// VirtIO-GPU Constants
// =========================================================

const VIRTIO_GPU_CMD_GET_DISPLAY_INFO: u32 = 0x0100;
const VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: u32 = 0x0101;
const VIRTIO_GPU_CMD_RESOURCE_UNREF: u32 = 0x0102;
const VIRTIO_GPU_CMD_SET_SCANOUT: u32 = 0x0103;
const VIRTIO_GPU_CMD_RESOURCE_FLUSH: u32 = 0x0104;
const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: u32 = 0x0105;
const VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: u32 = 0x0106;

const VIRTIO_GPU_RESP_OK_NODATA: u32 = 0x1100;
const VIRTIO_GPU_RESP_OK_DISPLAY_INFO: u32 = 0x1101;

const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;

const RESOURCE_ID: u32 = 1;

// =========================================================
// VirtIO-GPU Structures (packed for wire format)
// =========================================================

const VirtioGpuCtrlHdr = extern struct {
    type: u32,
    flags: u32,
    fence_id: u64,
    ctx_id: u32,
    padding: u32,
};

const VirtioGpuResourceCreate2D = extern struct {
    hdr: VirtioGpuCtrlHdr,
    resource_id: u32,
    format: u32,
    width: u32,
    height: u32,
};

const VirtioGpuMemEntry = extern struct {
    addr: u64,
    length: u32,
    padding: u32,
};

const VirtioGpuResourceAttachBacking = extern struct {
    hdr: VirtioGpuCtrlHdr,
    resource_id: u32,
    nr_entries: u32,
    // Followed by VirtioGpuMemEntry array
};

const VirtioGpuSetScanout = extern struct {
    hdr: VirtioGpuCtrlHdr,
    r_x: u32,
    r_y: u32,
    r_width: u32,
    r_height: u32,
    scanout_id: u32,
    resource_id: u32,
};

const VirtioGpuTransferToHost2D = extern struct {
    hdr: VirtioGpuCtrlHdr,
    r_x: u32,
    r_y: u32,
    r_width: u32,
    r_height: u32,
    offset: u64,
    resource_id: u32,
    padding: u32,
};

const VirtioGpuResourceFlush = extern struct {
    hdr: VirtioGpuCtrlHdr,
    r_x: u32,
    r_y: u32,
    r_width: u32,
    r_height: u32,
    resource_id: u32,
    padding: u32,
};

// =========================================================
// Driver State
// =========================================================

var initialized: bool = false;

// MMIO base for VirtIO-GPU (discovered at runtime or hardcoded for virt machine)
// On QEMU virt, virtio-gpu-device is at 0x10008000 (device 1 after net)
// Actually it depends on dtb, for now we'll use a probe or hardcode.
var mmio_base: usize = 0;

// VirtIO Queue Layout (Contiguous for v1 legacy support)
const QUEUE_SIZE = 16;
const PAGE_SIZE = 4096;

const VirtioDesc = extern struct {
    addr: u64,
    len: u32,
    flags: u16,
    next: u16,
};

const VirtioAvail = extern struct {
    flags: u16,
    idx: u16,
    ring: [QUEUE_SIZE]u16,
    // Note: older versions might have used_event here, but we don't need it for basic polling
};

const VirtioUsedItem = extern struct {
    id: u32,
    len: u32,
};

const VirtioUsed = extern struct {
    flags: u16,
    idx: u16,
    ring: [QUEUE_SIZE]VirtioUsedItem,
};

// We create a structure that matches the legacy layout:
// Descriptors [16 * 16 = 256 bytes]
// Available   [2 + 2 + 32 = 36 bytes]
// Padding to 4096
// Used        [2 + 2 + 16 * 8 = 132 bytes]
const VirtioQueueLayout = extern struct {
    desc: [QUEUE_SIZE]VirtioDesc,
    avail: VirtioAvail,
    _pad1: [PAGE_SIZE - @sizeOf([QUEUE_SIZE]VirtioDesc) - @sizeOf(VirtioAvail)]u8,
    used: VirtioUsed,
};

var queue align(PAGE_SIZE) = VirtioQueueLayout{
    .desc = undefined,
    .avail = .{ .flags = 0, .idx = 0, .ring = [_]u16{0} ** QUEUE_SIZE },
    ._pad1 = [_]u8{0} ** (PAGE_SIZE - @sizeOf([QUEUE_SIZE]VirtioDesc) - @sizeOf(VirtioAvail)),
    .used = .{ .flags = 0, .idx = 0, .ring = [_]VirtioUsedItem{.{ .id = 0, .len = 0 }} ** QUEUE_SIZE },
};

var last_used_idx: u16 = 0;

// Command/Response buffers (static)
var cmd_buf: [512]u8 align(4096) = undefined;
var resp_buf: [256]u8 align(4096) = undefined;

// =========================================================
// MMIO Helpers
// =========================================================

fn mmio_read(offset: usize) u32 {
    const ptr: *volatile u32 = @ptrFromInt(mmio_base + offset);
    return ptr.*;
}

fn mmio_write(offset: usize, val: u32) void {
    const ptr: *volatile u32 = @ptrFromInt(mmio_base + offset);
    ptr.* = val;
}

// VirtIO MMIO offsets
const VIRTIO_MMIO_MAGIC_VALUE = 0x000;
const VIRTIO_MMIO_VERSION = 0x004;
const VIRTIO_MMIO_DEVICE_ID = 0x008;
const VIRTIO_MMIO_VENDOR_ID = 0x00c;
const VIRTIO_MMIO_DEVICE_FEATURES = 0x010;
const VIRTIO_MMIO_DRIVER_FEATURES = 0x020;
const VIRTIO_MMIO_GUEST_FEATURES = 0x020;
const VIRTIO_MMIO_GUEST_FEATURES_SEL = 0x024;
const VIRTIO_MMIO_QUEUE_SEL = 0x030;
const VIRTIO_MMIO_QUEUE_NUM_MAX = 0x034;
const VIRTIO_MMIO_QUEUE_NUM = 0x038;
const VIRTIO_MMIO_QUEUE_ALIGN = 0x03c;
const VIRTIO_MMIO_QUEUE_PFN = 0x040;
const VIRTIO_MMIO_QUEUE_READY = 0x044;
const VIRTIO_MMIO_QUEUE_NOTIFY = 0x050;
const VIRTIO_MMIO_INTERRUPT_STATUS = 0x060;
const VIRTIO_MMIO_INTERRUPT_ACK = 0x064;
const VIRTIO_MMIO_STATUS = 0x070;
const VIRTIO_MMIO_QUEUE_DESC_LOW = 0x080;
const VIRTIO_MMIO_QUEUE_DESC_HIGH = 0x084;
const VIRTIO_MMIO_QUEUE_AVAIL_LOW = 0x090;
const VIRTIO_MMIO_QUEUE_AVAIL_HIGH = 0x094;
const VIRTIO_MMIO_QUEUE_USED_LOW = 0x0a0;
const VIRTIO_MMIO_QUEUE_USED_HIGH = 0x0a4;

const VIRTIO_STATUS_ACKNOWLEDGE = 1;
const VIRTIO_STATUS_DRIVER = 2;
const VIRTIO_STATUS_DRIVER_OK = 4;
const VIRTIO_STATUS_FEATURES_OK = 8;
const VIRTIO_STATUS_NEEDS_RESET = 64;
const VIRTIO_STATUS_FAILED = 128;

const VRING_DESC_F_NEXT: u16 = 1;
const VRING_DESC_F_WRITE: u16 = 2;
const VRING_DESC_F_INDIRECT: u16 = 4;

// =========================================================
// Queue Operations
// =========================================================

fn queue_init() void {
    // Select queue 0 (controlq)
    mmio_write(VIRTIO_MMIO_QUEUE_SEL, 0);

    const max = mmio_read(VIRTIO_MMIO_QUEUE_NUM_MAX);
    uart.print("[GPU] Queue 0 Max Size: ");
    uart.print_hex(max);
    uart.print("\n");

    if (max == 0) {
        uart.print("[GPU] Queue 0 not available!\n");
        return;
    }

    mmio_write(VIRTIO_MMIO_QUEUE_NUM, QUEUE_SIZE);

    const version = mmio_read(VIRTIO_MMIO_VERSION);

    if (version == 1) {
        // Legacy VirtIO MMIO v1
        const queue_addr = @intFromPtr(&queue);
        const pfn = queue_addr / 4096; // Page frame number

        mmio_write(VIRTIO_MMIO_QUEUE_ALIGN, 4096);
        mmio_write(VIRTIO_MMIO_QUEUE_PFN, @truncate(pfn));

        uart.print("[GPU] Legacy queue (v1) initialized at PFN 0x");
        uart.print_hex(pfn);
        uart.print("\n");
    } else {
        // Modern VirtIO MMIO v2
        const desc_addr = @intFromPtr(&queue.desc);
        const avail_addr = @intFromPtr(&queue.avail);
        const used_addr = @intFromPtr(&queue.used);

        mmio_write(VIRTIO_MMIO_QUEUE_DESC_LOW, @truncate(desc_addr));
        mmio_write(VIRTIO_MMIO_QUEUE_DESC_HIGH, @truncate(desc_addr >> 32));
        mmio_write(VIRTIO_MMIO_QUEUE_AVAIL_LOW, @truncate(avail_addr));
        mmio_write(VIRTIO_MMIO_QUEUE_AVAIL_HIGH, @truncate(avail_addr >> 32));
        mmio_write(VIRTIO_MMIO_QUEUE_USED_LOW, @truncate(used_addr));
        mmio_write(VIRTIO_MMIO_QUEUE_USED_HIGH, @truncate(used_addr >> 32));

        mmio_write(VIRTIO_MMIO_QUEUE_READY, 1);
        uart.print("[GPU] Modern queue (v2) initialized.\n");
    }

    queue.avail.idx = 0;
    last_used_idx = 0;
}

fn send_command(ptr: [*]const u8, len: usize) void {
    const phys_cmd = @intFromPtr(ptr);
    const phys_resp = @intFromPtr(&resp_buf);

    // Descriptor 0: Command (device read)
    queue.desc[0] = .{
        .addr = phys_cmd,
        .len = @intCast(len),
        .flags = VRING_DESC_F_NEXT,
        .next = 1,
    };

    // Descriptor 1: Response (device write)
    queue.desc[1] = .{
        .addr = phys_resp,
        .len = @sizeOf(@TypeOf(resp_buf)),
        .flags = VRING_DESC_F_WRITE,
        .next = 0,
    };

    // Add to available ring
    queue.avail.ring[queue.avail.idx % QUEUE_SIZE] = 0;
    asm volatile ("fence" ::: .{ .memory = true });
    queue.avail.idx +%= 1;
    asm volatile ("fence" ::: .{ .memory = true });

    // Notify device
    mmio_write(VIRTIO_MMIO_QUEUE_NOTIFY, 0);

    // Wait for response (polling)
    var timeout: usize = 0;
    while (last_used_idx == queue.used.idx) {
        asm volatile ("" ::: .{ .memory = true });
        timeout += 1;
        if (timeout % 10000000 == 0) {
            uart.print("[GPU] Polling... last=");
            uart.print_hex(last_used_idx);
            uart.print(" current=");
            uart.print_hex(queue.used.idx);
            uart.print("\n");
        }
    }
    last_used_idx = queue.used.idx;
}

// =========================================================
// GPU Commands
// =========================================================

fn cmd_resource_create_2d() void {
    const cmd = @as(*VirtioGpuResourceCreate2D, @ptrCast(@alignCast(&cmd_buf)));
    cmd.* = .{
        .hdr = .{
            .type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
            .flags = 0,
            .fence_id = 0,
            .ctx_id = 0,
            .padding = 0,
        },
        .resource_id = RESOURCE_ID,
        .format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
        .width = fb.WIDTH,
        .height = fb.HEIGHT,
    };
    send_command(&cmd_buf, @sizeOf(VirtioGpuResourceCreate2D));
}

fn cmd_attach_backing() void {
    // We need to send the header + 1 mem entry
    const AttachCmd = extern struct {
        hdr: VirtioGpuCtrlHdr,
        resource_id: u32,
        nr_entries: u32,
        entry: VirtioGpuMemEntry,
    };

    const cmd = @as(*AttachCmd, @ptrCast(@alignCast(&cmd_buf)));
    cmd.* = .{
        .hdr = .{
            .type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
            .flags = 0,
            .fence_id = 0,
            .ctx_id = 0,
            .padding = 0,
        },
        .resource_id = RESOURCE_ID,
        .nr_entries = 1,
        .entry = .{
            .addr = fb.get_buffer_phys(),
            .length = @intCast(fb.get_size()),
            .padding = 0,
        },
    };
    send_command(&cmd_buf, @sizeOf(AttachCmd));
}

fn cmd_set_scanout() void {
    const cmd = @as(*VirtioGpuSetScanout, @ptrCast(@alignCast(&cmd_buf)));
    cmd.* = .{
        .hdr = .{
            .type = VIRTIO_GPU_CMD_SET_SCANOUT,
            .flags = 0,
            .fence_id = 0,
            .ctx_id = 0,
            .padding = 0,
        },
        .r_x = 0,
        .r_y = 0,
        .r_width = fb.WIDTH,
        .r_height = fb.HEIGHT,
        .scanout_id = 0,
        .resource_id = RESOURCE_ID,
    };
    send_command(&cmd_buf, @sizeOf(VirtioGpuSetScanout));
}

fn cmd_transfer_2d() void {
    const cmd = @as(*VirtioGpuTransferToHost2D, @ptrCast(@alignCast(&cmd_buf)));
    cmd.* = .{
        .hdr = .{
            .type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
            .flags = 0,
            .fence_id = 0,
            .ctx_id = 0,
            .padding = 0,
        },
        .r_x = 0,
        .r_y = 0,
        .r_width = fb.WIDTH,
        .r_height = fb.HEIGHT,
        .offset = 0,
        .resource_id = RESOURCE_ID,
        .padding = 0,
    };
    send_command(&cmd_buf, @sizeOf(VirtioGpuTransferToHost2D));
}

fn cmd_resource_flush() void {
    const cmd = @as(*VirtioGpuResourceFlush, @ptrCast(@alignCast(&cmd_buf)));
    cmd.* = .{
        .hdr = .{
            .type = VIRTIO_GPU_CMD_RESOURCE_FLUSH,
            .flags = 0,
            .fence_id = 0,
            .ctx_id = 0,
            .padding = 0,
        },
        .r_x = 0,
        .r_y = 0,
        .r_width = fb.WIDTH,
        .r_height = fb.HEIGHT,
        .resource_id = RESOURCE_ID,
        .padding = 0,
    };
    send_command(&cmd_buf, @sizeOf(VirtioGpuResourceFlush));
}

// =========================================================
// Public API
// =========================================================

pub fn probe(base: usize) bool {
    mmio_base = base;

    const magic = mmio_read(VIRTIO_MMIO_MAGIC_VALUE);
    const version = mmio_read(VIRTIO_MMIO_VERSION);
    const device_id = mmio_read(VIRTIO_MMIO_DEVICE_ID);

    // Debug: Print what we found
    uart.print("[GPU Probe] 0x");
    uart.print_hex(base);
    uart.print(" Magic=0x");
    uart.print_hex(magic);
    uart.print(" Ver=");
    uart.print_hex(version);
    uart.print(" DevID=");
    uart.print_hex(device_id);
    uart.print("\n");

    // Magic = "virt" (0x74726976), Version = 1 or 2, Device ID = 16 (GPU)
    if (magic != 0x74726976) return false;
    if (version != 1 and version != 2) return false;
    if (device_id != 16) return false;

    uart.print("[GPU] VirtIO-GPU found at 0x");
    uart.print_hex(base);
    uart.print("\n");

    return true;
}

pub fn init(base: usize) void {
    if (!probe(base)) return;

    // Reset
    mmio_write(VIRTIO_MMIO_STATUS, 0);

    // Acknowledge + Driver
    mmio_write(VIRTIO_MMIO_STATUS, 1);
    mmio_write(VIRTIO_MMIO_STATUS, 1 | 2);

    // Setup queue
    queue_init();

    // Driver OK
    mmio_write(VIRTIO_MMIO_STATUS, 1 | 2 | 4);

    // Initialize framebuffer
    fb.init();

    // GPU Setup sequence
    uart.print("[GPU] Creating 2D Resource...\n");
    cmd_resource_create_2d();

    uart.print("[GPU] Attaching Backing...\n");
    cmd_attach_backing();

    uart.print("[GPU] Setting Scanout...\n");
    cmd_set_scanout();

    initialized = true;
    uart.print("[GPU] VirtIO-GPU initialized. Resolution: 800x600\n");

    // Draw initial test pattern
    fb.fill_rect(100, 100, 200, 50, 0xFF00FF00); // Neon green bar
    flush();
}

pub fn flush() void {
    if (!initialized) return;

    cmd_transfer_2d();
    cmd_resource_flush();
}

// Export for Nim
export fn virtio_gpu_init(base: usize) void {
    init(base);
}

export fn virtio_gpu_flush() void {
    flush();
}
