// TODO: Turn apic keyboard interrupt into a standard ipc message
use {
    log::trace,
    spin::{Lazy, Mutex},
    x2apic::lapic::{LocalApic, LocalApicBuilder},
    x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
};

pub unsafe fn init() {
    trace!("Initialising IDT");
    IDT.load();
    Lazy::force(&LAPIC);
    x86_64::instructions::interrupts::enable();
}

#[repr(u8)]
enum Interrupt {
    Timer = 32,

    ApicErr = u8::MAX - 1,
    Spurious = u8::MAX,
}

pub(crate) static LAPIC: Lazy<Mutex<LocalApic>> = Lazy::new(|| {
    let mut lapic = LocalApicBuilder::new()
        .timer_vector(Interrupt::Timer as usize)
        .error_vector(Interrupt::ApicErr as usize)
        .spurious_vector(Interrupt::Spurious as usize)
        .set_xapic_base(
            unsafe { x2apic::lapic::xapic_base() }
                + super::memory::HHDM_OFFSET.load(core::sync::atomic::Ordering::Relaxed),
        )
        .build()
        .expect("failed to setup Local APIC");
    unsafe { lapic.enable() };
    Mutex::new(lapic)
});

static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
    let mut idt = InterruptDescriptorTable::new();
    unsafe {
        idt.double_fault
            .set_handler_fn(double_fault)
            .set_stack_index(super::gdt::DOUBLE_FAULT_IX);
    }
    idt.page_fault.set_handler_fn(page_fault);

    idt[Interrupt::ApicErr as usize].set_handler_fn(apic_err);
    idt[Interrupt::Spurious as usize].set_handler_fn(spurious);
    idt[Interrupt::Timer as usize].set_handler_fn(timer);

    idt
});

extern "x86-interrupt" fn double_fault(stack_frame: InterruptStackFrame, error_code: u64) -> ! {
    panic!("Double fault: error code {error_code} \n{stack_frame:#?}")
}

extern "x86-interrupt" fn page_fault(
    stack_frame: InterruptStackFrame,
    error_code: PageFaultErrorCode,
) {
    panic!("Page fault ({error_code:?}): {stack_frame:?}")
}

extern "x86-interrupt" fn timer(_isf: InterruptStackFrame) {
    // TODO: Pause the running program then schedule the next program

    unsafe { LAPIC.lock().end_of_interrupt() };
}

extern "x86-interrupt" fn apic_err(_: InterruptStackFrame) {
    panic!("Internal APIC error");
}

extern "x86-interrupt" fn spurious(_: InterruptStackFrame) {
    unsafe { LAPIC.lock().end_of_interrupt() };
}