use core::ptr;
use Allocator;

pub struct System {
    _priv: (),
}

impl System {
    pub const fn new() -> System {
        System { _priv: () }
    }
}

#[cfg(target_arch = "riscv32")]
mod sys {
    use core::arch::asm;

    pub fn increase_heap(length: usize) -> Result<(usize, usize), ()> {
        let syscall_no_increase_heap = 10usize;
        let memory_flags_read_write = 2usize | 4usize;

        let mut a0 = syscall_no_increase_heap;
        let mut a1 = length;
        let mut a2 = memory_flags_read_write;

        unsafe {
            asm!(
                "ecall",
                inlateout("a0") a0,
                inlateout("a1") a1,
                inlateout("a2") a2,
                out("a3") _,
                out("a4") _,
                out("a5") _,
                out("a6") _,
                out("a7") _,
            )
        };

        let result = a0;
        let address = a1;
        let length = a2;

        // 3 is the "MemoryRange" type, and the result is only valid
        // if we get nonzero address and length.
        if result == 3 && address != 0 && length != 0 {
            Ok((address, length))
        } else {
            Err(())
        }
    }
}

unsafe impl Allocator for System {
    /// Allocate an additional `size` bytes on the heap, and return a new
    /// chunk of memory, as well as the size of the allocation and some
    /// flags. Since flags are unused on this platform, they will always
    /// be `0`.
    fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
        let size = if size == 0 {
            4096
        } else if size & 4095 == 0 {
            size
        } else {
            size + (4096 - (size & 4095))
        };

        if let Ok((address, length)) = sys::increase_heap(size) {
            let start = address - size + length;
            (start as *mut u8, size, 0)
        } else {
            (ptr::null_mut(), 0, 0)
        }
    }

    fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
        // TODO
        ptr::null_mut()
    }

    fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
        false
    }

    fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
        false
    }

    fn can_release_part(&self, _flags: u32) -> bool {
        false
    }

    fn allocates_zeros(&self) -> bool {
        true
    }

    fn page_size(&self) -> usize {
        4 * 1024
    }
}

#[cfg(feature = "global")]
pub fn acquire_global_lock() {
    // global feature should not be enabled
    unimplemented!()
}

#[cfg(feature = "global")]
pub fn release_global_lock() {
    // global feature should not be enabled
    unimplemented!()
}

#[cfg(feature = "global")]
pub unsafe fn enable_alloc_after_fork() {
    // platform does not support `fork()` call
}
