use std::collections::HashMap;
use super::normalize::normalize_name;
pub struct Collection<T> {
items: Vec<T>,
by_id: HashMap<String, usize>,
by_norm: HashMap<String, Vec<usize>>,
by_faction: HashMap<String, Vec<usize>>,
norm_names: Vec<Option<String>>,
}
impl<T> Collection<T> {
pub fn build(
items: Vec<T>,
id_of: impl Fn(&T) -> String,
name_of: impl Fn(&T) -> Option<&str>,
faction_of: impl Fn(&T) -> Option<&str>,
dedupe_of: impl Fn(&T) -> String,
) -> Self {
let mut kept: Vec<T> = Vec::with_capacity(items.len());
let mut by_id: HashMap<String, usize> = HashMap::new();
let mut by_norm: HashMap<String, Vec<usize>> = HashMap::new();
let mut by_faction: HashMap<String, Vec<usize>> = HashMap::new();
let mut norm_names: Vec<Option<String>> = Vec::with_capacity(items.len());
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
for item in items {
if !seen.insert(dedupe_of(&item)) {
continue; }
let idx = kept.len();
by_id.entry(id_of(&item)).or_insert(idx);
let norm = name_of(&item).map(normalize_name);
if let Some(key) = &norm {
by_norm.entry(key.clone()).or_default().push(idx);
}
norm_names.push(norm);
if let Some(faction) = faction_of(&item) {
by_faction.entry(faction.to_string()).or_default().push(idx);
}
kept.push(item);
}
Self {
items: kept,
by_id,
by_norm,
by_faction,
norm_names,
}
}
pub fn all(&self) -> &[T] {
&self.items
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn get(&self, id: &str) -> Option<&T> {
self.by_id.get(id).map(|&i| &self.items[i])
}
pub fn has(&self, id: &str) -> bool {
self.by_id.contains_key(id)
}
pub(super) fn at(&self, idx: usize) -> &T {
&self.items[idx]
}
pub fn find(&self, query: &str) -> Option<&T> {
self.find_all(query).into_iter().next()
}
pub fn find_all(&self, query: &str) -> Vec<&T> {
if let Some(&i) = self.by_id.get(query) {
return vec![&self.items[i]];
}
let key = normalize_name(query);
if let Some(idxs) = self.by_norm.get(&key) {
if !idxs.is_empty() {
return idxs.iter().map(|&i| &self.items[i]).collect();
}
}
if key.is_empty() {
return Vec::new();
}
self.norm_names
.iter()
.enumerate()
.filter_map(|(i, n)| match n {
Some(name) if name.contains(&key) => Some(&self.items[i]),
_ => None,
})
.collect()
}
pub fn by_faction(&self, faction_id: &str) -> Vec<&T> {
self.by_faction
.get(faction_id)
.map(|idxs| idxs.iter().map(|&i| &self.items[i]).collect())
.unwrap_or_default()
}
pub fn iter(&self) -> std::slice::Iter<'_, T> {
self.items.iter()
}
}
impl<'a, T> IntoIterator for &'a Collection<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}