use core::cell::UnsafeCell;
use core::ops::Deref;
use core::sync::atomic::{AtomicBool, Ordering};

#[cfg_attr(feature = "unstable_docs", doc(cfg(not(feature = "std"))))]
pub struct Lazy<T, F = fn() -> T> {
    value: UnsafeCell<Option<T>>,
    is_initialized: AtomicBool,
    initializer: UnsafeCell<Option<F>>,
}

unsafe impl<T, F> Sync for Lazy<T, F>
where
    T: Send + Sync,
    F: Send + Sync,
{
}

impl<T, F> Lazy<T, F>
where
    F: FnOnce() -> T,
{
    pub const fn new(initializer: F) -> Self {
        Self {
            value: UnsafeCell::new(None),
            is_initialized: AtomicBool::new(false),
            initializer: UnsafeCell::new(Some(initializer)),
        }
    }

    pub fn get(&self) -> &T {
        if !self.is_initialized.load(Ordering::Acquire) {
            self.initialize();
        }
        unsafe { (*self.value.get()).as_ref().unwrap() }
    }

    fn initialize(&self) {
        let already_initialized = self
            .is_initialized
            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
            .is_err();
        if !already_initialized {
            let initializer = unsafe { (*self.initializer.get()).take().unwrap() };
            let value = initializer();
            unsafe { *self.value.get() = Some(value) };
        }
    }
}

impl<T, F> Deref for Lazy<T, F>
where
    F: FnOnce() -> T,
{
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.get()
    }
}
