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

use super::constants::{MAX_SUPPORTED_PAGE_SIZE, MIN_OBJECT_SIZE, MIN_SUPPORTED_PAGE_SIZE};
use super::platform;
static PAGE_SIZE_CACHE: AtomicUsize = AtomicUsize::new(0);

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PageConfig {
    size: usize,
    shift: u32,
    slots_per_span: usize,
}

impl PageConfig {
    #[inline(always)]
    pub fn get() -> Self {
        let size = page_size();
        Self {
            size,
            shift: size.trailing_zeros(),
            slots_per_span: size / MIN_OBJECT_SIZE,
        }
    }

    #[inline(always)]
    pub const fn size(self) -> usize {
        self.size
    }

    #[inline(always)]
    pub const fn shift(self) -> u32 {
        self.shift
    }

    #[inline(always)]
    pub const fn slots_per_span(self) -> usize {
        self.slots_per_span
    }
}

#[inline]
pub fn page_size() -> usize {
    let cached = PAGE_SIZE_CACHE.load(Ordering::Acquire);
    if cached != 0 {
        return cached;
    }

    let size = query_page_size();
    match PAGE_SIZE_CACHE.compare_exchange(0, size, Ordering::AcqRel, Ordering::Acquire) {
        Ok(_) => size,
        Err(existing) => existing,
    }
}

#[inline(always)]
pub fn page_shift() -> u32 {
    page_size().trailing_zeros()
}

#[inline(always)]
pub fn page_count(size: usize) -> usize {
    let page = page_size();
    size.div_ceil(page)
}

#[inline(always)]
pub fn round_up_to_page(size: usize) -> usize {
    page_count(size) * page_size()
}

#[inline(always)]
pub fn runtime_slots_per_span() -> usize {
    page_size() / MIN_OBJECT_SIZE
}

fn query_page_size() -> usize {
    let size = platform::page_size();
    assert!(
        size.is_power_of_two(),
        "page size is not a power of two: {size}"
    );
    assert!(
        (MIN_SUPPORTED_PAGE_SIZE..=MAX_SUPPORTED_PAGE_SIZE).contains(&size),
        "unsupported page size {size}; supported range is {}..={}",
        MIN_SUPPORTED_PAGE_SIZE,
        MAX_SUPPORTED_PAGE_SIZE
    );
    assert_eq!(
        size % MIN_OBJECT_SIZE,
        0,
        "page size {size} is not MIN_OBJECT_SIZE-aligned"
    );
    size
}
