use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::primitives::PropertyValue;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Number {
Real,
Double,
Signed,
Unsigned,
}
pub(super) fn numeric(
object_type: ObjectType,
property: PropertyIdentifier,
index: Option<u32>,
) -> Option<Number> {
type O = ObjectType;
type P = PropertyIdentifier;
let value = match property {
P::PRESENT_VALUE | P::RELINQUISH_DEFAULT | P::HIGH_LIMIT | P::LOW_LIMIT => index.is_none(),
P::PRIORITY_ARRAY => index.is_some_and(|slot| slot > 0),
P::COV_INCREMENT | P::DEADBAND => {
return match object_type {
O::ANALOG_INPUT | O::ANALOG_OUTPUT | O::ANALOG_VALUE => Some(Number::Real),
O::LARGE_ANALOG_VALUE => Some(Number::Double),
O::INTEGER_VALUE | O::POSITIVE_INTEGER_VALUE => Some(Number::Unsigned),
_ => None,
}
.filter(|_| index.is_none());
}
_ => false,
};
if !value {
return None;
}
match object_type {
O::ANALOG_INPUT | O::ANALOG_OUTPUT | O::ANALOG_VALUE => Some(Number::Real),
O::LARGE_ANALOG_VALUE => Some(Number::Double),
O::INTEGER_VALUE => Some(Number::Signed),
O::POSITIVE_INTEGER_VALUE => Some(Number::Unsigned),
_ => None,
}
}
pub(super) fn finite(value: PropertyValue) -> Result<PropertyValue, String> {
match value {
PropertyValue::Real(v) if !v.is_finite() => {
Err("the number is out of a REAL's range".into())
}
PropertyValue::Double(v) if !v.is_finite() => {
Err("the number is out of a Double's range".into())
}
value => Ok(value),
}
}
pub(super) fn coerce(number: Number, value: PropertyValue) -> Result<PropertyValue, String> {
use PropertyValue as V;
let whole = |what: &str| format!("{what} takes a whole number");
finite(match (number, value) {
(Number::Real, V::Unsigned(n)) => V::Real(n as f32),
(Number::Real, V::Signed(n)) => V::Real(n as f32),
(Number::Double, V::Unsigned(n)) => V::Double(n as f64),
(Number::Double, V::Signed(n)) => V::Double(f64::from(n)),
(Number::Double, V::Real(n)) => V::Double(f64::from(n)),
(Number::Signed, V::Unsigned(n)) => {
V::Signed(i32::try_from(n).map_err(|_| format!("{n} is out of an INTEGER's range"))?)
}
(Number::Signed, V::Real(_)) => return Err(whole("this INTEGER property")),
(Number::Unsigned, V::Signed(n)) => {
return Err(format!("{n} is negative; this property is Unsigned"));
}
(Number::Unsigned, V::Real(_)) => return Err(whole("this Unsigned property")),
(_, value) => value,
})
}