//! Self-owning shared data segment.

use std::{cell::RefCell, rc::Rc};

#[derive(Clone, Copy, Debug)]
pub enum Format {
    Z = 0,
    E = 8,
    S = 16,
    T = 32,
}

/// Reference-counted, self-owned, dynamically sized chunk of bytes.
///
/// In the original WINGs, this type either owned or borrowed a data buffer and
/// had some associated metadata. In Rust, this is little more than a thin
/// wrapper around an `Rc<RefCell<Vec<u8>>>`. It is mostly used by proplists,
/// and it should be done away with once its dependents have been ported to
/// Rust.
pub struct Data(Rc<RefCell<Inner>>);

impl Data {
    /// Runs `f` on a borrow of `self`'s data, returning whatever `f` does.
    ///
    /// This is provided because the internal structure of `Data` does not make
    /// it easy to borrow its contents directly. As we migrate away from the
    /// original C interfaces to WINGs data structures, we may be able to
    /// provide a direct borrow, which would make this method obsolete. (That
    /// would be a good thing.)
    pub fn with_bytes<R>(&self, f: impl FnOnce(&[u8]) -> R) -> R {
        f(&self.0.borrow().bytes)
    }
}

struct Inner {
    bytes: Vec<u8>,
    format: Format,
}

pub mod ffi {
    use super::{Data, Format, Inner};

    use std::{
        cell::RefCell,
        ffi::{c_int, c_uint, c_void},
        ptr,
        rc::Rc,
    };

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMCreateDataWithCapacity(capacity: c_uint) -> *mut Data {
        Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner {
            bytes: Vec::with_capacity(capacity as usize),
            format: Format::Z,
        })))))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMCreateDataWithLength(length: c_uint) -> *mut Data {
        Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner {
            bytes: vec![0; length as usize],
            format: Format::Z,
        })))))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMCreateDataWithBytes(
        bytes: *const c_void,
        length: c_uint,
    ) -> *mut Data {
        let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::<u8>(), length as usize) };
        let bytes = Vec::from(bytes);
        Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner {
            bytes,
            format: Format::Z,
        })))))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMCreateDataWithData(data: *mut Data) -> *mut Data {
        if data.is_null() {
            return ptr::null_mut();
        }
        let data = unsafe { &*data };
        Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner {
            bytes: data.0.borrow().bytes.clone(),
            format: data.0.borrow().format,
        })))))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMRetainData(data: *mut Data) -> *mut Data {
        if data.is_null() {
            return ptr::null_mut();
        }
        let data = unsafe { &*data };
        Box::leak(Box::new(Data(data.0.clone())))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMReleaseData(data: *mut Data) {
        if data.is_null() {
            return;
        }
        let _ = unsafe { ptr::read(data) };
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMDataBytes(data: *mut Data) -> *const c_void {
        if data.is_null() {
            return ptr::null();
        }

        unsafe { (*data).0.borrow().bytes.as_ptr().cast::<c_void>() }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMIsDataEqualToData(a: *mut Data, b: *mut Data) -> c_int {
        if a.is_null() || b.is_null() {
            return 0;
        }
        if ptr::eq(a, b) {
            return 1;
        }
        let a = unsafe { &*a };
        let b = unsafe { &*b };
        (a.0.borrow().bytes == b.0.borrow().bytes) as c_int
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMGetDataLength(data: *mut Data) -> c_uint {
        if data.is_null() {
            return 0;
        }
        unsafe { (*data).0.borrow().bytes.len() as c_uint }
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMAppendDataBytes(
        data: *mut Data,
        bytes: *const c_void,
        length: c_uint,
    ) {
        if data.is_null() || bytes.is_null() || length == 0 {
            return;
        }
        let data = unsafe { &mut *data };
        let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::<u8>(), length as usize) };
        data.0.borrow_mut().bytes.extend_from_slice(bytes);
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMAppendData(data: *mut Data, ext: *mut Data) {
        if data.is_null() || ext.is_null() {
            return;
        }
        let data = unsafe { &mut *data };
        let ext = unsafe { &*ext };
        data.0
            .borrow_mut()
            .bytes
            .extend_from_slice(&ext.0.borrow().bytes);
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMSetData(data: *mut Data, other: *mut Data) {
        if data.is_null() || other.is_null() {
            return;
        }
        let data = unsafe { &mut *data };
        let other = unsafe { &*other };
        data.0
            .borrow_mut()
            .bytes
            .copy_from_slice(&other.0.borrow().bytes);
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMGetDataFormat(data: *mut Data) -> c_uint {
        if data.is_null() {
            return 0;
        }
        return unsafe {
            match (*data).0.borrow().format {
                Format::Z => 0,
                Format::E => 8,
                Format::S => 16,
                Format::T => 32,
            }
        };
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMSetDataFormat(data: *mut Data, format: c_uint) {
        if data.is_null() {
            return;
        }
        let format = match format {
            0 => Format::Z,
            8 => Format::E,
            16 => Format::S,
            32 => Format::T,
            _ => return,
        };
        unsafe {
            (*data).0.borrow_mut().format = format;
        }
    }
}
