//! A FFI Compatible Array for Dart

use core::slice;
use std::mem::ManuallyDrop;

use ffi::{DartCObject, DartCObjectType, DartCObjectValue, DartNativeArray};

use crate::{ffi, IntoDart};

/// A wrapper around a list of `DartCObject` that will be dropped after been
/// sent to dart vm.
#[derive(Debug, Clone)]
pub struct DartArray {
    inner: Box<[*mut DartCObject]>,
}

impl<I, T: IntoDart> From<I> for DartArray
where
    I: Iterator<Item = T>,
{
    fn from(iter: I) -> Self {
        let vec: Vec<_> = iter
            // convert them to dart objects
            .map(IntoDart::into_dart)
            // box them to be transferred to dart
            .map(Box::new)
            // as pointers
            .map(Box::into_raw)
            // then collect everything
            .collect();
        let inner = vec.into_boxed_slice();
        Self { inner }
    }
}

impl IntoDart for DartArray {
    fn into_dart(self) -> ffi::DartCObject {
        let mut s = ManuallyDrop::new(self);
        // we drop vec when DartCObject get dropped
        let (data, len) = (s.inner.as_mut_ptr(), s.inner.len());

        let array = DartNativeArray {
            length: len as isize,
            values: data,
        };
        DartCObject {
            ty: DartCObjectType::DartArray,
            value: DartCObjectValue { as_array: array },
        }
    }
}

impl From<DartNativeArray> for DartArray {
    fn from(arr: DartNativeArray) -> Self {
        let inner = unsafe {
            let slice =
                slice::from_raw_parts_mut(arr.values, arr.length as usize);
            Box::from_raw(slice)
        };
        Self { inner }
    }
}

impl Drop for DartArray {
    fn drop(&mut self) {
        for v in self.inner.iter() {
            unsafe {
                let _ = Box::from_raw(*v);
            }
        }
    }
}
