use core::marker::PhantomData;

use std::alloc;
use std::mem;
use std::ptr;

pub(crate) trait RawBlockTraits {
    fn init_size() -> usize;
    fn align() -> usize;

    #[inline]
    fn base_offset(base: *const u8) -> *const u8 {
        base
    }
}

#[derive(Debug)]
pub(crate) struct RawBlock<T: RawBlockTraits> {
    pub(crate) size: usize,
    pub(crate) base: *const u8,
    pub(crate) top: *const u8,
    _marker: PhantomData<T>,
}

impl<T: RawBlockTraits> RawBlock<T> {
    pub(crate) fn new() -> Self {
        let mut block = RawBlock {
            size: 0,
            base: ptr::null(),
            top: ptr::null(),
            _marker: PhantomData,
        };

        unsafe {
            block.grow();
        }

        block
    }

    unsafe fn init_at_size(&mut self, cap: usize) {
        let layout = alloc::Layout::from_size_align_unchecked(cap, T::align());

        self.base = alloc::alloc(layout) as *const _;
        self.size = cap;

        self.top = T::base_offset(self.base);
    }

    pub(super) unsafe fn grow(&mut self) {
        if self.size == 0 {
            self.init_at_size(T::init_size());
        } else {
            let layout = alloc::Layout::from_size_align_unchecked(T::init_size(), T::align());
            let top_dist = self.top as usize - self.base as usize;

            self.base = alloc::realloc(self.base as *mut _, layout, self.size * 2) as *const _;
            self.top = (self.base as usize + top_dist) as *const _;
            self.size *= 2;
        }
    }

    fn empty_block() -> Self {
        RawBlock {
            size: 0,
            base: ptr::null(),
            top: ptr::null(),
            _marker: PhantomData,
        }
    }

    #[inline]
    pub(crate) fn take(&mut self) -> Self {
        mem::replace(self, Self::empty_block())
    }

    #[inline]
    fn free_space(&self) -> usize {
        debug_assert!(
            self.top >= self.base,
            "self.top = {:?} < {:?} = self.base",
            self.top,
            self.base
        );

        self.size - (self.top as usize - self.base as usize)
    }

    #[inline]
    pub(crate) unsafe fn new_block(&mut self, size: usize) -> *const u8 {
        loop {
            if self.free_space() >= size {
                return (self.top as usize + size) as *const _;
            } else {
                self.grow();
            }
        }
    }

    pub(crate) fn deallocate(&mut self) {
        unsafe {
            let layout = alloc::Layout::from_size_align_unchecked(self.size, T::align());

            alloc::dealloc(self.base as *mut u8, layout);

            self.top = ptr::null();
            self.base = ptr::null();
            self.size = 0;
        }
    }
}
