use crate::{
    api::{VMHooksApi, VMHooksApiBackend, i32_to_bool},
    executor::debug::ContractDebugInstanceState,
};
use multiversx_chain_vm::executor::MemPtr;
use multiversx_sc::{
    api::{HandleConstraints, InvalidSliceError, ManagedBufferApiImpl, use_raw_handle},
    types::BoxedBytes,
};

impl<VHB: VMHooksApiBackend> ManagedBufferApiImpl for VMHooksApi<VHB> {
    fn mb_new_empty(&self) -> Self::ManagedBufferHandle {
        let raw_handle = self.with_vm_hooks(|vh| vh.mbuffer_new());
        use_raw_handle(raw_handle)
    }

    fn mb_new_from_bytes(&self, bytes: &[u8]) -> Self::ManagedBufferHandle {
        let (offset, length) = ContractDebugInstanceState::main_memory_ptr(bytes);
        let raw_handle = self.with_vm_hooks(|vh| vh.mbuffer_new_from_bytes(offset, length));
        use_raw_handle(raw_handle)
    }

    fn mb_len(&self, handle: Self::ManagedBufferHandle) -> usize {
        self.with_vm_hooks_ctx_1(&handle, |vh| {
            vh.mbuffer_get_length(handle.get_raw_handle_unchecked())
        }) as usize
    }

    fn mb_to_boxed_bytes(&self, handle: Self::ManagedBufferHandle) -> BoxedBytes {
        self.with_vm_hooks_ctx_1(&handle, |vh| {
            let len = vh.mbuffer_get_length(handle.get_raw_handle_unchecked())? as usize;
            let mut res = BoxedBytes::zeros(len);
            if len > 0 {
                let _ = vh.mbuffer_get_bytes(
                    handle.get_raw_handle_unchecked(),
                    res.as_mut_ptr() as MemPtr,
                );
            }
            Ok(res)
        })
    }

    fn mb_load_slice(
        &self,
        source_handle: Self::ManagedBufferHandle,
        starting_position: usize,
        dest_slice: &mut [u8],
    ) -> Result<(), InvalidSliceError> {
        let (offset, length) = ContractDebugInstanceState::main_memory_mut_ptr(dest_slice);
        let err = self.with_vm_hooks_ctx_1(&source_handle, |vh| {
            vh.mbuffer_get_byte_slice(
                source_handle.get_raw_handle_unchecked(),
                starting_position as i32,
                length as i32,
                offset,
            )
        });
        if err == 0 {
            Ok(())
        } else {
            Err(InvalidSliceError)
        }
    }

    fn mb_copy_slice(
        &self,
        source_handle: Self::ManagedBufferHandle,
        starting_pos: usize,
        slice_len: usize,
        dest_handle: Self::ManagedBufferHandle,
    ) -> Result<(), InvalidSliceError> {
        let err = self.with_vm_hooks_ctx_2(&source_handle, &dest_handle, |vh| {
            vh.mbuffer_copy_byte_slice(
                source_handle.get_raw_handle_unchecked(),
                starting_pos as i32,
                slice_len as i32,
                dest_handle.get_raw_handle_unchecked(),
            )
        });
        if err == 0 {
            Ok(())
        } else {
            Err(InvalidSliceError)
        }
    }

    fn mb_overwrite(&self, handle: Self::ManagedBufferHandle, value: &[u8]) {
        self.with_vm_hooks_ctx_1(&handle, |vh| {
            let (offset, length) = ContractDebugInstanceState::main_memory_ptr(value);
            vh.mbuffer_set_bytes(handle.get_raw_handle_unchecked(), offset, length)
        });
    }

    fn mb_set_slice(
        &self,
        dest_handle: Self::ManagedBufferHandle,
        starting_position: usize,
        source_slice: &[u8],
    ) -> Result<(), InvalidSliceError> {
        let (offset, length) = ContractDebugInstanceState::main_memory_ptr(source_slice);
        let err = self.with_vm_hooks_ctx_1(&dest_handle, |vh| {
            vh.mbuffer_set_byte_slice(
                dest_handle.get_raw_handle_unchecked(),
                starting_position as i32,
                length,
                offset,
            )
        });
        if err == 0 {
            Ok(())
        } else {
            Err(InvalidSliceError)
        }
    }

    fn mb_set_random(&self, dest_handle: Self::ManagedBufferHandle, length: usize) {
        self.with_vm_hooks_ctx_1(&dest_handle, |vh| {
            vh.mbuffer_set_random(dest_handle.get_raw_handle_unchecked(), length as i32)
        });
    }

    fn mb_append(
        &self,
        accumulator_handle: Self::ManagedBufferHandle,
        data_handle: Self::ManagedBufferHandle,
    ) {
        self.with_vm_hooks_ctx_2(&accumulator_handle, &data_handle, |vh| {
            vh.mbuffer_append(
                accumulator_handle.get_raw_handle_unchecked(),
                data_handle.get_raw_handle_unchecked(),
            )
        });
    }

    fn mb_append_bytes(&self, accumulator_handle: Self::ManagedBufferHandle, bytes: &[u8]) {
        let (offset, length) = ContractDebugInstanceState::main_memory_ptr(bytes);
        self.with_vm_hooks_ctx_1(&accumulator_handle, |vh| {
            let _ = vh.mbuffer_append_bytes(
                accumulator_handle.get_raw_handle_unchecked(),
                offset,
                length,
            )?;
            Ok(())
        });
    }

    fn mb_eq(
        &self,
        handle1: Self::ManagedBufferHandle,
        handle2: Self::ManagedBufferHandle,
    ) -> bool {
        i32_to_bool(self.with_vm_hooks_ctx_2(&handle1, &handle2, |vh| {
            vh.mbuffer_eq(
                handle1.get_raw_handle_unchecked(),
                handle2.get_raw_handle_unchecked(),
            )
        }))
    }

    fn mb_to_hex(
        &self,
        source_handle: Self::ManagedBufferHandle,
        dest_handle: Self::ManagedBufferHandle,
    ) {
        self.with_vm_hooks_ctx_2(&source_handle, &dest_handle, |vh| {
            vh.managed_buffer_to_hex(
                source_handle.get_raw_handle_unchecked(),
                dest_handle.get_raw_handle_unchecked(),
            )
        })
    }
}
