// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: MIT

use crate::{LOGGER, format, log_info, log_trace};
use crate::{LogLevel, log_error};
use lazy_static::lazy_static;
use x86_64::PhysAddr;
use x86_64::structures::idt::*;

pub fn get_lapic_addr() -> PhysAddr {
    let apic_base = x86_64::registers::model_specific::ApicBase::read();
    let addr: PhysAddr = apic_base.0.start_address();
    log_info!("Found LAPIC at 0x{:x}, flags {:?}", addr, apic_base.1);
    addr
}

lazy_static! {
    pub static ref IDT: InterruptDescriptorTable = {
        let mut idt = InterruptDescriptorTable::new();
        // TODO: Rewrite using our own x86 interrupt wrapper
        // code. The x86_interrupt ABI is always breaking, and this
        // approach will fall apart once multiarch support is re-added.
        idt.double_fault.set_handler_fn(double_fault);
        idt.page_fault.set_handler_fn(page_fault);
        idt.general_protection_fault.set_handler_fn(gen_prot_fault);
        idt
    };
}

// Must do this to avoid unused function warning
#[allow(dead_code)]
extern "x86-interrupt" fn double_fault(info: InterruptStackFrame, _: u64) -> ! {
    crate::interrupt::double_fault(format!("{info:#?}"));
}

#[allow(dead_code)]
extern "x86-interrupt" fn page_fault(info: InterruptStackFrame, errcode: PageFaultErrorCode) {
    let addr = x86_64::registers::control::Cr2::read();
    log_trace!(
        "Fault: Page fault with {}, error code 0b{:b}",
        match addr {
            Ok(addr_inner) => format!("address 0x{:x}", addr_inner),
            Err(_) => "invalid virtual address".into(),
        },
        errcode
    );
    if errcode.contains(PageFaultErrorCode::USER_MODE) {
        // Fault occurred in usermode. Non fatal.
        todo!("Page fault handler (usermode)")
    } else {
        // Fault occurred in kernel mode. This is possibly fatal.
        // This is recoverable if we simply hit an unavailable page,
        // but unrecoverable if we hit a nonexistent or invalid page.
        if errcode.contains(PageFaultErrorCode::PROTECTION_VIOLATION)
            | errcode.contains(PageFaultErrorCode::MALFORMED_TABLE)
        {
            let info_formatted = format!("{info:#?}");
            crate::interrupt::page_fault_fatal(addr.unwrap().as_u64() as usize, info_formatted)
        } else {
            log_error!("Page Fault, trying to recover...");
            todo!("Page fault handler (kernel mode)")
        }
    }
}

#[allow(dead_code)]
extern "x86-interrupt" fn gen_prot_fault(info: InterruptStackFrame, _: u64) {
    crate::interrupt::gen_prot_fault(format!(
        "RIP 0x{:x}, CS {} ({:?})",
        info.instruction_pointer,
        info.code_segment.index(),
        info.code_segment.rpl()
    ));
}
