use std::mem::discriminant;
use bacnet_types::enums::Reliability;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::{ScheduleObject, ScheduleTargetOutcome, ScheduleWrite};
use crate::traits::ReliabilityEvaluation;
impl ScheduleObject {
fn scheduled_values(&self) -> impl Iterator<Item = &PropertyValue> {
let weekly = self.weekly_schedule.iter().flatten();
let exceptions = self
.exception_schedule
.iter()
.flat_map(|event| &event.list_of_time_values);
weekly
.chain(exceptions)
.map(|time_value| &time_value.value)
.chain(std::iter::once(&self.schedule_default))
.filter(|value| **value != PropertyValue::Null)
}
pub(super) fn values_share_one_datatype(&self) -> bool {
let mut datatypes = self.scheduled_values().map(discriminant);
match datatypes.next() {
Some(first) => datatypes.all(|datatype| datatype == first),
None => true,
}
}
pub(super) fn complete_write(
&mut self,
write: &ScheduleWrite,
outcomes: &[ScheduleTargetOutcome],
) -> bool {
let datatype = discriminant(&write.value);
if !self
.scheduled_values()
.any(|value| discriminant(value) == datatype)
{
return false;
}
for (reference, outcome) in write.references.iter().zip(outcomes) {
match outcome {
ScheduleTargetOutcome::Accepted => {
self.refusing_references
.retain(|refusing| refusing != reference);
}
ScheduleTargetOutcome::Failed if write.retry => {
self.refusing_references
.retain(|refusing| refusing != reference);
}
ScheduleTargetOutcome::DatatypeRefused
| ScheduleTargetOutcome::ReferenceRefused
if self.list_of_object_property_references.contains(reference)
&& !self.refusing_references.contains(reference) =>
{
self.refusing_references.push(reference.clone());
}
_ => {}
}
}
matches!(
self.recompute_reliability(),
ReliabilityEvaluation::Changed { .. }
)
}
pub(super) fn retry_refused(&self) -> Option<ScheduleWrite> {
if self.refusing_references.is_empty() || self.present_value == PropertyValue::Null {
return None;
}
Some(ScheduleWrite {
value: self.present_value.clone(),
priority: self.priority_for_writing,
references: self.refusing_references.clone(),
retry: true,
})
}
pub(super) fn retry_refused_naming(&self, target: ObjectIdentifier) -> Option<ScheduleWrite> {
if self.out_of_service || !self.in_effective_period {
return None;
}
if !self
.refusing_references
.iter()
.any(|reference| reference.object_identifier == target)
{
return None;
}
let mut write = self.retry_refused()?;
write
.references
.retain(|reference| reference.object_identifier == target);
Some(write)
}
pub(super) fn recompute_reliability(&mut self) -> ReliabilityEvaluation {
if self.out_of_service {
return ReliabilityEvaluation::Unchanged;
}
let misconfigured =
!self.values_share_one_datatype() || !self.refusing_references.is_empty();
let new_reliability = if self.owns_configuration_error {
if misconfigured {
Reliability::CONFIGURATION_ERROR
} else {
Reliability::NO_FAULT_DETECTED
}
} else if misconfigured && self.reliability == Reliability::NO_FAULT_DETECTED {
Reliability::CONFIGURATION_ERROR
} else {
return ReliabilityEvaluation::Unchanged;
};
self.owns_configuration_error = misconfigured;
if new_reliability == self.reliability {
return ReliabilityEvaluation::Unchanged;
}
let old_reliability = std::mem::replace(&mut self.reliability, new_reliability);
ReliabilityEvaluation::Changed {
old_reliability,
new_reliability,
}
}
}