#![allow(non_upper_case_globals)]

use core::arch::asm;

use moa_rt_define::{
    TrapFrameOps,
    aarch64::{
        ESR_ELx_CM, ESR_ELx_EC_DABT_CUR, ESR_ELx_EC_IABT_CUR, ESR_ELx_EC_IABT_LOW,
        ESR_ELx_FSC_PERM, ESR_ELx_FSC_TYPE, ESR_ELx_WNR, KERNEL_DS, PSR_MODE_EL0t, PSR_MODE_MASK,
        TrapFrame, esr_elx_ec,
    },
};

use crate::{
    bsp::__irqchip_irq_handler,
    irq::irqflags::{local_daif_mask, local_irq_disable, local_irq_restore},
    print, println,
    processor::{cpu_relax, this_processor_id},
    sched::{
        preempt::{NMI_OFFSET, preempt_count_add, preempt_count_sub},
        task::current,
    },
    smp::smp_send_stop,
    space::{
        addr::Uaddr,
        mm::{VmFlags, pgtabledef::PAGE_SIZE},
        uaccess::USER_SPACE_SIZE,
    },
};

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn handle_arch_irq(_regs: &mut TrapFrame) {
    unsafe {
        __irqchip_irq_handler();
    }
}

const HANDLER: [&str; 4] = ["Synchronous Abort", "IRQ", "FIQ", "Error"];

const ESR_CLASS_STR: [&str; 0x3f] = [
    "Unknown/Uncategorized",      // ESR_ELx_EC_UNKNOWN
    "WFI/WFE",                    // ESR_ELx_EC_WFx
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "CP15 MCR/MRC",               // ESR_ELx_EC_CP15_32
    "CP15 MCRR/MRRC",             // ESR_ELx_EC_CP15_64
    "CP14 MCR/MRC",               // ESR_ELx_EC_CP14_MR
    "CP14 LDC/STC",               // ESR_ELx_EC_CP14_LS
    "ASIMD",                      // ESR_ELx_EC_FP_ASIMD
    "CP10 MRC/VMRS",              // ESR_ELx_EC_CP10_ID
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC      9
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC      10
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC      11
    "CP14 MCRR/MRRC",             // ESR_ELx_EC_CP14_64      12
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "PSTATE.IL",                  // ESR_ELx_EC_ILL       14
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "SVC (AArch32)",              // ESR_ELx_EC_SVC32     17
    "HVC (AArch32)",              // ESR_ELx_EC_HVC32     18
    "SMC (AArch32)",              // ESR_ELx_EC_SMC32
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "SVC (AArch64)",              // ESR_ELx_EC_SVC64     21
    "HVC (AArch64)",              // ESR_ELx_EC_HVC64     22
    "SMC (AArch64)",              // ESR_ELx_EC_SMC64         23
    "MSR/MRS (AArch64)",          // ESR_ELx_EC_SYS64     24
    "SVE",                        // ESR_ELx_EC_SVE       25
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "EL3 IMP DEF",                // ESR_ELx_EC_IMP_DEF       31
    "IABT (lower EL)",            // ESR_ELx_EC_IABT_LOW      32
    "IABT (current EL)",          // ESR_ELx_EC_IABT_CUR
    "PC Alignment",               // ESR_ELx_EC_PC_ALIGN
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "DABT (lower EL)",            // ESR_ELx_EC_DABT_LOW
    "DABT (current EL)",          // ESR_ELx_EC_DABT_CUR
    "SP Alignment",               // ESR_ELx_EC_SP_ALIGN
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "FP (AArch32)",               // ESR_ELx_EC_FP_EXC32      40
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "FP (AArch64)",               // ESR_ELx_EC_FP_EXC64      44
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "SError",                     // ESR_ELx_EC_SERROR
    "Breakpoint (lower EL)",      // ESR_ELx_EC_BREAKPT_LOW
    "Breakpoint (current EL)",    // ESR_ELx_EC_BREAKPT_CUR
    "Software Step (lower EL)",   // ESR_ELx_EC_SOFTSTP_LOW
    "Software Step (current EL)", // ESR_ELx_EC_SOFTSTP_CUR
    "Watchpoint (lower EL)",      // ESR_ELx_EC_WATCHPT_LOW
    "Watchpoint (current EL)",    // ESR_ELx_EC_WATCHPT_CUR
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "BKPT (AArch32)",             // ESR_ELx_EC_BKPT32        56
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "Vector catch (AArch32)",     // ESR_ELx_EC_VECTOR32
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "BRK (AArch64)",              // ESR_ELx_EC_BRK64
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
    "UNRECOGNIZED EC",            // UNRECOGNIZED EC
];

