use crate::error::Error;
use crate::Key;
use data_value::DataValue;
/// Basic trait for the work with the candidate data
/// This trait is used to store and retrive the data for the candidates
pub trait CandidateData:
    Default
    + Clone
    + std::fmt::Debug
    + Default
    + IntoIterator<Item = (Key, DataValue)>
    + Send
    + Sync
    + 'static
{
    /// Get the value for the key [`Key`]
    fn get_value(&self, key: &Key) -> Result<DataValue, Error>;
    fn get_value_ref(&self, key: &Key) -> Option<&DataValue>;
    /// Store the value [`DataValue`] for the key [`Key`] and return the old value if any
    fn store_value(&mut self, key: &Key, value: DataValue) -> Option<DataValue>;
    /// Merge the data from the other candidate
    fn merge(&mut self, other: &Self);
    /// Get the keys for the candidate
    fn keys(&self) -> Vec<Key>;
    /// Select the values for the keys
    fn select(&self, keys: &[Key]) -> Vec<DataValue>;
    /// Remove data from the candidate
    fn remove(&mut self, key: &Key) -> Option<DataValue>;
}
macro_rules! impl_candidate_data_for_hm {
    ($t: path) => {
        impl CandidateData for $t {
            fn get_value(&self, key: &Key) -> Result<DataValue, Error> {
                Ok(self.get(key).cloned().unwrap_or_else(|| DataValue::Null))
            }
            fn get_value_ref(&self, key: &Key) -> Option<&DataValue> {
                self.get(key)
            }
            fn store_value(&mut self, key: &Key, value: DataValue) -> Option<DataValue> {
                self.insert(key.clone(), value)
            }
            fn merge(&mut self, other: &Self) {
                for (key, value) in other.iter() {
                    self.store_value(key, value.clone());
                }
            }
            fn keys(&self) -> Vec<Key> {
                let mut v = self.keys().cloned().collect::<Vec<_>>();
                v.sort();
                v
            }
            fn select(&self, keys: &[Key]) -> Vec<DataValue> {
                keys.iter()
                    .map(|key| self.get_value(key).unwrap_or_default())
                    .collect()
            }

            fn remove(&mut self, key: &Key) -> Option<DataValue> {
                self.remove(key)
            }
        }
    };
}

impl_candidate_data_for_hm! {::halfbrown::HashMap<Key, DataValue>}
impl_candidate_data_for_hm! {::std::collections::HashMap<Key, DataValue>}

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

    #[rstest]
    #[case(halfbrown::HashMap::new())]
    #[case(std::collections::HashMap::new())]
    fn test_candidate_data<T: CandidateData>(#[case] mut candidate: T) {
        let key = Key::from("test");
        let value = DataValue::from(1);
        candidate.store_value(&key, value.clone());
        assert_eq!(candidate.get_value(&key).unwrap(), value);
        assert_eq!(candidate.get_value_ref(&key).unwrap(), &value);
        assert_eq!(candidate.keys(), vec![key.clone()]);
        assert_eq!(candidate.select(&[key.clone()]), vec![value.clone()]);
        assert_eq!(candidate.remove(&key), Some(value.clone()));
        assert_eq!(candidate.remove(&key), None);
    }
}
