use super::{AggregateChangeSets, AggregateType};
use ferroui_reactive::ObservableExt;
use ferroui_system::IObservable;
use std::cmp::Ordering;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum MaxOrMin {
Max,
Min,
}
pub(crate) type ChangesAndCollection<T> = (Rc<dyn super::IAggregateChangeSet<T>>, Vec<T>);
fn compare<R: PartialOrd>(left: &R, right: &R) -> Ordering {
left.partial_cmp(right).unwrap_or_else(|| {
let left_unordered = left.partial_cmp(left).is_none();
let right_unordered = right.partial_cmp(right).is_none();
match (left_unordered, right_unordered) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => Ordering::Equal,
}
})
}
fn extreme<T, R: Copy + PartialOrd>(collection: &[T], value_selector: &dyn Fn(&T) -> R, max_or_min: MaxOrMin) -> Option<R> {
let mut values = collection.iter().map(value_selector);
let first = values.next()?;
Some(values.fold(first, |current, value| {
let replaces = match max_or_min {
MaxOrMin::Max => compare(&value, ¤t) == Ordering::Greater,
MaxOrMin::Min => compare(&value, ¤t) == Ordering::Less,
};
if replaces {
value
} else {
current
}
}))
}
pub(crate) fn calculate<T: 'static, R: Copy + PartialOrd + 'static>(
source: Rc<dyn IObservable<ChangesAndCollection<T>>>,
value_selector: Rc<dyn Fn(&T) -> R>,
max_or_min: MaxOrMin,
empty_value: R,
) -> Rc<dyn IObservable<R>> {
source
.scan_with(None::<R>, move |state: Option<R>, (changes, collection): ChangesAndCollection<T>| {
let mut current = state;
let mut requires_reset = false;
for change in changes.items() {
let value = value_selector(change.item());
let existing = *current.get_or_insert(value);
if change.aggregate_type() == AggregateType::Add {
let replaces = match max_or_min {
MaxOrMin::Max => compare(&value, &existing) == Ordering::Greater,
MaxOrMin::Min => compare(&value, &existing) == Ordering::Less,
};
if replaces {
current = Some(value);
}
} else {
if compare(&value, &existing) != Ordering::Equal {
continue;
}
requires_reset = true;
break;
}
}
if requires_reset {
current = extreme(&collection, &*value_selector, max_or_min);
}
current
})
.select(move |extreme: Option<R>| extreme.unwrap_or(empty_value))
.distinct_until_changed()
}
pub(crate) fn cache_changes_and_collection<T: Clone + 'static, K: Eq + std::hash::Hash + Clone + 'static>(
source: Rc<dyn IObservable<crate::cache::ChangeSet<T, K>>>,
) -> Rc<dyn IObservable<ChangesAndCollection<T>>> {
source.publish_with(|shared: Rc<dyn IObservable<crate::cache::ChangeSet<T, K>>>| {
let changes: AggregateChangeSets<T> = shared.select(super::cache_aggregate::<T, K>);
let data = crate::cache::ObservableCacheEx::to_collection(&shared);
data.zip(changes, |data: Vec<T>, changes| (changes, data))
})
}
pub(crate) fn list_changes_and_collection<T: Clone + PartialEq + 'static>(
source: Rc<dyn IObservable<crate::list::ChangeSet<T>>>,
) -> Rc<dyn IObservable<ChangesAndCollection<T>>> {
source.publish_with(|shared: Rc<dyn IObservable<crate::list::ChangeSet<T>>>| {
let changes: AggregateChangeSets<T> = shared.select(super::list_aggregate::<T>);
let data = crate::list::ObservableListEx::to_collection(&shared);
data.zip(changes, |data: Vec<T>, changes| (changes, data))
})
}