const fn esr_get_class_string(esr: u32) -> &'static str {
    ESR_CLASS_STR[esr_elx_ec(esr as usize)]
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn bad_mode(_regs: &mut TrapFrame, reason: usize, esr: u32) {
    println!(
        "Bad mod in {} handler detected on CPU{}, code {:#x} -- {}",
        HANDLER[reason],
        this_processor_id().raw(),
        esr,
        esr_get_class_string(esr)
    );

    local_daif_mask();
    panic!("bad mode");
}

fn do_bad(_: usize, _: u32, _: &mut TrapFrame) -> bool {
    false
}

fn do_sea(addr: usize, esr: u32, regs: &mut TrapFrame) -> bool {
    let inf = esr_to_fault_info(esr);

    arm64_notify_die(inf.name, regs, addr, esr);
    true
}

fn do_alignment_fault(addr: usize, esr: u32, regs: &mut TrapFrame) -> bool {
    do_bad_area(addr, esr, regs);
    true
}

fn do_translation_fault(addr: usize, esr: u32, regs: &mut TrapFrame) -> bool {
    if addr < USER_SPACE_SIZE {
        return do_page_fault(addr, esr, regs);
    }
    do_bad_area(addr, esr, regs);
    true
}

fn do_bad_area(addr: usize, esr: u32, regs: &mut TrapFrame) {
    if user_mode(regs) {
        let inf = esr_to_fault_info(esr);
        arm64_force_sig_fault(0, esr as usize, addr as u64, inf.name);
    } else {
        __do_kernel_fault(addr, esr, regs);
    }
}

fn is_el0_instruction_abort(esr: u32) -> bool {
    esr_elx_ec(esr as usize) == ESR_ELx_EC_IABT_LOW
}

fn is_el1_permission_fault(_addr: usize, esr: u32, _regs: &mut TrapFrame) -> bool {
    let ec = esr_elx_ec(esr as usize);
    if ec != ESR_ELx_EC_DABT_CUR && ec != ESR_ELx_EC_IABT_CUR {
        return false;
    }

    let fsc_type = esr as usize & ESR_ELx_FSC_TYPE;
    if fsc_type == ESR_ELx_FSC_PERM {
        return true;
    }

    false
}

fn is_el1_instruction_abort(esr: u32) -> bool {
    esr_elx_ec(esr as usize) == ESR_ELx_EC_IABT_CUR
}

fn do_page_fault(addr: usize, esr: u32, regs: &mut TrapFrame) -> bool {
    let mut vm_flags = VmFlags::VM_READ;
    if is_el0_instruction_abort(esr) {
        vm_flags = VmFlags::VM_EXEC;
    } else if (esr as usize & ESR_ELx_WNR != 0) && (esr as usize & ESR_ELx_CM == 0) {
        vm_flags = VmFlags::VM_WRITE;
    }

    if addr < USER_SPACE_SIZE && is_el1_permission_fault(addr, esr, regs) {
        if regs.get_orig_addr_limit() == KERNEL_DS as u64 {
            die_kernel_fault("access to user memory with fs=KERNEL_DS", addr, esr, regs);
        }

        if is_el1_instruction_abort(esr) {
            die_kernel_fault("execution of user memory", addr, esr, regs);
        }

        if !search_exception_tables(regs.pc() as u64) {
            die_kernel_fault("access to user memory outside uaccess routines", addr, esr, regs);
        }
    }

    let mm = current().mm();
    if let Some(m) = mm {
        m.handle_mm_fault(Uaddr::from(addr), vm_flags).unwrap();
    } else {
        __do_kernel_fault(addr, esr, regs);
    }
    true
}

fn search_exception_tables(_pc: u64) -> bool {
    false
}

fn fixup_exception(_regs: &mut TrapFrame) -> bool {
    false
}

fn die_kernel_fault(msg: &str, addr: usize, _esr: u32, _regs: &mut TrapFrame) {
    panic!("Unable to handle kernel {} at virtual address {:#016x}", msg, addr);
}

fn __do_kernel_fault(addr: usize, esr: u32, regs: &mut TrapFrame) {
    if !is_el1_instruction_abort(esr) && fixup_exception(regs) {
        return;
    }

    let msg;
    if is_el1_permission_fault(addr, esr, regs) {
        if esr as usize & ESR_ELx_WNR != 0 {
            msg = "write to read-only memory";
        } else {
            msg = "read from unreadable memory";
        }
    } else if addr < PAGE_SIZE {
        msg = "NULL pointer dereference";
    } else {
        msg = "paging request";
    }

    die_kernel_fault(msg, addr, esr, regs);
}

