use crate::cell::UnsafeCell;
use crate::mem::MaybeUninit;
use crate::os::yggdrasil::signal::SignalHandler;
use crate::sync::atomic::AtomicBool;

use yggdrasil_rt::{
    process::{ExitCode as OsExitCode, Signal, SignalEntryData},
    sys,
};

const SIGNAL_STACK_SIZE: usize = 16384;
const MAX_SIGNAL_HANDLERS: usize = 8;

#[repr(C, align(0x1000))]
struct SignalStack {
    data: [u8; SIGNAL_STACK_SIZE],
}

struct SignalTableEntry {
    signal: Signal,
    handler: SignalHandler,
}

struct SignalTable {
    entries: [MaybeUninit<SignalTableEntry>; MAX_SIGNAL_HANDLERS],
    count: usize,
}

impl SignalTable {
    const fn new() -> Self {
        Self { entries: MaybeUninit::uninit_array(), count: 0 }
    }

    fn lookup(&self, signal: Signal) -> Option<SignalHandler> {
        for entry in self.entries[..self.count].iter() {
            let entry = unsafe { entry.assume_init_ref() };
            if entry.signal == signal {
                return Some(entry.handler);
            }
        }
        None
    }

    fn push(&mut self, signal: Signal, handler: SignalHandler) {
        assert!(self.count < MAX_SIGNAL_HANDLERS);
        self.entries[self.count].write(SignalTableEntry { signal, handler });
        self.count += 1;
    }
}

static mut SIGNAL_STACK: SignalStack = SignalStack { data: [0; SIGNAL_STACK_SIZE] };
static mut SIGNAL_TABLE: SignalTable = SignalTable::new();

pub(crate) unsafe fn set_signal_handler(signal: Signal, handler: SignalHandler) {
    SIGNAL_TABLE.push(signal, handler);
}

unsafe extern "C" fn signal_entry(data: &SignalEntryData) -> ! {
    let handler = SIGNAL_TABLE.lookup(data.signal).unwrap_or(SignalHandler::Terminate);
    let pid = crate::process::id();

    match handler {
        SignalHandler::Function(f) => {
            f(data.signal);
            sys::exit_signal(data);
        }
        SignalHandler::Ignore => {
            eprintln!("#{}: ignored {:?}", pid, data.signal);
            sys::exit_signal(data);
        }
        SignalHandler::Terminate => {
            eprintln!("#{}: {:?}", pid, data.signal);
            sys::exit_process(OsExitCode::BySignal(data.signal));
        }
    }
}

pub(super) unsafe fn signal_init() {
    let signal_sp = SIGNAL_STACK.data.as_mut_ptr() as usize + SIGNAL_STACK_SIZE;
    let signal_entry = signal_entry as usize;

    sys::set_signal_entry(signal_entry, signal_sp);
}
