use core::sync::atomic::{AtomicBool, Ordering};

use crate::{io, ssp};

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

// TODO implement topological sorting for proper .init_array calls in dyn-loader
#[link_section = ".preinit_array"]
#[used]
static PREINIT_ARRAY: [extern "C" fn(); 1] = [init];

pub extern "C" fn init() {
    if INIT_COMPLETE.swap(true, Ordering::Acquire) {
        return;
    }

    unsafe {
        ssp::init();
        io::init();
    }
}

unsafe fn call_constructor_list(start: usize, end: usize) {
    log::debug!("call_constructor_list {:#x?}", start..end);
    let base = start as *const usize;
    let len = (end - start) / size_of::<usize>();
    for i in 0..len {
        let addr = base.add(i).read();
        if addr == 0 {
            continue;
        }
        log::debug!("call constructor: {:#x}", addr);
        let func = core::mem::transmute::<_, extern "C" fn()>(addr);
        func();
    }
}

pub fn call_constructors() {
    extern "C" {
        static __preinit_array_start: u8;
        static __preinit_array_end: u8;

        static __init_array_start: u8;
        static __init_array_end: u8;
    }

    // Global constructors already called
    if INIT_COMPLETE.load(Ordering::Acquire) {
        return;
    }

    unsafe {
        call_constructor_list(
            (&raw const __preinit_array_start).addr(),
            (&raw const __preinit_array_end).addr(),
        );
        call_constructor_list(
            (&raw const __init_array_start).addr(),
            (&raw const __init_array_end).addr(),
        );
    }
}