struct FaultInfo {
    f: fn(addr: usize, esr: u32, regs: &mut TrapFrame) -> bool,
    name: &'static str,
}

const FAULT_INFO: [FaultInfo; 64] = [
    FaultInfo { f: do_bad, name: "ttbr address size fault" },
    FaultInfo { f: do_bad, name: "level 1 address size fault" },
    FaultInfo { f: do_bad, name: "level 2 address size fault" },
    FaultInfo { f: do_bad, name: "level 3 address size fault" },
    FaultInfo { f: do_translation_fault, name: "level 0 translation fault" },
    FaultInfo { f: do_translation_fault, name: "level 1 translation fault" },
    FaultInfo { f: do_translation_fault, name: "level 2 translation fault" },
    FaultInfo { f: do_translation_fault, name: "level 3 translation fault" },
    FaultInfo { f: do_bad, name: "unknown 8" },
    FaultInfo { f: do_page_fault, name: "level 1 access flag fault" },
    FaultInfo { f: do_page_fault, name: "level 2 access flag fault" },
    FaultInfo { f: do_page_fault, name: "level 3 access flag fault" },
    FaultInfo { f: do_bad, name: "unknown 12" },
    FaultInfo { f: do_page_fault, name: "level 1 permission fault" },
    FaultInfo { f: do_page_fault, name: "level 2 permission fault" },
    FaultInfo { f: do_page_fault, name: "level 3 permission fault" },
    FaultInfo { f: do_sea, name: "synchronous external abort" },
    FaultInfo { f: do_bad, name: "unknown 17" },
    FaultInfo { f: do_bad, name: "unknown 18" },
    FaultInfo { f: do_bad, name: "unknown 19" },
    FaultInfo { f: do_sea, name: "synchronous external abort" },
    FaultInfo { f: do_sea, name: "level 0 (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 1 (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 2 (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 3 (translation table walk)" },
    FaultInfo { f: do_bad, name: "unknown 25" },
    FaultInfo { f: do_bad, name: "unknown 26" },
    FaultInfo { f: do_bad, name: "unknown 27" },
    FaultInfo { f: do_sea, name: "level 0 synchronous parity error (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 1 synchronous parity error (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 2 synchronous parity error (translation table walk)" },
    FaultInfo { f: do_sea, name: "level 3 synchronous parity error (translation table walk)" },
    FaultInfo { f: do_bad, name: "unknown 32" },
    FaultInfo { f: do_alignment_fault, name: "alignment fault" },
    FaultInfo { f: do_bad, name: "unknown 34" },
    FaultInfo { f: do_bad, name: "unknown 35" },
    FaultInfo { f: do_bad, name: "unknown 36" },
    FaultInfo { f: do_bad, name: "unknown 37" },
    FaultInfo { f: do_bad, name: "unknown 38" },
    FaultInfo { f: do_bad, name: "unknown 39" },
    FaultInfo { f: do_bad, name: "unknown 40" },
    FaultInfo { f: do_bad, name: "unknown 41" },
    FaultInfo { f: do_bad, name: "unknown 42" },
    FaultInfo { f: do_bad, name: "unknown 43" },
    FaultInfo { f: do_bad, name: "unknown 44" },
    FaultInfo { f: do_bad, name: "unknown 45" },
    FaultInfo { f: do_bad, name: "unknown 46" },
    FaultInfo { f: do_bad, name: "unknown 47" },
    FaultInfo { f: do_bad, name: "TLB conflict abort" },
    FaultInfo { f: do_bad, name: "Unsupported atomic hardware update fault" },
    FaultInfo { f: do_bad, name: "unknown 50" },
    FaultInfo { f: do_bad, name: "unknown 51" },
    FaultInfo { f: do_bad, name: "implementation fault (lockdown abort)" },
    FaultInfo { f: do_bad, name: "implementation fault (unsupported exclusive)" },
    FaultInfo { f: do_bad, name: "unknown 54" },
    FaultInfo { f: do_bad, name: "unknown 55" },
    FaultInfo { f: do_bad, name: "unknown 56" },
    FaultInfo { f: do_bad, name: "unknown 57" },
    FaultInfo { f: do_bad, name: "unknown 58" },
    FaultInfo { f: do_bad, name: "unknown 59" },
    FaultInfo { f: do_bad, name: "unknown 60" },
    FaultInfo { f: do_bad, name: "section domain fault" },
    FaultInfo { f: do_bad, name: "page domain fault" },
    FaultInfo { f: do_bad, name: "unknown 63" },
];

