use std::collections::BTreeMap;

use crate::error::{Error, Result};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct H5Map {
    key_type: String,
    value_type: String,
    create_plist: BTreeMap<String, Vec<u8>>,
    access_plist: BTreeMap<String, Vec<u8>>,
    entries: BTreeMap<Vec<u8>, Vec<u8>>,
    open: bool,
}

impl H5Map {
    pub fn new(key_type: impl Into<String>, value_type: impl Into<String>) -> Self {
        Self {
            key_type: key_type.into(),
            value_type: value_type.into(),
            create_plist: BTreeMap::new(),
            access_plist: BTreeMap::new(),
            entries: BTreeMap::new(),
            open: true,
        }
    }

    fn ensure_open(&self) -> Result<()> {
        if self.open {
            Ok(())
        } else {
            Err(Error::InvalidFormat("map handle is closed".into()))
        }
    }
}

#[allow(non_snake_case)]
pub fn H5M_init() -> bool {
    true
}

#[allow(non_snake_case)]
pub fn H5M__init_package() -> bool {
    H5M_init()
}

#[allow(non_snake_case)]
pub fn H5M_top_term_package() {}

#[allow(non_snake_case)]
pub fn H5M_term_package() {}

#[allow(non_snake_case)]
pub fn H5M__close_cb(map: &mut H5Map) {
    map.open = false;
}

#[allow(non_snake_case)]
pub fn H5M__create_api_common(key_type: impl Into<String>, value_type: impl Into<String>) -> H5Map {
    H5Map::new(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5Mcreate_anon(key_type: impl Into<String>, value_type: impl Into<String>) -> H5Map {
    H5M__create_api_common(key_type, value_type)
}

#[allow(non_snake_case)]
pub fn H5M__open_api_common_ref(map: &H5Map) -> Result<&H5Map> {
    map.ensure_open()?;
    Ok(map)
}

#[deprecated(note = "use H5M__open_api_common_ref to borrow the map without cloning")]
#[allow(non_snake_case)]
pub fn H5M__open_api_common(map: &H5Map) -> Result<H5Map> {
    Ok(H5M__open_api_common_ref(map)?.clone())
}

#[allow(non_snake_case)]
pub fn H5Mclose(map: &mut H5Map) {
    H5M__close_cb(map);
}

#[allow(non_snake_case)]
pub fn H5Mget_key_type(map: &H5Map) -> Result<&str> {
    map.ensure_open()?;
    Ok(&map.key_type)
}

#[allow(non_snake_case)]
pub fn H5Mget_val_type(map: &H5Map) -> Result<&str> {
    map.ensure_open()?;
    Ok(&map.value_type)
}

#[allow(non_snake_case)]
pub fn H5Mget_create_plist_ref(map: &H5Map) -> Result<&BTreeMap<String, Vec<u8>>> {
    map.ensure_open()?;
    Ok(&map.create_plist)
}

#[allow(non_snake_case)]
pub fn H5Mget_access_plist_ref(map: &H5Map) -> Result<&BTreeMap<String, Vec<u8>>> {
    map.ensure_open()?;
    Ok(&map.access_plist)
}

#[deprecated(note = "use H5Mget_create_plist_ref to borrow the property list without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_create_plist(map: &H5Map) -> Result<BTreeMap<String, Vec<u8>>> {
    Ok(H5Mget_create_plist_ref(map)?.clone())
}

#[deprecated(note = "use H5Mget_access_plist_ref to borrow the property list without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_access_plist(map: &H5Map) -> Result<BTreeMap<String, Vec<u8>>> {
    Ok(H5Mget_access_plist_ref(map)?.clone())
}

#[allow(non_snake_case)]
pub fn H5Mget_count(map: &H5Map) -> Result<usize> {
    map.ensure_open()?;
    Ok(map.entries.len())
}

#[allow(non_snake_case)]
pub fn H5M__put_api_common(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    map.ensure_open()?;
    map.entries.insert(key, value);
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Mput(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    H5M__put_api_common(map, key, value)
}

#[allow(non_snake_case)]
pub fn H5Mput_async(map: &mut H5Map, key: Vec<u8>, value: Vec<u8>) -> Result<()> {
    H5Mput(map, key, value)
}

#[allow(non_snake_case)]
pub fn H5M__get_api_common_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    map.ensure_open()?;
    Ok(map.entries.get(key).map(Vec::as_slice))
}

#[allow(non_snake_case)]
pub fn H5Mget_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    H5M__get_api_common_ref(map, key)
}

#[allow(non_snake_case)]
pub fn H5Mget_async_ref<'a>(map: &'a H5Map, key: &[u8]) -> Result<Option<&'a [u8]>> {
    H5Mget_ref(map, key)
}

#[deprecated(note = "use H5M__get_api_common_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5M__get_api_common(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5M__get_api_common_ref(map, key)?.map(<[u8]>::to_vec))
}

