use std::collections::{HashMap, HashSet};
use bacnet_objects::clock::ClockFrame;
use bacnet_services::cov_multiple::{COVNotificationItem, COVNotificationValue};
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::primitives::ObjectIdentifier;
use crate::cov::multiple_reads::MultipleReads;
use crate::cov::timed::TimedChange;
use crate::cov::{CovSubscriptionKey, CovSubscriptionSnapshot};
pub(super) type Coordinate = (ObjectIdentifier, Option<PropertyIdentifier>, Option<u32>);
pub(super) type Retained<'a> = [(&'a CovSubscriptionSnapshot, &'a [COVNotificationValue])];
pub(super) type History<'a> = [(&'a CovSubscriptionKey, &'a TimedChange)];
pub(super) type Latest<'a> = HashMap<&'a CovSubscriptionKey, &'a TimedChange>;
pub(super) type LastChange = (u64, ClockFrame);
pub(super) enum FieldStamp {
Unowned,
At(LastChange),
Withhold,
}
pub(super) type Stamp<'a> = dyn Fn(&Coordinate, &[u8]) -> FieldStamp + 'a;
pub(super) fn build_items(
history: &History<'_>,
retained: Option<&Retained<'_>>,
latest: &Latest<'_>,
reads: &MultipleReads,
untimed: &HashSet<Coordinate>,
stamp: &Stamp<'_>,
) -> (Vec<COVNotificationItem>, Option<LastChange>) {
let mut items: Vec<COVNotificationItem> = Vec::new();
let item_for = |items: &mut Vec<COVNotificationItem>, oid: ObjectIdentifier| {
items
.iter()
.position(|item| item.monitored_object_identifier == oid)
.unwrap_or_else(|| {
items.push(COVNotificationItem {
monitored_object_identifier: oid,
list_of_values: Vec::new(),
});
items.len() - 1
})
};
for &(key, change) in history {
let index = item_for(&mut items, key.object());
let list = &mut items[index].list_of_values;
for value in change.values() {
let coordinate = (
key.object(),
Some(value.property_identifier),
value.property_array_index,
);
if !untimed.contains(&coordinate) {
list.push(value.clone());
}
}
}
let Some(retained) = retained else {
items.retain(|item| !item.list_of_values.is_empty());
for item in &mut items {
let values = std::mem::take(&mut item.list_of_values);
let rows = values.len();
item.list_of_values = collapse_repeats(values, rows);
}
return (items, None);
};
let history_rows: Vec<usize> = items.iter().map(|item| item.list_of_values.len()).collect();
let start = |index: usize| history_rows.get(index).copied().unwrap_or(0);
for (sub, values) in retained {
let values = if sub.timestamped {
match latest.get(sub.key()) {
Some(change) => change.values().to_vec(),
None => continue,
}
} else {
values.to_vec()
};
let index = item_for(&mut items, sub.monitored_object_identifier);
let from = start(index);
for value in values {
let current = &mut items[index].list_of_values[from..];
if let Some(existing) = current.iter_mut().find(|v| {
v.property_identifier == value.property_identifier
&& v.property_array_index == value.property_array_index
}) {
existing.time_of_change = existing.time_of_change.or(value.time_of_change);
} else {
items[index].list_of_values.push(value);
}
}
}
for (index, item) in items.iter_mut().enumerate() {
if item.list_of_values[start(index)..]
.iter()
.any(|v| v.property_identifier == PropertyIdentifier::STATUS_FLAGS)
{
continue;
}
if let Some(encoded) = reads.encoded_flags(&item.monitored_object_identifier) {
item.list_of_values.push(COVNotificationValue {
property_identifier: PropertyIdentifier::STATUS_FLAGS,
property_array_index: None,
value: encoded.to_vec(),
time_of_change: None,
});
}
}
for (sub, _) in retained {
let current = latest.get(sub.key());
if sub.timestamped && current.is_none() {
continue;
}
let Some(index) = items
.iter()
.position(|item| item.monitored_object_identifier == sub.monitored_object_identifier)
else {
continue;
};
let from = start(index);
if let Some(value) = items[index].list_of_values[from..]
.iter_mut()
.find(|value| {
Some(value.property_identifier) == sub.monitored_property
&& value.property_array_index == sub.monitored_property_array_index
})
{
value.time_of_change = current.map(|change| change.frame().local_time);
}
}
let mut stamped: Option<LastChange> = None;
for (index, item) in items.iter_mut().enumerate() {
let oid = item.monitored_object_identifier;
let from = start(index);
let mut kept = Vec::with_capacity(item.list_of_values.len());
for (at, mut value) in std::mem::take(&mut item.list_of_values)
.into_iter()
.enumerate()
{
if at >= from && value.time_of_change.is_none() {
let coordinate = (
oid,
Some(value.property_identifier),
value.property_array_index,
);
match stamp(&coordinate, &value.value) {
FieldStamp::Unowned => {}
FieldStamp::At((seq, frame)) => {
value.time_of_change = Some(frame.local_time);
stamped = Some(match stamped {
Some(newest) if newest.0 > seq => newest,
_ => (seq, frame),
});
}
FieldStamp::Withhold => continue,
}
}
kept.push(value);
}
item.list_of_values = kept;
}
for (index, item) in items.iter_mut().enumerate() {
let oid = item.monitored_object_identifier;
for value in &mut item.list_of_values[start(index)..] {
if untimed.contains(&(
oid,
Some(value.property_identifier),
value.property_array_index,
)) {
value.time_of_change = None;
}
}
}
for (index, item) in items.iter_mut().enumerate() {
item.list_of_values =
collapse_repeats(std::mem::take(&mut item.list_of_values), start(index));
}
items.retain(|item| !item.list_of_values.is_empty());
(items, stamped)
}
fn collapse_repeats(
values: Vec<COVNotificationValue>,
history: usize,
) -> Vec<COVNotificationValue> {
let repeated: Vec<bool> = (0..values.len())
.map(|at| {
at < history
&& values[at + 1..]
.iter()
.find(|later| {
later.property_identifier == values[at].property_identifier
&& later.property_array_index == values[at].property_array_index
})
.is_some_and(|next| *next == values[at])
})
.collect();
values
.into_iter()
.zip(repeated)
.filter_map(|(value, repeated)| (!repeated).then_some(value))
.collect()
}