//! Kernel panic handler code
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};

// use device_api::interrupt::{IpiDeliveryTarget, IpiMessage};
use kernel_arch::{Architecture, ArchitectureImpl};
use libk::{
    arch::{Cpu, LocalCpu},
    debug::{self, PanicLoggerSink},
    device::display::console::flush_consoles,
};
use libk_util::sync::SpinFence;

use crate::arch::{Platform, PLATFORM};

static PANIC_HANDLED_FENCE: SpinFence = SpinFence::new();

// Just a simple sequencer to ensure secondary panics don't trash the screen
static PANIC_FINISHED_FENCE: SpinFence = SpinFence::new();
static PANIC_SEQUENCE: AtomicU32 = AtomicU32::new(0);

/// Panic handler for CPUs other than the one that initiated it
pub fn panic_secondary() -> ! {
    // Will also mask IRQs in this section
    let cpu = Cpu::local();

    PANIC_HANDLED_FENCE.signal();
    PANIC_FINISHED_FENCE.wait_one();

    while PANIC_SEQUENCE.load(Ordering::Acquire) != cpu.id() {
        core::hint::spin_loop();
    }

    log::error!(target: "raw", "X");
    flush_consoles();

    PANIC_SEQUENCE.fetch_add(1, Ordering::Release);

    loop {
        ArchitectureImpl::wait_for_interrupt();
    }
}

fn dump_panic_info(cpu: &LocalCpu, pi: &core::panic::PanicInfo) {
    let sink = PanicLoggerSink::lock();

    debug::panic_log!(sink, "--- BEGIN PANIC ---\n");
    debug::panic_log!(sink, "In CPU {}\n", cpu.id());
    debug::panic_log!(sink, "Kernel panic");

    if let Some(location) = pi.location() {
        debug::panic_log!(sink, " at {}:{}:\n", location.file(), location.line());
    } else {
        debug::panic_log!(sink, ":\n");
    }
    debug::panic_log!(sink, "\n{}\n", pi.message());
    debug::panic_log!(sink, "--- END PANIC ---\n");
}

pub(crate) fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
    let cpu = Cpu::local();

    static PANIC_HAPPENED: AtomicBool = AtomicBool::new(false);

    if PANIC_HAPPENED
        .compare_exchange(false, true, Ordering::Release, Ordering::Acquire)
        .is_ok()
    {
        // TODO IPI leads to triple fault sometimes in x86-64
        // // Let other CPUs know we're screwed
        // if unsafe {
        //     PLATFORM
        //         .send_ipi(IpiDeliveryTarget::OtherCpus, IpiMessage::Panic)
        //         .unwrap_or(false)
        // } {
        //     let ap_count = ArchitectureImpl::cpu_count() - 1;
        //     PANIC_HANDLED_FENCE.wait_all(ap_count);
        // }

        // unsafe { hack_locks() };

        dump_panic_info(&cpu, pi);

        PANIC_FINISHED_FENCE.signal();
        while PANIC_SEQUENCE.load(Ordering::Acquire) != cpu.id() {
            core::hint::spin_loop();
        }

        log::error!(target: "raw", "X");

        flush_consoles();

        PANIC_SEQUENCE.fetch_add(1, Ordering::Release);

        unsafe {
            PLATFORM.reset();
        }
    }

    loop {
        ArchitectureImpl::wait_for_interrupt();
    }
}
