use std::ops::RangeInclusive;
use bacnet_encoding::constructed::{decode_color_command_value, encode_color_command};
use bacnet_types::constructed::{BACnetColorCommand, BACnetXyColor};
use bacnet_types::enums::ColorOperation;
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
use crate::common;
pub(super) const FADE_TIME_MS: RangeInclusive<u32> = 100..=86_400_000;
pub(super) const KELVIN: RangeInclusive<u32> = 1_000..=30_000;
pub(super) const KELVIN_STEP: RangeInclusive<u32> = 1..=30_000;
const COORDINATE: RangeInclusive<f32> = 0.0..=1.0;
fn within<T: PartialOrd>(value: Option<T>, range: RangeInclusive<T>) -> Result<(), Error> {
match value {
Some(value) if !range.contains(&value) => Err(common::value_out_of_range_error()),
_ => Ok(()),
}
}
fn required<T>(value: Option<T>) -> Result<T, Error> {
value.ok_or_else(common::value_out_of_range_error)
}
pub(super) fn xy_in_range(color: BACnetXyColor) -> bool {
COORDINATE.contains(&color.x) && COORDINATE.contains(&color.y)
}
pub(super) fn check_color(command: &BACnetColorCommand) -> Result<(), Error> {
match command.operation {
ColorOperation::FADE_TO_COLOR => {
if !xy_in_range(required(command.target_color)?) {
return Err(common::value_out_of_range_error());
}
within(command.fade_time, FADE_TIME_MS)
}
ColorOperation::STOP => Ok(()),
_ => Err(common::value_out_of_range_error()),
}
}
pub(super) fn check_color_temperature(command: &BACnetColorCommand) -> Result<(), Error> {
match command.operation {
ColorOperation::FADE_TO_CCT => {
within(Some(required(command.target_color_temperature)?), KELVIN)?;
within(command.fade_time, FADE_TIME_MS)
}
ColorOperation::RAMP_TO_CCT => {
within(Some(required(command.target_color_temperature)?), KELVIN)?;
within(command.ramp_rate, KELVIN_STEP)
}
ColorOperation::STEP_UP_CCT | ColorOperation::STEP_DOWN_CCT => {
within(command.step_increment, KELVIN_STEP)
}
ColorOperation::STOP => Ok(()),
_ => Err(common::value_out_of_range_error()),
}
}
pub(super) fn decode_write(
value: PropertyValue,
check: fn(&BACnetColorCommand) -> Result<(), Error>,
) -> Result<BACnetColorCommand, Error> {
let command = common::decode_command_write(value, decode_color_command_value)?;
check(&command)?;
Ok(command)
}
pub(super) fn encode(command: &BACnetColorCommand) -> PropertyValue {
let mut encoded = BytesMut::new();
encode_color_command(&mut encoded, command);
PropertyValue::ApplicationData(encoded.to_vec())
}