use std::borrow::Cow;
use std::sync::Arc;
use bacnet_types::calendar::SpecificDate;
use bacnet_types::constructed::BACnetCalendarEntry;
use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
use super::{calendar_metadata, date_list};
use crate::clock::ClockReader;
use crate::common::{self, read_property_list_property};
use crate::traits::BACnetObject;
pub struct CalendarObject {
oid: ObjectIdentifier,
name: String,
description: String,
date_list: Vec<BACnetCalendarEntry>,
clock: Option<Arc<dyn ClockReader>>,
}
impl CalendarObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
let oid = ObjectIdentifier::new(ObjectType::CALENDAR, instance)?;
Ok(Self {
oid,
name: name.into(),
description: String::new(),
date_list: Vec::new(),
clock: None,
})
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
pub fn add_date_entry(&mut self, entry: BACnetCalendarEntry) -> Result<(), Error> {
if !entry.is_valid() {
return Err(common::value_out_of_range_error());
}
if self.date_list.len() >= date_list::MAX_DATE_LIST_ENTRIES {
return Err(date_list::no_space_error());
}
self.date_list.push(entry);
Ok(())
}
pub fn clear_date_list(&mut self) {
self.date_list.clear();
}
pub fn date_list(&self) -> &[BACnetCalendarEntry] {
&self.date_list
}
pub fn is_active_on(&self, day: SpecificDate) -> bool {
self.date_list.iter().any(|entry| entry.matches(day))
}
pub fn present_value(&self) -> bool {
self.clock
.as_ref()
.and_then(|clock| clock.read_clock())
.and_then(|frame| SpecificDate::from_date(&frame.local_date))
.is_some_and(|today| self.is_active_on(today))
}
}
impl BACnetObject for CalendarObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
match property {
p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
Ok(PropertyValue::ObjectIdentifier(self.oid))
}
p if p == PropertyIdentifier::OBJECT_NAME => {
Ok(PropertyValue::CharacterString(self.name.clone()))
}
p if p == PropertyIdentifier::DESCRIPTION => {
Ok(PropertyValue::CharacterString(self.description.clone()))
}
p if p == PropertyIdentifier::OBJECT_TYPE => {
Ok(PropertyValue::Enumerated(ObjectType::CALENDAR.to_raw()))
}
p if p == PropertyIdentifier::PRESENT_VALUE => {
Ok(PropertyValue::Boolean(self.present_value()))
}
p if p == PropertyIdentifier::DATE_LIST => Ok(date_list::read(&self.date_list)),
p if p == PropertyIdentifier::PROPERTY_LIST => {
read_property_list_property(&self.property_list(), array_index)
}
_ => Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
}),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
if property == PropertyIdentifier::DATE_LIST {
if array_index.is_some() {
return Err(common::property_is_not_an_array_error());
}
self.date_list = date_list::decode_write(value)?;
return Ok(());
}
if property == PropertyIdentifier::PRESENT_VALUE {
return Err(common::write_access_denied_error());
}
Err(common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
))
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
calendar_metadata::for_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
self.clock = clock;
}
fn calendar_state_internal(&self, day: SpecificDate) -> Option<bool> {
Some(self.is_active_on(day))
}
}