#![allow(unused_variables)]

use core::alloc::GlobalAlloc;
use core::ffi::{c_size_t, c_void};

use enumset::EnumSet;

use crate::ffi::string::__xkbc_memcpy;

// Here we select the allocator to use for libxkbcommon.
pub use crate::ram::PSRAM_ALLOCATOR as XKBC_ALLOCATOR;

// Implementation based on esp-alloc's `compat` feature.

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_malloc(size: c_size_t) -> *mut c_void {
    unsafe { malloc_with_caps(size, EnumSet::empty()) as *mut _ }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_malloc(size: c_size_t) -> *mut c_void {
    unsafe { __xkbc_malloc(size) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_calloc(number: c_size_t, size: c_size_t) -> *mut c_void {
    let total_size = number * size;
    unsafe {
        let ptr = __xkbc_malloc(total_size) as *mut u8;

        if !ptr.is_null() {
            for i in 0..total_size as isize {
                ptr.offset(i).write_volatile(0);
            }
        }

        ptr as *mut _
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_calloc(number: c_size_t, size: c_size_t) -> *mut c_void {
    unsafe { __xkbc_calloc(number, size) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_realloc(ptr: *mut c_void, new_size: c_size_t) -> *mut c_void {
    unsafe { realloc_with_caps(ptr as *mut _, new_size, EnumSet::empty()) as *mut _ }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_realloc(ptr: *mut c_void, new_size: c_size_t) -> *mut c_void {
    unsafe { __xkbc_realloc(ptr, new_size) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_free(ptr: *mut c_void) {
    if ptr.is_null() {
        return;
    }

    unsafe {
        let ptr = ptr.offset(-4);
        let total_size = *(ptr as *const usize);

        XKBC_ALLOCATOR.dealloc(
            ptr as *mut u8,
            core::alloc::Layout::from_size_align_unchecked(total_size, 4),
        )
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_free(ptr: *mut c_void) {
    unsafe {
        __xkbc_free(ptr);
    }
}

unsafe fn malloc_with_caps(size: usize, caps: EnumSet<crate::MemoryCapability>) -> *mut u8 {
    let total_size = size + 4;

    unsafe {
        let ptr = XKBC_ALLOCATOR.alloc_caps(
            caps,
            core::alloc::Layout::from_size_align_unchecked(total_size, 4),
        );

        if ptr.is_null() {
            return ptr;
        }

        *(ptr as *mut usize) = total_size;
        ptr.offset(4)
    }
}

unsafe fn realloc_with_caps(
    ptr: *mut u8,
    new_size: usize,
    caps: enumset::EnumSet<crate::MemoryCapability>,
) -> *mut u8 {
    unsafe {
        let p = malloc_with_caps(new_size, caps);
        if !p.is_null() && !ptr.is_null() {
            let len = usize::min(
                (ptr as *const u32).sub(1).read_volatile() as usize,
                new_size,
            );
            __xkbc_memcpy(p as *mut _, ptr as *const _, len);
            __xkbc_free(ptr as *mut _);
        }
        p
    }
}
