use crate::{
DataType,
counter::{Counter, CounterMap},
};
use std::collections::{BTreeMap, HashMap};
pub trait IsEmpty {
fn is_empty(&self) -> bool;
}
impl IsEmpty for Counter {
fn is_empty(&self) -> bool {
self.total() == 0
}
}
impl IsEmpty for CounterMap {
fn is_empty(&self) -> bool {
self.iter().all(|(_, counter)| counter.is_empty())
}
}
impl<K, V> IsEmpty for HashMap<K, V> {
fn is_empty(&self) -> bool {
HashMap::is_empty(self)
}
}
pub trait Aggregable: Sized + Clone + IsEmpty {
fn new_empty() -> Self;
fn from_counter(counter: &Counter) -> Self;
fn merge(&mut self, other: &Self);
}
impl Aggregable for Counter {
fn new_empty() -> Self {
Counter::new()
}
fn from_counter(counter: &Counter) -> Self {
counter.clone()
}
fn merge(&mut self, other: &Self) {
Counter::merge(self, other);
}
}
impl Aggregable for HashMap<String, u64> {
fn new_empty() -> Self {
HashMap::new()
}
fn from_counter(counter: &Counter) -> Self {
counter.total_by_id()
}
fn merge(&mut self, other: &Self) {
for (key, value) in other {
*self.entry(key.clone()).or_insert(0) += *value;
}
}
}
pub struct AggregationResult<T: Aggregable> {
pub grouped: BTreeMap<String, BTreeMap<DataType, T>>,
pub total_by_type: BTreeMap<DataType, T>,
pub total_combined: T,
}
impl<T: Aggregable> AggregationResult<T> {
pub fn new(counter_map: &CounterMap) -> Self {
let mut grouped: BTreeMap<String, BTreeMap<DataType, T>> = BTreeMap::new();
let mut total_by_type: BTreeMap<DataType, T> = BTreeMap::new();
let mut total_combined = T::new_empty();
for (scope, counter) in counter_map.iter() {
if counter.is_empty() {
continue;
}
let item_summary = T::from_counter(counter);
grouped
.entry(scope.dimension.clone())
.or_default()
.entry(scope.data_type)
.or_insert_with(T::new_empty)
.merge(&item_summary);
total_by_type
.entry(scope.data_type)
.or_insert_with(T::new_empty)
.merge(&item_summary);
total_combined.merge(&item_summary);
}
total_by_type
.entry(DataType::BlockEntity)
.or_insert_with(T::new_empty);
total_by_type
.entry(DataType::Entity)
.or_insert_with(T::new_empty);
total_by_type
.entry(DataType::Player)
.or_insert_with(T::new_empty);
Self {
grouped,
total_by_type,
total_combined,
}
}
fn dimension_combined(&self, dimension: &str) -> T {
let mut combined_summary = T::new_empty();
if let Some(types_map) = self.grouped.get(dimension) {
for item_summary in types_map.values() {
combined_summary.merge(item_summary);
}
}
combined_summary
}
}
pub trait SummaryDataProvider {
type ItemSummary: Aggregable;
fn get_grouped_data(&self) -> &BTreeMap<String, BTreeMap<DataType, Self::ItemSummary>>;
fn get_total_block_entity_summary(&self) -> &Self::ItemSummary;
fn get_total_entity_summary(&self) -> &Self::ItemSummary;
fn get_total_player_data_summary(&self) -> &Self::ItemSummary;
fn get_total_combined_summary(&self) -> &Self::ItemSummary;
fn calculate_dimension_combined_summary(&self, dimension: &str) -> Self::ItemSummary;
}
impl<T: Aggregable> SummaryDataProvider for AggregationResult<T> {
type ItemSummary = T;
fn get_grouped_data(&self) -> &BTreeMap<String, BTreeMap<DataType, Self::ItemSummary>> {
&self.grouped
}
fn get_total_block_entity_summary(&self) -> &Self::ItemSummary {
self.total_by_type
.get(&DataType::BlockEntity)
.expect("BlockEntity total should always be present due to initialization in new()")
}
fn get_total_entity_summary(&self) -> &Self::ItemSummary {
self.total_by_type
.get(&DataType::Entity)
.expect("Entity total should always be present due to initialization in new()")
}
fn get_total_player_data_summary(&self) -> &Self::ItemSummary {
self.total_by_type
.get(&DataType::Player)
.expect("Player total should always be present due to initialization in new()")
}
fn get_total_combined_summary(&self) -> &Self::ItemSummary {
&self.total_combined
}
fn calculate_dimension_combined_summary(&self, dimension: &str) -> Self::ItemSummary {
self.dimension_combined(dimension)
}
}