#[deprecated(note = "use H5Mget_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5Mget_ref(map, key)?.map(<[u8]>::to_vec))
}

#[deprecated(note = "use H5Mget_async_ref to borrow the value without cloning")]
#[allow(non_snake_case)]
pub fn H5Mget_async(map: &H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    Ok(H5Mget_async_ref(map, key)?.map(<[u8]>::to_vec))
}

#[allow(non_snake_case)]
pub fn H5Mexists(map: &H5Map, key: &[u8]) -> Result<bool> {
    map.ensure_open()?;
    Ok(map.entries.contains_key(key))
}

#[allow(non_snake_case)]
pub fn H5M_iter_entries(map: &H5Map) -> Result<impl Iterator<Item = (&[u8], &[u8])> + '_> {
    map.ensure_open()?;
    Ok(map
        .entries
        .iter()
        .map(|(key, value)| (key.as_slice(), value.as_slice())))
}

#[allow(non_snake_case)]
pub fn H5M_iterate_with<F>(map: &H5Map, mut callback: F) -> Result<()>
where
    F: FnMut(&[u8], &[u8]) -> Result<()>,
{
    map.ensure_open()?;
    for (key, value) in &map.entries {
        callback(key, value)?;
    }
    Ok(())
}

#[allow(non_snake_case)]
pub fn H5Miterate_with<F>(map: &H5Map, callback: F) -> Result<()>
where
    F: FnMut(&[u8], &[u8]) -> Result<()>,
{
    H5M_iterate_with(map, callback)
}

#[allow(non_snake_case)]
pub fn H5M_iterate_into(map: &H5Map, entries: &mut Vec<(Vec<u8>, Vec<u8>)>) -> Result<()> {
    map.ensure_open()?;
    entries.reserve(map.entries.len());
    H5M_iterate_with(map, |key, value| {
        entries.push((key.to_vec(), value.to_vec()));
        Ok(())
    })
}

#[allow(non_snake_case)]
pub fn H5Miterate_into(map: &H5Map, entries: &mut Vec<(Vec<u8>, Vec<u8>)>) -> Result<()> {
    H5M_iterate_into(map, entries)
}

#[deprecated(
    note = "use H5M_iter_entries, H5M_iterate_with, or H5M_iterate_into to avoid returning an allocated Vec"
)]
#[allow(non_snake_case)]
pub fn H5M_iterate(map: &H5Map) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
    let mut entries = Vec::new();
    H5M_iterate_into(map, &mut entries)?;
    Ok(entries)
}

#[deprecated(
    note = "use H5M_iter_entries, H5Miterate_with, or H5Miterate_into to avoid returning an allocated Vec"
)]
#[allow(non_snake_case)]
pub fn H5Miterate(map: &H5Map) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
    let mut entries = Vec::new();
    H5Miterate_into(map, &mut entries)?;
    Ok(entries)
}

#[deprecated(
    note = "use H5M_iter_entries, H5Miterate_with, or H5Miterate_into to avoid returning an allocated Vec"
)]
#[allow(non_snake_case)]
pub fn H5Miterate_by_name(map: &H5Map) -> Result<Vec<(Vec<u8>, Vec<u8>)>> {
    let mut entries = Vec::new();
    H5Miterate_into(map, &mut entries)?;
    Ok(entries)
}

#[allow(non_snake_case)]
pub fn H5Mdelete(map: &mut H5Map, key: &[u8]) -> Result<Option<Vec<u8>>> {
    map.ensure_open()?;
    Ok(map.entries.remove(key))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn map_api_put_get_iterate_delete() {
        let mut map = H5Mcreate_anon("u8", "bytes");
        H5Mput(&mut map, b"k".to_vec(), b"v".to_vec()).unwrap();
        assert_eq!(H5Mget_count(&map).unwrap(), 1);
        assert!(H5Mexists(&map, b"k").unwrap());
        assert!(std::ptr::eq(H5M__open_api_common_ref(&map).unwrap(), &map));
        assert_eq!(H5Mget_ref(&map, b"k").unwrap(), Some(b"v".as_slice()));
        let entries: Vec<_> = H5M_iter_entries(&map).unwrap().collect();
        assert_eq!(entries, vec![(b"k".as_slice(), b"v".as_slice())]);

        let mut visited = Vec::new();
        H5Miterate_with(&map, |key, value| {
            visited.push((key.to_vec(), value.to_vec()));
            Ok(())
        })
        .unwrap();
        assert_eq!(visited, vec![(b"k".to_vec(), b"v".to_vec())]);

        let mut copied = Vec::new();
        H5M_iterate_into(&map, &mut copied).unwrap();
        assert_eq!(copied, vec![(b"k".to_vec(), b"v".to_vec())]);

        assert_eq!(H5Mdelete(&mut map, b"k").unwrap(), Some(b"v".to_vec()));
        H5Mclose(&mut map);
        assert!(H5Mget_count(&map).is_err());
    }
}
