use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
use bacnet_types::enums::{
ErrorClass, ErrorCode, LifeSafetyOperation, LifeSafetyState, SilencedState,
};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use crate::traits::LifeSafetyOperationEffect;
use super::{
life_safety_error, valid_life_safety_state, LifeSafetyPointObject, LifeSafetyZoneObject,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LifeSafetyPointResetContext {
pub object_identifier: ObjectIdentifier,
pub operation: LifeSafetyOperation,
pub present_value: LifeSafetyState,
pub tracking_value: LifeSafetyState,
pub silenced: SilencedState,
pub operation_expected: LifeSafetyOperation,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LifeSafetyPointResetCommit {
pub present_value: Option<LifeSafetyState>,
pub tracking_value: Option<LifeSafetyState>,
pub silenced: Option<SilencedState>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LifeSafetyZoneResetContext {
pub object_identifier: ObjectIdentifier,
pub operation: LifeSafetyOperation,
pub present_value: LifeSafetyState,
pub tracking_value: LifeSafetyState,
pub silenced: SilencedState,
pub operation_expected: LifeSafetyOperation,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LifeSafetyZoneResetCommit {
pub present_value: Option<LifeSafetyState>,
pub tracking_value: Option<LifeSafetyState>,
pub silenced: Option<SilencedState>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LifeSafetyResetError {
UnsupportedVariant,
InvalidOperationInThisState,
ServiceRequestDenied,
}
pub type LifeSafetyPointResetExecutor = Arc<
dyn Fn(&LifeSafetyPointResetContext) -> Result<LifeSafetyPointResetCommit, LifeSafetyResetError>
+ Send
+ Sync,
>;
pub type LifeSafetyZoneResetExecutor = Arc<
dyn Fn(&LifeSafetyZoneResetContext) -> Result<LifeSafetyZoneResetCommit, LifeSafetyResetError>
+ Send
+ Sync,
>;
pub(super) fn is_reset_operation(operation: LifeSafetyOperation) -> bool {
matches!(
operation,
LifeSafetyOperation::RESET
| LifeSafetyOperation::RESET_ALARM
| LifeSafetyOperation::RESET_FAULT
)
}
fn reset_error(error: LifeSafetyResetError) -> Error {
match error {
LifeSafetyResetError::UnsupportedVariant => {
life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
}
LifeSafetyResetError::InvalidOperationInThisState => {
life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
}
LifeSafetyResetError::ServiceRequestDenied => Error::Protocol {
class: ErrorClass::SERVICES.to_raw() as u32,
code: ErrorCode::SERVICE_REQUEST_DENIED.to_raw() as u32,
},
}
}
fn optional_functionality_error() -> Error {
life_safety_error(ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED)
}
fn invalid_operation_error() -> Error {
life_safety_error(ErrorCode::INVALID_OPERATION_IN_THIS_STATE)
}
fn commit_value_error() -> Error {
life_safety_error(ErrorCode::VALUE_OUT_OF_RANGE)
}
fn valid_silenced_state(state: SilencedState) -> bool {
SilencedState::ALL_NAMED
.iter()
.any(|&(_, named)| named == state)
}
impl LifeSafetyPointObject {
pub(super) fn apply_reset_operation(
&mut self,
operation: LifeSafetyOperation,
) -> Result<LifeSafetyOperationEffect, Error> {
if !is_reset_operation(operation) || self.operation_expected != operation {
return Err(invalid_operation_error());
}
let executor = self
.reset_executor
.clone()
.ok_or_else(optional_functionality_error)?;
let context = LifeSafetyPointResetContext {
object_identifier: self.oid,
operation,
present_value: self.present_value,
tracking_value: self.tracking_value,
silenced: self.silenced,
operation_expected: self.operation_expected,
};
let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
Ok(result) => result,
Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
};
let commit = result.map_err(reset_error)?;
if commit
.present_value
.is_some_and(|value| !valid_life_safety_state(value))
|| commit
.tracking_value
.is_some_and(|value| !valid_life_safety_state(value))
|| commit
.silenced
.is_some_and(|value| !valid_silenced_state(value))
{
return Err(commit_value_error());
}
if let Some(value) = commit.present_value {
self.present_value = value;
}
if let Some(value) = commit.tracking_value {
self.simulation().track(value);
}
if let Some(value) = commit.silenced {
self.silenced = value;
}
self.operation_expected = LifeSafetyOperation::NONE;
Ok(LifeSafetyOperationEffect::Applied)
}
}
impl LifeSafetyZoneObject {
pub(super) fn apply_reset_operation(
&mut self,
operation: LifeSafetyOperation,
) -> Result<LifeSafetyOperationEffect, Error> {
if !is_reset_operation(operation) || self.operation_expected != operation {
return Err(invalid_operation_error());
}
let executor = self
.reset_executor
.clone()
.ok_or_else(optional_functionality_error)?;
let context = LifeSafetyZoneResetContext {
object_identifier: self.oid,
operation,
present_value: self.present_value,
tracking_value: self.tracking_value,
silenced: self.silenced,
operation_expected: self.operation_expected,
};
let result = match catch_unwind(AssertUnwindSafe(|| executor(&context))) {
Ok(result) => result,
Err(_) => return Err(reset_error(LifeSafetyResetError::ServiceRequestDenied)),
};
let commit = result.map_err(reset_error)?;
if commit
.present_value
.is_some_and(|value| !valid_life_safety_state(value))
|| commit
.tracking_value
.is_some_and(|value| !valid_life_safety_state(value))
|| commit
.silenced
.is_some_and(|value| !valid_silenced_state(value))
{
return Err(commit_value_error());
}
if let Some(value) = commit.present_value {
self.present_value = value;
}
if let Some(value) = commit.tracking_value {
self.simulation().track(value);
}
if let Some(value) = commit.silenced {
self.silenced = value;
}
self.operation_expected = LifeSafetyOperation::NONE;
Ok(LifeSafetyOperationEffect::Applied)
}
}