/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::ffi::{call_with_output, ForeignBytes, RustCallStatus};

/// Support for passing an allocated-by-Rust buffer of bytes over the FFI.
///
/// We can pass a `Vec<u8>` to foreign language code by decomposing it into
/// its raw parts (buffer pointer, length, and capacity) and passing those
/// around as a struct. Naturally, this can be tremendously unsafe! So here
/// are the details:
///
///   * `RustBuffer` structs must only ever be constructed from a `Vec<u8>`,
///     either explicitly via `RustBuffer::from_vec` or indirectly by calling
///     one of the `RustBuffer::new*` constructors.
///
///   * `RustBuffer` structs do not implement `Drop`, since they are intended
///     to be passed to foreign-language code outside of the control of Rust's
///     ownership system. To avoid memory leaks they *must* passed back into
///     Rust and either explicitly destroyed using `RustBuffer::destroy`, or
///     converted back to a `Vec<u8>` using `RustBuffer::destroy_into_vec`
///     (which will then be dropped via Rust's usual ownership-tracking system).
///
/// Foreign-language code should not construct `RustBuffer` structs other than
/// by receiving them from a call into the Rust code, and should not modify them
/// apart from the following safe operations:
///
///   * Writing bytes into the buffer pointed to by `data`, without writing
///     beyond the indicated `capacity`.
///
///   * Adjusting the `len` property to indicate the amount of data written,
///     while ensuring that 0 <= `len` <= `capacity`.
///
///   * As a special case, constructing a `RustBuffer` with zero capacity, zero
///     length, and a null `data` pointer to indicate an empty buffer.
///
/// In particular, it is not safe for foreign-language code to construct a `RustBuffer`
/// that points to its own allocated memory; use the `ForeignBytes` struct to
/// pass a view of foreign-owned memory in to Rust code.
///
/// Implementation note: all the fields of this struct are private, so you can't
/// manually construct instances that don't come from a `Vec<u8>`. If you've got
/// a `RustBuffer` then it either came from a public constructor (all of which
/// are safe) or it came from foreign-language code (which should have in turn
/// received it by calling some Rust function, and should be respecting the
/// invariants listed above).
///
/// This struct is based on `ByteBuffer` from the `ffi-support` crate, but modified
/// to retain unallocated capacity rather than truncating to the occupied length.
#[repr(C)]
pub struct RustBuffer {
    /// The allocated capacity of the underlying `Vec<u8>`.
    /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
    capacity: i32,
    /// The occupied length of the underlying `Vec<u8>`.
    /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
    len: i32,
    /// The pointer to the allocated buffer of the `Vec<u8>`.
    data: *mut u8,
}

impl RustBuffer {
    /// Creates an empty `RustBuffer`.
    ///
    /// The buffer will not allocate.
    /// The resulting vector will not be automatically dropped; you must
    /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
    pub fn new() -> Self {
        Self::from_vec(Vec::new())
    }

