use std::collections::{HashMap, hash_map::Entry};
use std::sync::Arc;
use std::collections::HashSet;
use serde::{Serialize, Deserialize, ser::{Serializer, SerializeMap}, de::{Visitor, MapAccess, Deserializer, Error}};
use thiserror::Error;
use crate::util::*;
#[derive(Debug, Clone, PartialEq, Eq, Suitability)]
pub struct NamedPartitioning {
map: HashMap<String, Arc<str>>,
if_none: Option<Arc<str>>
}
#[derive(Debug, Error)]
pub enum MakeNamedPartitioningError {
#[error("Attempted to make multiple partitions named {name:?}.")]
DuplicatePartition {
name: String
},
#[error("Attempted to assign element {element:?} to partition {second_partition:?} when it's already in partition {first_partition:?}.")]
DuplicateElement {
first_partition: String,
second_partition: String,
element: Option<String>
}
}
impl NamedPartitioning {
pub fn try_from_iter<I: IntoIterator<Item = (String, Vec<Option<String>>)>>(iter: I) -> Result<Self, MakeNamedPartitioningError> {
let mut ret = NamedPartitioning {
map: HashMap::new(),
if_none: None
};
let mut partition_names = HashSet::<Arc<str>>::new();
for (partition_name, elements) in iter {
let partition_name: Arc<str> = Arc::from(&*partition_name);
if partition_names.insert(partition_name.clone()) {
for element in elements {
match element {
Some(element) => match ret.map.entry(element) {
Entry::Vacant(e) => {e.insert(partition_name.clone());},
Entry::Occupied(e) => {
let (element, name) = e.remove_entry();
Err(MakeNamedPartitioningError::DuplicateElement {
first_partition: name.to_string(),
second_partition: partition_name.to_string(),
element: Some(element)
})?
}
},
None => if let Some(ref first_partition) = ret.if_none {
Err(MakeNamedPartitioningError::DuplicateElement {
first_partition: first_partition.to_string(),
second_partition: partition_name.to_string(),
element: None
})?
} else {
ret.if_none = Some(partition_name.clone());
}
}
}
} else {
return Err(MakeNamedPartitioningError::DuplicatePartition {name: partition_name.to_string()});
}
}
Ok(ret)
}
pub fn contains(&self, element: Option<&str>) -> bool {
debug!(NamedPartitioning::contains, self, element);
match element {
Some(element) => self.map.contains_key(element),
None => self.if_none.is_some()
}
}
pub fn get_partition_of<'a>(&'a self, element: Option<&str>) -> Option<&'a str> {
debug!(NamedPartitioning::get_partition_of, self, element);
match element {
Some(element) => self.map.get(element).map(|x| &**x),
None => self.if_none.as_deref()
}
}
}
struct NamedPartitioningVisitor;
impl<'de> Visitor<'de> for NamedPartitioningVisitor {
type Value = NamedPartitioning;
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
NamedPartitioning::try_from_iter(std::iter::from_fn(|| map.next_entry::<String, Vec<Option<String>>>().transpose()).collect::<Result<Vec<_>, _>>()?).map_err(A::Error::custom)
}
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "Expected a map")
}
}
impl<'de> Deserialize<'de> for NamedPartitioning {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_map(NamedPartitioningVisitor)
}
}
impl Serialize for NamedPartitioning {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut x = HashMap::<&str, Vec<Option<&str>>>::new();
for (element, partition_name) in self.map.iter() {
x.entry(partition_name).or_default().push(Some(element));
}
if let Some(ref partition_name) = self.if_none {
x.entry(partition_name).or_default().push(None);
}
let mut serializer = serializer.serialize_map(Some(x.len()))?;
for (name, values) in x {
serializer.serialize_entry(name, &values)?;
}
serializer.end()
}
}