//! Thread-safe observable key/value storage.

use crate::{DictionaryEventAction, Error};
use libremetaverse_types::compat::{DictionaryEntry, IEnumerator, Object};
use std::collections::HashMap;
use std::hash::Hash;
use std::sync::{Arc, Mutex};

type Callback = Arc<dyn Fn(DictionaryEventAction, DictionaryEntry) + Send + Sync>;

pub struct ObservableDictionary<TKey, TValue> {
    values: Mutex<HashMap<TKey, Option<TValue>>>,
    delegates: Mutex<HashMap<DictionaryEventAction, Vec<Callback>>>,
}

impl<TKey, TValue> ObservableDictionary<TKey, TValue>
where
    TKey: Clone + Eq + Hash + Into<Object>,
    TValue: Clone + PartialEq + Into<Object>,
{
    pub fn new_with_constructor() -> Result<Self, Error> {
        Self::new_with_capacity(0)
    }

    pub fn new_with_int32(capacity: i32) -> Result<Self, Error> {
        let capacity = usize::try_from(capacity).map_err(|_| Error::Argument)?;
        Self::new_with_capacity(capacity)
    }

    fn new_with_capacity(capacity: usize) -> Result<Self, Error> {
        Ok(Self {
            values: Mutex::new(HashMap::with_capacity(capacity)),
            delegates: Mutex::new(HashMap::new()),
        })
    }

    pub fn add(&self, key: TKey, value: Option<TValue>) -> Result<(), Error> {
        self.values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .insert(key.clone(), value.clone());
        self.fire(DictionaryEventAction::Add, key, value);
        Ok(())
    }

    pub fn add_delegate(
        &self,
        action: DictionaryEventAction,
        callback: Callback,
    ) -> Result<(), Error> {
        self.delegates
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .entry(action)
            .or_default()
            .push(callback);
        Ok(())
    }

    pub fn remove_delegate(
        &self,
        action: DictionaryEventAction,
        callback: Callback,
    ) -> Result<(), Error> {
        if let Some(callbacks) = self
            .delegates
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .get_mut(&action)
        {
            callbacks.retain(|registered| !Arc::ptr_eq(registered, &callback));
        }
        Ok(())
    }

    pub fn clear(&self) -> Result<(), Error> {
        let removed: Vec<_> = self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .drain()
            .collect();
        for (key, value) in removed {
            self.fire(DictionaryEventAction::Remove, key, value);
        }
        Ok(())
    }

    pub fn contains_key(&self, key: TKey) -> Result<bool, Error> {
        Ok(self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .contains_key(&key))
    }

    pub fn contains_value(&self, value: Option<TValue>) -> Result<bool, Error> {
        Ok(self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .values()
            .any(|candidate| candidate == &value))
    }

    pub fn find(
        &self,
        predicate: Box<dyn Fn(&Option<TValue>) -> bool + Send + Sync>,
    ) -> Result<Option<TValue>, Error> {
        Ok(self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .values()
            .find(|value| predicate(value))
            .cloned()
            .flatten())
    }

    pub fn find_all_with_predicate(
        &self,
        predicate: Box<dyn Fn(&TKey) -> bool + Send + Sync>,
    ) -> Result<Vec<TKey>, Error> {
        Ok(self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .keys()
            .filter(|key| predicate(key))
            .cloned()
            .collect())
    }

    pub fn find_all_with_predicate_6d98ccd2(
        &self,
        predicate: Box<dyn Fn(&Option<TValue>) -> bool + Send + Sync>,
    ) -> Result<Vec<Option<TValue>>, Error> {
        Ok(self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .values()
            .filter(|value| predicate(value))
            .cloned()
            .collect())
    }

    pub fn get_enumerator(&self) -> Result<IEnumerator, Error> {
        Ok(IEnumerator)
    }

    pub fn remove(&self, key: TKey) -> Result<bool, Error> {
        let removed = self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .remove(&key);
        if let Some(value) = removed {
            self.fire(DictionaryEventAction::Remove, key, value);
            Ok(true)
        } else {
            Ok(false)
        }
    }

    #[must_use]
    pub fn try_get_value(&self, key: TKey, value: &mut Option<TValue>) -> bool {
        let found = self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .get(&key)
            .cloned();
        match found {
            Some(found) => {
                *value = found;
                true
            }
            None => false,
        }
    }

    #[must_use]
    pub fn count(&self) -> i32 {
        i32::try_from(
            self.values
                .lock()
                .unwrap_or_else(std::sync::PoisonError::into_inner)
                .len(),
        )
        .unwrap_or(i32::MAX)
    }

    #[must_use]
    pub fn item(&self, key: Option<TKey>) -> Option<TValue> {
        let key = key?;
        self.values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .get(&key)
            .cloned()
            .flatten()
    }

    pub fn set_item(&mut self, key: Option<TKey>, value: Option<TValue>) {
        let Some(key) = key else { return };
        let action = if self
            .values
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .insert(key.clone(), value.clone())
            .is_some()
        {
            DictionaryEventAction::Change
        } else {
            DictionaryEventAction::Add
        };
        self.fire(action, key, value);
    }

    fn fire(&self, action: DictionaryEventAction, key: TKey, value: Option<TValue>) {
        let callbacks = self
            .delegates
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .get(&action)
            .cloned()
            .unwrap_or_default();
        let entry = DictionaryEntry(key.into(), value.map_or(Object::Undefined, Into::into));
        for callback in callbacks {
            callback(action, DictionaryEntry(entry.0.clone(), entry.1.clone()));
        }
    }
}