    /// Creates a `RustBuffer` from its constituent fields.
    ///
    /// This is intended mainly as an internal convenience function and should not
    /// be used outside of this module.
    ///
    /// # Safety
    ///
    /// You must ensure that the raw parts uphold the documented invariants of this class.
    pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
        Self {
            capacity,
            len,
            data,
        }
    }

    /// Get the current length of the buffer, as a `usize`.
    ///
    /// This is mostly a helper function to convert the `i32` length field
    /// into a `usize`, which is what Rust code usually expects.
    ///
    /// # Panics
    ///
    /// Panics if called on an invalid struct obtained from foreign-language code,
    /// in which the `len` field is negative.
    pub fn len(&self) -> usize {
        self.len
            .try_into()
            .expect("buffer length negative or overflowed")
    }

    /// Returns true if the length of the buffer is 0.
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Creates a `RustBuffer` zero-filed to the requested size.
    ///
    /// The resulting vector will not be automatically dropped; you must
    /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
    ///
    /// # Panics
    ///
    /// Panics if the requested size is too large to fit in an `i32`, and
    /// hence would risk incompatibility with some foreign-language code.
    pub fn new_with_size(size: usize) -> Self {
        assert!(
            size < i32::MAX as usize,
            "RustBuffer requested size too large"
        );
        Self::from_vec(vec![0u8; size])
    }

    /// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`.
    ///
    /// The resulting vector will not be automatically dropped; you must
    /// arrange to call `destroy` or `destroy_into_vec` when finished with it.
    ///
    /// # Panics
    ///
    /// Panics if the vector's length or capacity are too large to fit in an `i32`,
    /// and hence would risk incompatibility with some foreign-language code.
    pub fn from_vec(v: Vec<u8>) -> Self {
        let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
        let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
        let mut v = std::mem::ManuallyDrop::new(v);
        unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
    }

    /// Converts this `RustBuffer` back into an owned `Vec<u8>`.
    ///
    /// This restores ownership of the underlying buffer to Rust, meaning it will
    /// be dropped when the `Vec<u8>` is dropped. The `RustBuffer` *must* have been
    /// previously obtained from a valid `Vec<u8>` owned by this Rust code.
    ///
    /// # Panics
    ///
    /// Panics if called on an invalid struct obtained from foreign-language code,
    /// which does not respect the invairiants on `len` and `capacity`.
    pub fn destroy_into_vec(self) -> Vec<u8> {
        // Rust will never give us a null `data` pointer for a `Vec`, but
        // foreign-language code can use it to cheaply pass an empty buffer.
        if self.data.is_null() {
            assert!(self.capacity == 0, "null RustBuffer had non-zero capacity");
            assert!(self.len == 0, "null RustBuffer had non-zero length");
            vec![]
        } else {
            let capacity: usize = self
                .capacity
                .try_into()
                .expect("buffer capacity negative or overflowed");
            let len: usize = self
                .len
                .try_into()
                .expect("buffer length negative or overflowed");
            assert!(len <= capacity, "RustBuffer length exceeds capacity");
            unsafe { Vec::from_raw_parts(self.data, len, capacity) }
        }
    }

    /// Reclaim memory stored in this `RustBuffer`.
    ///
    /// # Panics
    ///
    /// Panics if called on an invalid struct obtained from foreign-language code,
    /// which does not respect the invairiants on `len` and `capacity`.
    pub fn destroy(self) {
        drop(self.destroy_into_vec());
    }
}

impl Default for RustBuffer {
    fn default() -> Self {
        Self::new()
    }
}

// extern "C" functions for the RustBuffer functionality.
//
// These are used in two ways:
//   1. Code that statically links to UniFFI can use these directly to handle RustBuffer
//      allocation/destruction. The plan is to use this for the Firefox desktop JS bindings.
//
//   2. The scaffolding code re-exports these functions, prefixed with the component name and UDL
//      hash  This creates a separate set of functions for each UniFFIed component, which is needed
//      in the case where we create multiple dylib artifacts since each dylib will have its own
//      allocator.

/// This helper allocates a new byte buffer owned by the Rust code, and returns it
/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually
/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined
/// below, or by passing ownership of the buffer back into Rust code.
#[no_mangle]
pub extern "C" fn uniffi_rustbuffer_alloc(
    size: i32,
    call_status: &mut RustCallStatus,
) -> RustBuffer {
    call_with_output(call_status, || {
        RustBuffer::new_with_size(size.max(0) as usize)
    })
}

/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned
/// by the Rust code, and returns it as a `RustBuffer` struct. Callers must eventually
/// free the resulting buffer, either by explicitly calling the destructor defined below,
/// or by passing ownership of the buffer back into Rust code.
///
/// # Safety
/// This function will dereference a provided pointer in order to copy bytes from it, so
/// make sure the `ForeignBytes` struct contains a valid pointer and length.
#[no_mangle]
pub unsafe extern "C" fn uniffi_rustbuffer_from_bytes(
    bytes: ForeignBytes,
    call_status: &mut RustCallStatus,
) -> RustBuffer {
    call_with_output(call_status, || {
        let bytes = bytes.as_slice();
        RustBuffer::from_vec(bytes.to_vec())
    })
}

