mod array;
mod async_;
mod bindings;
use crate::errors::Result;
pub use crate::ffi::enums::StorageType;
pub use crate::ffi::AttributeInfo;
use crate::node::HoudiniNode;
use crate::stringhandle::{StringArray, StringHandle};
pub use array::*;
use async_::AsyncAttribResult;
use std::any::Any;
use std::borrow::Cow;
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
pub type JobId = i32;
mod private {
    pub trait Sealed {}
}
pub trait AttribAccess: private::Sealed + Clone + Default + Send + Sized + 'static {
    fn storage() -> StorageType;
    fn storage_array() -> StorageType;
    fn get(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part_id: i32,
        buffer: &mut Vec<Self>,
    ) -> Result<()>;
    fn get_async(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part_id: i32,
        buffer: &mut Vec<Self>,
    ) -> Result<JobId>;
    fn set(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part_id: i32,
        data: &[Self],
        start: i32,
        len: i32,
    ) -> Result<()>;
    fn set_async(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part: i32,
        data: &[Self],
        start: i32,
        len: i32,
    ) -> Result<JobId>;
    fn set_unique(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part_id: i32,
        data: &[Self],
        start: i32,
    ) -> Result<()>;
    fn set_unique_async(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part_id: i32,
        data: &[Self],
        start: i32,
    ) -> Result<JobId>;
    fn get_array(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part: i32,
    ) -> Result<DataArray<'static, Self>>
    where
        [Self]: ToOwned<Owned = Vec<Self>>;
    fn get_array_async(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        data: &mut [Self],
        sizes: &mut [i32],
        part: i32,
    ) -> Result<JobId>;
    fn set_array(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part: i32,
        data: &[Self],
        sizes: &[i32],
    ) -> Result<()>
    where
        [Self]: ToOwned<Owned = Vec<Self>>;
    fn set_array_async(
        name: &CStr,
        node: &HoudiniNode,
        info: &AttributeInfo,
        part: i32,
        data: &[Self],
        sizes: &[i32],
    ) -> Result<JobId>;
}
macro_rules! impl_sealed {
    ($($x:ident),+ $(,)?) => {
        $(impl private::Sealed for $x {})+
    }
}
impl_sealed!(u8, i8, i16, i32, i64, f32, f64);
impl StorageType {
    pub(crate) fn type_matches(&self, other: StorageType) -> bool {
        use StorageType::*;
        match other {
            IntArray | Uint8Array | Int8Array | Int16Array | Int64Array => {
                matches!(*self, Int | Uint8 | Int16 | Int64)
            }
            FloatArray | Float64Array => matches!(*self, Float | Float64),
            StringArray => matches!(*self, StringArray),
            _st => matches!(*self, _st),
        }
    }
}
pub(crate) struct AttributeBundle {
    pub(crate) info: AttributeInfo,
    pub(crate) name: CString,
    pub(crate) node: HoudiniNode,
}
pub struct NumericAttr<T: AttribAccess>(pub(crate) AttributeBundle, PhantomData<T>);
pub struct NumericArrayAttr<T: AttribAccess>(pub(crate) AttributeBundle, PhantomData<T>);
pub struct StringAttr(pub(crate) AttributeBundle);
pub struct StringArrayAttr(pub(crate) AttributeBundle);
pub struct DictionaryAttr(pub(crate) AttributeBundle);
pub struct DictionaryArrayAttr(pub(crate) AttributeBundle);
impl<T: AttribAccess> NumericArrayAttr<T>
where
    [T]: ToOwned<Owned = Vec<T>>,
{
    pub(crate) fn new(
        name: CString,
        info: AttributeInfo,
        node: HoudiniNode,
    ) -> NumericArrayAttr<T> {
        NumericArrayAttr(AttributeBundle { info, name, node }, PhantomData)
    }
    pub fn get(&self, part_id: i32) -> Result<DataArray<T>> {
        debug_assert!(self.0.info.storage().type_matches(T::storage()));
        T::get_array(&self.0.name, &self.0.node, &self.0.info, part_id)
    }
    pub fn get_async(&self, part_id: i32) -> Result<(JobId, DataArray<T>)> {
        let info = &self.0.info;
        debug_assert!(info.storage().type_matches(T::storage()));
        let mut data = vec![T::default(); info.total_array_elements() as usize];
        let mut sizes = vec![0i32; info.count() as usize];
        let job_id = T::get_array_async(
            &self.0.name,
            &self.0.node,
            &info,
            &mut data,
            &mut sizes,
            part_id,
        )?;
        Ok((job_id, DataArray::new_owned(data, sizes)))
    }
    pub fn set(&self, part_id: i32, values: &DataArray<T>) -> Result<()> {
        debug_assert!(self.0.info.storage().type_matches(T::storage()));
        debug_assert_eq!(
            self.0.info.count(),
            values.sizes().len() as i32,
            "sizes array must be the same as AttributeInfo::count"
        );
        debug_assert_eq!(
            self.0.info.total_array_elements(),
            values.data().len() as i64,
            "data array must be the same as AttributeInfo::total_array_elements"
        );
        T::set_array(
            &self.0.name,
            &self.0.node,
            &self.0.info,
            part_id,
            values.data(),
            values.sizes(),
        )
    }
}
impl<T: AttribAccess> NumericAttr<T> {
    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> NumericAttr<T> {
        NumericAttr(AttributeBundle { info, name, node }, PhantomData)
    }
    pub fn get(&self, part_id: i32) -> Result<Vec<T>> {
        debug_assert_eq!(self.0.info.storage(), T::storage());
        let mut buffer = vec![];
        T::get(
            &self.0.name,
            &self.0.node,
            &self.0.info,
            part_id,
            &mut buffer,
        )?;
        Ok(buffer)
    }
    pub fn read_async_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<i32> {
        let info = &self.0.info;
        buffer.resize((info.count() * info.tuple_size()) as usize, T::default());
        T::get_async(&self.0.name, &self.0.node, &self.0.info, part_id, buffer)
    }
    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<T>> {
        let info = &self.0.info;
        let size = (info.count() * info.tuple_size()) as usize;
        let mut data = Vec::<T>::with_capacity(size);
        let job_id = T::get_async(&self.0.name, &self.0.node, &info, part_id, &mut data)?;
        Ok(AsyncAttribResult {
            job_id,
            data,
            size,
            session: self.0.node.session.clone(),
        })
    }
    pub fn read_into(&self, part_id: i32, buffer: &mut Vec<T>) -> Result<()> {
        debug_assert_eq!(self.0.info.storage(), T::storage());
        let info = AttributeInfo::new(&self.0.node, part_id, self.0.info.owner(), &self.0.name)?;
        T::get(&self.0.name, &self.0.node, &info, part_id, buffer)
    }
    pub fn set(&self, part_id: i32, values: &[T]) -> Result<()> {
        debug_assert_eq!(self.0.info.storage(), T::storage());
        T::set(
            &self.0.name,
            &self.0.node,
            &self.0.info,
            part_id,
            values,
            0,
            self.0.info.count().min(values.len() as i32),
        )
    }
    pub fn set_unique(&self, part_id: i32, value: &[T]) -> Result<()> {
        debug_assert_eq!(self.0.info.storage(), T::storage());
        debug_assert!(value.len() <= self.0.info.tuple_size() as usize);
        T::set_unique(&self.0.name, &self.0.node, &self.0.info, part_id, value, 0)
    }
}
impl StringAttr {
    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringAttr {
        StringAttr(AttributeBundle { info, name, node })
    }
    pub fn get(&self, part_id: i32) -> Result<StringArray> {
        debug_assert!(self.0.node.is_valid()?);
        bindings::get_attribute_string_data(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
        )
    }
    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
        bindings::get_attribute_string_data_async(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
        )
    }
    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_string_data(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
            ptrs.as_mut(),
        )
    }
    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_string_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            ptrs.as_mut(),
        )
    }
    pub fn set_unique(&self, part: i32, value: &CStr) -> Result<()> {
        bindings::set_attribute_string_unique_data(
            &self.0.node,
            self.0.name.as_c_str(),
            &self.0.info.0,
            part,
            value.as_ptr(),
        )
    }
    pub fn set_unique_async(&self, part: i32, value: &CStr) -> Result<JobId> {
        bindings::set_attribute_string_unique_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            &self.0.info.0,
            part,
            value.as_ptr(),
        )
    }
    pub fn set_indexed(
        &self,
        part_id: i32,
        values: &[impl AsRef<CStr>],
        indices: &[i32],
    ) -> Result<()> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_indexed_string_data(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
            ptrs.as_mut(),
            indices,
        )
    }
    pub fn set_indexed_async(
        &self,
        part_id: i32,
        values: &[impl AsRef<CStr>],
        indices: &[i32],
    ) -> Result<JobId> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_indexed_string_data_async(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
            ptrs.as_mut(),
            indices,
        )
    }
}
impl StringArrayAttr {
    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> StringArrayAttr {
        StringArrayAttr(AttributeBundle { info, name, node })
    }
    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
        debug_assert!(self.0.node.is_valid()?);
        bindings::get_attribute_string_array_data(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info,
        )
    }
    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
        let mut handles = vec![StringHandle(-1); self.0.info.total_array_elements() as usize];
        let mut sizes = vec![0; self.0.info.count() as usize];
        let job_id = bindings::get_attribute_string_array_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            &mut handles,
            &mut sizes,
        )?;
        Ok((
            job_id,
            StringMultiArray {
                handles,
                sizes,
                session: self.0.node.session.clone(),
            },
        ))
    }
    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
        debug_assert!(self.0.node.is_valid()?);
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_string_array_data(
            &self.0.node,
            self.0.name.as_c_str(),
            &self.0.info.0,
            part_id,
            ptrs.as_mut(),
            &sizes,
        )
    }
    pub fn set_async(
        &self,
        part_id: i32,
        values: &[impl AsRef<CStr>],
        sizes: &[i32],
    ) -> Result<JobId> {
        debug_assert!(self.0.node.is_valid()?);
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_string_array_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            ptrs.as_mut(),
            &sizes,
        )
    }
}
impl DictionaryAttr {
    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
        DictionaryAttr(AttributeBundle { info, name, node })
    }
    pub fn get(&self, part_id: i32) -> Result<StringArray> {
        debug_assert!(self.0.node.is_valid()?);
        bindings::get_attribute_dictionary_data(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
        )
    }
    pub fn set_async(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<JobId> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_dictionary_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            ptrs.as_mut(),
        )
    }
    pub fn get_async(&self, part_id: i32) -> Result<AsyncAttribResult<StringHandle>> {
        bindings::get_attribute_dictionary_data_async(
            &self.0.node,
            part_id,
            self.0.name.as_c_str(),
            &self.0.info.0,
        )
    }
    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>]) -> Result<()> {
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_dictionary_data(
            &self.0.node,
            part_id,
            &self.0.name.as_c_str(),
            &self.0.info.0,
            ptrs.as_mut(),
        )
    }
}
impl DictionaryArrayAttr {
    pub(crate) fn new(name: CString, info: AttributeInfo, node: HoudiniNode) -> Self {
        DictionaryArrayAttr(AttributeBundle { info, name, node })
    }
    pub fn get(&self, part_id: i32) -> Result<StringMultiArray> {
        debug_assert!(self.0.node.is_valid()?);
        bindings::get_attribute_dictionary_array_data(
            &self.0.node,
            &self.0.name,
            part_id,
            &self.0.info,
        )
    }
    pub fn get_async(&self, part_id: i32) -> Result<(JobId, StringMultiArray)> {
        let mut handles = vec![StringHandle(-1); self.0.info.total_array_elements() as usize];
        let mut sizes = vec![0; self.0.info.count() as usize];
        let job_id = bindings::get_attribute_dictionary_array_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            &mut handles,
            &mut sizes,
        )?;
        Ok((
            job_id,
            StringMultiArray {
                handles,
                sizes,
                session: self.0.node.session.clone(),
            },
        ))
    }
    pub fn set(&self, part_id: i32, values: &[impl AsRef<CStr>], sizes: &[i32]) -> Result<()> {
        debug_assert!(self.0.node.is_valid()?);
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_dictionary_array_data(
            &self.0.node,
            self.0.name.as_c_str(),
            &self.0.info.0,
            part_id,
            ptrs.as_mut(),
            &sizes,
        )
    }
    pub fn set_async(
        &self,
        part_id: i32,
        values: &[impl AsRef<CStr>],
        sizes: &[i32],
    ) -> Result<JobId> {
        debug_assert!(self.0.node.is_valid()?);
        let mut ptrs: Vec<*const i8> = values.iter().map(|cs| cs.as_ref().as_ptr()).collect();
        bindings::set_attribute_dictionary_array_data_async(
            &self.0.node,
            self.0.name.as_c_str(),
            part_id,
            &self.0.info.0,
            ptrs.as_mut(),
            &sizes,
        )
    }
}
#[doc(hidden)]
pub trait AsAttribute: Send {
    fn info(&self) -> &AttributeInfo;
    fn storage(&self) -> StorageType;
    fn boxed(self) -> Box<dyn AnyAttribWrapper>
    where
        Self: Sized + 'static,
    {
        Box::new(self)
    }
    fn name(&self) -> &CStr;
    fn node(&self) -> &HoudiniNode;
}
impl<T: AttribAccess> AsAttribute for NumericAttr<T> {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        T::storage()
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
impl<T: AttribAccess> AsAttribute for NumericArrayAttr<T> {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        T::storage()
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
impl AsAttribute for StringAttr {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        StorageType::String
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
impl AsAttribute for StringArrayAttr {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        StorageType::StringArray
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
impl AsAttribute for DictionaryAttr {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        StorageType::Dictionary
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
impl AsAttribute for DictionaryArrayAttr {
    fn info(&self) -> &AttributeInfo {
        &self.0.info
    }
    fn storage(&self) -> StorageType {
        StorageType::DictionaryArray
    }
    fn name(&self) -> &CStr {
        &self.0.name
    }
    fn node(&self) -> &HoudiniNode {
        &self.0.node
    }
}
#[doc(hidden)]
pub trait AnyAttribWrapper: Any + AsAttribute {
    fn as_any(&self) -> &dyn Any;
}
impl<T: AsAttribute + 'static> AnyAttribWrapper for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}
pub struct Attribute(Box<dyn AnyAttribWrapper>);
impl Attribute {
    pub(crate) fn new(attr_obj: Box<dyn AnyAttribWrapper>) -> Self {
        Attribute(attr_obj)
    }
    pub fn downcast<T: AnyAttribWrapper>(&self) -> Option<&T> {
        self.0.as_any().downcast_ref::<T>()
    }
    pub fn name(&self) -> Cow<str> {
        self.0.name().to_string_lossy()
    }
    pub fn storage(&self) -> StorageType {
        self.0.storage()
    }
    pub fn info(&self) -> &AttributeInfo {
        self.0.info()
    }
    pub fn delete(self, part_id: i32) -> Result<()> {
        crate::ffi::delete_attribute(self.0.node(), part_id, self.0.name(), &self.0.info().0)
    }
}