use core::array;
use core::ptr::null;

use super::constants::{MAX_ATTACHED_MINIHEAPS_PER_CLASS, MIN_OBJECT_SIZE, NUM_SIZE_CLASSES};
use super::miniheap::{MiniHeap, MiniHeapId};
use super::platform;
use super::raw_sys;
use super::rng::Mwc;
use super::shuffle::ShuffleVector;
use super::size_map::byte_size_for_class;

#[derive(Clone, Copy, Debug)]
pub(crate) struct ClassState {
    pub shuffle: ShuffleVector,
    pub attached_ids: [MiniHeapId; MAX_ATTACHED_MINIHEAPS_PER_CLASS],
    pub attached_heaps: [*const MiniHeap; MAX_ATTACHED_MINIHEAPS_PER_CLASS],
    pub attached_len: u8,
    pub attached_cursor: u8,
}

impl ClassState {
    fn new(object_size: usize, seed1: u64, seed2: u64) -> Self {
        Self {
            shuffle: ShuffleVector::for_object_size(object_size, seed1, seed2),
            attached_ids: [MiniHeapId::new(0); MAX_ATTACHED_MINIHEAPS_PER_CLASS],
            attached_heaps: [null(); MAX_ATTACHED_MINIHEAPS_PER_CLASS],
            attached_len: 0,
            attached_cursor: 0,
        }
    }

    #[inline(always)]
    pub fn clear_attached(&mut self) {
        self.attached_len = 0;
        self.attached_cursor = 0;
        self.shuffle.clear();

        let mut index = 0usize;
        while index < MAX_ATTACHED_MINIHEAPS_PER_CLASS {
            self.attached_ids[index] = MiniHeapId::new(0);
            self.attached_heaps[index] = null();
            index += 1;
        }
    }

    #[inline(always)]
    pub fn attached_full(&self) -> bool {
        self.attached_len as usize == MAX_ATTACHED_MINIHEAPS_PER_CLASS
    }

    #[inline(always)]
    pub fn push_attached(&mut self, id: MiniHeapId, heap: *const MiniHeap) -> Option<u8> {
        if self.attached_full() {
            return None;
        }
        let index = self.attached_len as usize;
        self.attached_ids[index] = id;
        self.attached_heaps[index] = heap;
        self.attached_len += 1;
        Some(index as u8)
    }

    #[inline(always)]
    pub fn find_attached(&self, id: MiniHeapId) -> Option<u8> {
        let len = self.attached_len as usize;
        let mut i = 0usize;
        while i < len {
            if self.attached_ids[i] == id {
                return Some(i as u8);
            }
            i += 1;
        }
        None
    }

    #[inline(always)]
    pub fn heap_at(&self, index: usize) -> Option<&MiniHeap> {
        if index >= self.attached_len as usize {
            return None;
        }
        let heap = self.attached_heaps[index];
        if heap.is_null() {
            return None;
        }

        unsafe { Some(&*heap) }
    }
}

#[derive(Debug)]
pub struct ThreadLocalHeap {
    thread_id: u32,
    classes: [ClassState; NUM_SIZE_CLASSES],
}

impl ThreadLocalHeap {
    pub fn new() -> raw_sys::Result<Self> {
        let thread_id = platform::gettid()?;
        let mut seed_rng = Mwc::from_os_seed()?;
        let classes = array::from_fn(|class| {
            let object_size = byte_size_for_class(class as u8).max(MIN_OBJECT_SIZE);
            let seed1 = seed_rng.next_u32() as u64 + 1;
            let seed2 = seed_rng.next_u32() as u64 + 1;
            ClassState::new(object_size, seed1, seed2)
        });

        Ok(Self { thread_id, classes })
    }

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

    #[inline(always)]
    pub(crate) fn class(&self, class: u8) -> &ClassState {
        &self.classes[class as usize]
    }

    #[inline(always)]
    pub(crate) fn class_mut(&mut self, class: u8) -> &mut ClassState {
        &mut self.classes[class as usize]
    }
}
