use chemfiles_sys as ffi;
use crate::errors::{check, check_not_null, check_success, Error};
use crate::strings;
#[derive(Debug)]
pub(crate) struct RawProperty {
    handle: *mut ffi::CHFL_PROPERTY,
}
impl RawProperty {
    pub unsafe fn from_ptr(ptr: *mut ffi::CHFL_PROPERTY) -> RawProperty {
        check_not_null(ptr);
        RawProperty { handle: ptr }
    }
    pub fn as_ptr(&self) -> *const ffi::CHFL_PROPERTY {
        self.handle
    }
    fn double(value: f64) -> RawProperty {
        unsafe {
            let handle = ffi::chfl_property_double(value);
            RawProperty::from_ptr(handle)
        }
    }
    fn bool(value: bool) -> RawProperty {
        unsafe {
            let handle = ffi::chfl_property_bool(u8::from(value));
            RawProperty::from_ptr(handle)
        }
    }
    fn vector3d(value: [f64; 3]) -> RawProperty {
        unsafe {
            let handle = ffi::chfl_property_vector3d(value.as_ptr());
            RawProperty::from_ptr(handle)
        }
    }
    fn string(value: &str) -> RawProperty {
        let buffer = strings::to_c(value);
        unsafe {
            let handle = ffi::chfl_property_string(buffer.as_ptr());
            RawProperty::from_ptr(handle)
        }
    }
    fn get_kind(&self) -> ffi::chfl_property_kind {
        let mut kind = ffi::chfl_property_kind::CHFL_PROPERTY_BOOL;
        unsafe {
            check_success(ffi::chfl_property_get_kind(self.as_ptr(), &mut kind));
        }
        return kind;
    }
    fn get_bool(&self) -> Result<bool, Error> {
        let mut value = 0;
        unsafe {
            check(ffi::chfl_property_get_bool(self.as_ptr(), &mut value))?;
        }
        return Ok(value != 0);
    }
    fn get_double(&self) -> Result<f64, Error> {
        let mut value = 0.0;
        unsafe {
            check(ffi::chfl_property_get_double(self.as_ptr(), &mut value))?;
        }
        return Ok(value);
    }
    fn get_string(&self) -> Result<String, Error> {
        let get_string = |ptr, len| unsafe { ffi::chfl_property_get_string(self.as_ptr(), ptr, len) };
        let value = strings::call_autogrow_buffer(64, get_string)?;
        return Ok(strings::from_c(value.as_ptr()));
    }
    fn get_vector3d(&self) -> Result<[f64; 3], Error> {
        let mut value = [0.0; 3];
        unsafe {
            check(ffi::chfl_property_get_vector3d(self.as_ptr(), value.as_mut_ptr()))?;
        }
        return Ok(value);
    }
}
impl Drop for RawProperty {
    fn drop(&mut self) {
        unsafe {
            let _ = ffi::chfl_free(self.as_ptr().cast());
        }
    }
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Property {
    Bool(bool),
    Double(f64),
    String(String),
    Vector3D([f64; 3]),
}
impl From<bool> for Property {
    fn from(value: bool) -> Self {
        Property::Bool(value)
    }
}
impl From<f64> for Property {
    fn from(value: f64) -> Self {
        Property::Double(value)
    }
}
impl From<String> for Property {
    fn from(value: String) -> Self {
        Property::String(value)
    }
}
impl<'a> From<&'a str> for Property {
    fn from(value: &'a str) -> Self {
        Property::String(value.into())
    }
}
impl From<[f64; 3]> for Property {
    fn from(value: [f64; 3]) -> Self {
        Property::Vector3D(value)
    }
}
impl Property {
    pub(crate) fn as_raw(&self) -> RawProperty {
        match *self {
            Property::Bool(value) => RawProperty::bool(value),
            Property::Double(value) => RawProperty::double(value),
            Property::String(ref value) => RawProperty::string(value),
            Property::Vector3D(value) => RawProperty::vector3d(value),
        }
    }
    #[allow(clippy::needless_pass_by_value)] pub(crate) fn from_raw(raw: RawProperty) -> Property {
        match raw.get_kind() {
            ffi::chfl_property_kind::CHFL_PROPERTY_BOOL => Self::Bool(raw.get_bool().expect("should be a bool")),
            ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE => {
                Self::Double(raw.get_double().expect("should be a double"))
            }
            ffi::chfl_property_kind::CHFL_PROPERTY_STRING => {
                Self::String(raw.get_string().expect("should be a string"))
            }
            ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D => {
                Property::Vector3D(raw.get_vector3d().expect("should be a vector3d"))
            }
        }
    }
}
pub struct PropertiesIter<'a> {
    pub(crate) names: std::vec::IntoIter<String>,
    pub(crate) getter: Box<dyn Fn(&str) -> Property + 'a>,
}
impl<'a> Iterator for PropertiesIter<'a> {
    type Item = (String, Property);
    fn next(&mut self) -> Option<Self::Item> {
        self.names.next().map(|name| {
            let property = (self.getter)(&name);
            (name, property)
        })
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        self.names.size_hint()
    }
    fn count(self) -> usize {
        self.names.count()
    }
}
#[cfg(test)]
mod tests {
    mod raw {
        use super::super::*;
        #[test]
        fn bool() {
            let property = RawProperty::bool(false);
            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_BOOL);
            assert_eq!(property.get_bool(), Ok(false));
        }
        #[test]
        fn double() {
            let property = RawProperty::double(45.0);
            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE);
            assert_eq!(property.get_double(), Ok(45.0));
        }
        #[test]
        fn string() {
            let property = RawProperty::string("test");
            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_STRING);
            assert_eq!(property.get_string(), Ok("test".into()));
        }
        #[test]
        fn vector3d() {
            let property = RawProperty::vector3d([1.2, 3.4, 5.6]);
            assert_eq!(property.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D);
            assert_eq!(property.get_vector3d(), Ok([1.2, 3.4, 5.6]));
        }
    }
    mod rust {
        use super::super::*;
        #[test]
        fn bool() {
            let property = Property::Bool(false);
            let raw = property.as_raw();
            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_BOOL);
            assert_eq!(raw.get_bool(), Ok(false));
            assert_eq!(Property::from_raw(raw), property);
        }
        #[test]
        fn double() {
            let property = Property::Double(45.0);
            let raw = property.as_raw();
            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_DOUBLE);
            assert_eq!(raw.get_double(), Ok(45.0));
            assert_eq!(Property::from_raw(raw), property);
        }
        #[test]
        fn string() {
            let property = Property::String("test".into());
            let raw = property.as_raw();
            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_STRING);
            assert_eq!(raw.get_string(), Ok("test".into()));
            assert_eq!(Property::from_raw(raw), property);
            let property = Property::String("long string ".repeat(128));
            let raw = property.as_raw();
            assert_eq!(raw.get_string(), Ok("long string ".repeat(128)));
        }
        #[test]
        fn vector3d() {
            let property = Property::Vector3D([1.2, 3.4, 5.6]);
            let raw = property.as_raw();
            assert_eq!(raw.get_kind(), ffi::chfl_property_kind::CHFL_PROPERTY_VECTOR3D);
            assert_eq!(raw.get_vector3d(), Ok([1.2, 3.4, 5.6]));
            assert_eq!(Property::from_raw(raw), property);
        }
    }
}