const fn esr_to_fault_info(esr: u32) -> &'static FaultInfo {
    &FAULT_INFO[esr as usize & 0x3f]
}

fn user_mode(regs: &mut TrapFrame) -> bool {
    (regs.get_pstate() as usize & PSR_MODE_MASK) == PSR_MODE_EL0t
}

fn arm64_notify_die(str: &str, regs: &mut TrapFrame, addr: usize, err: u32) {
    if user_mode(regs) {
        arm64_force_sig_fault(0, err as usize, addr as u64, str);
    } else {
        die(str, regs, err);
    }
}

fn die(str: &str, _regs: &mut TrapFrame, _err: u32) {
    panic!("Fatal exception: {}", str);
}

/// # Safety
/// 由启动代码调用
#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_mem_abort(addr: usize, esr: u32, regs: &mut TrapFrame) {
    let inf = esr_to_fault_info(esr);
    if (inf.f)(addr, esr, regs) {
        return;
    }

    assert!(user_mode(regs), "Unhandled fault at {addr:#x}");

    arm64_notify_die(inf.name, regs, addr, esr);
}

/// # Safety
/// 由启动代码调用
#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_sp_pc_abort(addr: usize, esr: u32, regs: &mut TrapFrame) {
    if user_mode(regs) {
        local_irq_restore(0);
    }

    arm64_notify_die("SP/PC alignment exception", regs, addr, esr);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_undefinstr(regs: &mut TrapFrame) {
    arm64_notify_die("do_undefinstr", regs, 0, 0);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_debug_exception(addr: usize, esr: u32, regs: &mut TrapFrame) {
    arm64_notify_die("do_debug_exception", regs, addr, esr);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn preempt_schedule_irq() {
    println!("preempt_schedule_irq");
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_el0_ia_bp_hardening(addr: usize, esr: u32, regs: &mut TrapFrame) {
    local_irq_restore(0);
    do_mem_abort(addr, esr, regs);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_fpsimd_acc(_esr: u32, _regs: &mut TrapFrame) {
    println!("do_fpsimd_acc");
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_sve_acc(esr: u32, regs: &mut TrapFrame) {
    arm64_notify_die("do_sve_acc", regs, 0, esr);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_fpsimd_exc(esr: u32, regs: &mut TrapFrame) {
    arm64_notify_die("do_fpsimd_exc", regs, 0, esr);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_sysinstr(esr: u32, regs: &mut TrapFrame) {
    arm64_notify_die("do_sysinstr", regs, 0, esr);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_notify_resume(_regs: &mut TrapFrame, _thread_flags: u32) {
    panic!("do_notify_resume");
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn el0_svc_handler(regs: &mut TrapFrame) {
    crate::syscall::syscall_dispatch(regs);
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn do_serror(_regs: &mut TrapFrame, _esr: u32) {
    nmi_enter();
    nmi_exit();
    panic!("do_serror");
}

#[doc(hidden)]
#[unsafe(no_mangle)]
#[allow(unreachable_pub)]
pub extern "C" fn bad_el0_sync(regs: &mut TrapFrame, _: i32, esr: u32) {
    let pc = regs.pc();
    arm64_force_sig_fault(0, esr as usize, pc as u64, "Bad EL0 synchronous exception");
}

fn arm64_force_sig_fault(signo: usize, code: usize, addr: u64, str: &str) {
    print!("{}: unhandled exception: ", current().name());
    if code != 0 {
        print!("{}, ESR {:#x}, ", esr_get_class_string(code as u32), code);
    }
    println!("{} in {:#x}, sig {}", str, addr, signo);

    local_irq_disable();
    smp_send_stop();
    loop {
        cpu_relax();
    }
}

#[inline(always)]
fn nmi_enter() {
    preempt_count_add(NMI_OFFSET as u32);
}

#[inline(always)]
fn nmi_exit() {
    preempt_count_sub(NMI_OFFSET as u32);
}

pub(super) fn arch_secondary_info() {
    let mpidr: usize;
    unsafe {
        asm!(
            "mrs {0}, mpidr_el1",
            out(reg) mpidr
        );
    }
    let midr_el1: usize;
    unsafe {
        asm!(
            "mrs {0}, midr_el1",
            out(reg) midr_el1
        );
    }

    println!(
        "CPU{}: Booted secondary processor {:#010x} [{:#08x}]",
        this_processor_id().raw(),
        mpidr & 0xff00ffffff,
        midr_el1
    );
}