/// Free a byte buffer that had previously been passed to the foreign language code.
///
/// # Safety
/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
#[no_mangle]
pub unsafe extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
    call_with_output(call_status, || RustBuffer::destroy(buf))
}

/// Reserve additional capacity in a byte buffer that had previously been passed to the
/// foreign language code.
///
/// The first argument *must* be a uniquely-owned `RustBuffer` previously
/// obtained from a call into the Rust code that returned a buffer. Its underlying data pointer
/// will be reallocated if necessary and returned in a new `RustBuffer` struct.
///
/// The second argument must be the minimum number of *additional* bytes to reserve
/// capacity for in the buffer; it is likely to reserve additional capacity in practice
/// due to amortized growth strategy of Rust vectors.
///
/// # Safety
/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
#[no_mangle]
pub unsafe extern "C" fn uniffi_rustbuffer_reserve(
    buf: RustBuffer,
    additional: i32,
    call_status: &mut RustCallStatus,
) -> RustBuffer {
    call_with_output(call_status, || {
        let additional: usize = additional
            .try_into()
            .expect("additional buffer length negative or overflowed");
        let mut v = buf.destroy_into_vec();
        v.reserve(additional);
        RustBuffer::from_vec(v)
    })
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_rustbuffer_from_vec() {
        let rbuf = RustBuffer::from_vec(vec![1u8, 2, 3]);
        assert_eq!(rbuf.len(), 3);
        assert_eq!(rbuf.destroy_into_vec(), vec![1u8, 2, 3]);
    }

    #[test]
    fn test_rustbuffer_empty() {
        let rbuf = RustBuffer::new();
        assert_eq!(rbuf.len(), 0);
        // Rust will never give us a null pointer, even for an empty buffer.
        assert!(!rbuf.data.is_null());
        assert_eq!(rbuf.destroy_into_vec(), Vec::<u8>::new());
    }

    #[test]
    fn test_rustbuffer_new_with_size() {
        let rbuf = RustBuffer::new_with_size(5);
        assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8, 0, 0, 0, 0]);

        let rbuf = RustBuffer::new_with_size(0);
        assert!(!rbuf.data.is_null());
        assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
    }

    #[test]
    fn test_rustbuffer_null_means_empty() {
        // This is how foreign-language code might cheaply indicate an empty buffer.
        let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) };
        assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
    }

    #[test]
    #[should_panic]
    fn test_rustbuffer_null_must_have_no_capacity() {
        // We guard against foreign-language code providing this kind of invalid struct.
        let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) };
        rbuf.destroy_into_vec();
    }
    #[test]
    #[should_panic]
    fn test_rustbuffer_null_must_have_zero_length() {
        // We guard against foreign-language code providing this kind of invalid struct.
        let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) };
        rbuf.destroy_into_vec();
    }

    #[test]
    #[should_panic]
    fn test_rustbuffer_provided_capacity_must_be_non_negative() {
        // We guard against foreign-language code providing this kind of invalid struct.
        let mut v = vec![0u8, 1, 2];
        let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
        rbuf.destroy_into_vec();
    }

    #[test]
    #[should_panic]
    fn test_rustbuffer_provided_len_must_be_non_negative() {
        // We guard against foreign-language code providing this kind of invalid struct.
        let mut v = vec![0u8, 1, 2];
        let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
        rbuf.destroy_into_vec();
    }

    #[test]
    #[should_panic]
    fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
        // We guard against foreign-language code providing this kind of invalid struct.
        let mut v = vec![0u8, 1, 2];
        let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) };
        rbuf.destroy_into_vec();
    }
}
