use bacnet_types::constructed::BACnetXyColor;
use bacnet_types::enums::{ObjectType, PropertyIdentifier as P};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use crate::common;
use crate::database::ObjectDatabase;
use crate::property_metadata::{
PropertyConformance::Optional, PropertyMetadata, PropertyPresenceCondition,
PropertyWriteCapability::Always,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorLink {
pub reference: ObjectIdentifier,
pub color_override: Option<ColorOverride>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ColorOverride {
pub active: bool,
pub reference: ObjectIdentifier,
}
impl ColorLink {
pub const fn new(reference: ObjectIdentifier) -> Self {
Self {
reference,
color_override: None,
}
}
pub fn active_reference(&self) -> Option<ObjectIdentifier> {
let reference = match self.color_override {
Some(ColorOverride {
active: true,
reference,
}) => reference,
_ => self.reference,
};
(reference.instance_number() != ObjectIdentifier::WILDCARD_INSTANCE).then_some(reference)
}
pub(super) fn checked(self) -> Result<Self, Error> {
checked_reference(self.reference)?;
if let Some(color_override) = self.color_override {
checked_reference(color_override.reference)?;
}
Ok(self)
}
}
fn checked_reference(reference: ObjectIdentifier) -> Result<ObjectIdentifier, Error> {
if reference.instance_number() == ObjectIdentifier::WILDCARD_INSTANCE
|| matches!(
reference.object_type(),
ObjectType::COLOR | ObjectType::COLOR_TEMPERATURE
)
{
Ok(reference)
} else {
Err(common::value_out_of_range_error())
}
}
pub(super) fn metadata(link: Option<&ColorLink>) -> impl Iterator<Item = PropertyMetadata> {
let overridable = link.is_some_and(|link| link.color_override.is_some());
[
(P::COLOR_REFERENCE, link.is_some()),
(P::COLOR_OVERRIDE, overridable),
(P::OVERRIDE_COLOR_REFERENCE, overridable),
]
.into_iter()
.filter(|&(_, present)| present)
.map(|(property, _)| {
PropertyMetadata::new(
property,
Optional,
Some(PropertyPresenceCondition::LightingColor),
Always,
)
})
}
pub(super) fn read(link: Option<&ColorLink>, property: P) -> Option<Result<PropertyValue, Error>> {
let color_override = link.and_then(|link| link.color_override);
let value = match property {
P::COLOR_REFERENCE => link.map(|link| PropertyValue::ObjectIdentifier(link.reference)),
P::COLOR_OVERRIDE => color_override.map(|o| PropertyValue::Boolean(o.active)),
P::OVERRIDE_COLOR_REFERENCE => {
color_override.map(|o| PropertyValue::ObjectIdentifier(o.reference))
}
_ => return None,
};
Some(value.ok_or_else(common::unknown_property_error))
}
pub(super) fn write(
link: &mut Option<ColorLink>,
property: P,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
if let Err(error) = read(link.as_ref(), property)? {
return Some(Err(error));
}
let link = link.as_mut()?;
Some(match (property, value) {
(P::COLOR_REFERENCE, PropertyValue::ObjectIdentifier(reference)) => {
checked_reference(*reference).map(|reference| link.reference = reference)
}
(P::COLOR_OVERRIDE, PropertyValue::Boolean(active)) => {
if let Some(color_override) = &mut link.color_override {
color_override.active = *active;
}
Ok(())
}
(P::OVERRIDE_COLOR_REFERENCE, PropertyValue::ObjectIdentifier(reference)) => {
checked_reference(*reference).map(|reference| {
if let Some(color_override) = &mut link.color_override {
color_override.reference = reference;
}
})
}
_ => Err(common::invalid_data_type_error()),
})
}
fn read_link(output: &dyn crate::traits::BACnetObject) -> Option<ColorLink> {
let read = |property| output.read_property(property, None).ok();
let Some(PropertyValue::ObjectIdentifier(reference)) = read(P::COLOR_REFERENCE) else {
return None;
};
let color_override = match (read(P::COLOR_OVERRIDE), read(P::OVERRIDE_COLOR_REFERENCE)) {
(
Some(PropertyValue::Boolean(active)),
Some(PropertyValue::ObjectIdentifier(reference)),
) => Some(ColorOverride { active, reference }),
_ => None,
};
Some(ColorLink {
reference,
color_override,
})
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LightingColor {
pub source: ObjectIdentifier,
pub color: OutputColor,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OutputColor {
Xy(BACnetXyColor),
Kelvin(u32),
}
impl ObjectDatabase {
pub fn lighting_color(&self, lighting_output: &ObjectIdentifier) -> Option<LightingColor> {
let source = read_link(self.get(lighting_output)?)?.active_reference()?;
let tracking = self
.get(&source)?
.read_property(P::TRACKING_VALUE, None)
.ok()?;
let color = match (source.object_type(), tracking) {
(ObjectType::COLOR, PropertyValue::List(xy)) => match xy.as_slice() {
[PropertyValue::Real(x), PropertyValue::Real(y)] => {
OutputColor::Xy(BACnetXyColor::new(*x, *y))
}
_ => return None,
},
(ObjectType::COLOR_TEMPERATURE, PropertyValue::Unsigned(kelvin)) => {
OutputColor::Kelvin(u32::try_from(kelvin).ok()?)
}
_ => return None,
};
Some(LightingColor { source, color })
}
}