use crate::description::util::IterUtil;
use crate::fixture_type::FixtureType;
use crate::physical_descriptions::Filter;
use crate::validation::{ValidationError, ValidationErrorType, ValidationObject, ValidationResult};
use crate::values::{non_empty_string, ColorCie, Name, Node, NodeExt, Rotation};
use crate::ResourceMap;
use serde::de::{Error, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Wheel {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(rename = "Slot", skip_serializing_if = "Vec::is_empty", default)]
pub slots: Vec<WheelSlot>,
}
impl Wheel {
pub fn slot(&self, name: &str) -> Option<&WheelSlot> {
self.slots
.iter()
.find(|slot| slot.name.as_ref().map(Name::as_ref) == Some(name))
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
resource_map: &mut ResourceMap,
result: &mut ValidationResult,
) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::Wheel,
None,
ValidationErrorType::MissingName,
));
}
let duplicate_wheel_slot_name = self
.slots
.iter()
.filter_map(|slot| slot.name.as_ref())
.find_duplicate();
if let Some(name) = duplicate_wheel_slot_name {
result.errors.push(ValidationError::new(
ValidationObject::WheelSlot,
name.to_string(),
ValidationErrorType::DuplicateName,
));
}
for slot in &self.slots {
slot.validate(parent_fixture_type, resource_map, result);
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WheelSlot {
#[serde(rename = "@Name", skip_serializing_if = "Option::is_none")]
pub name: Option<Name>,
#[serde(flatten, default)]
pub optic: WheelSlotOptic,
#[serde(
rename = "@MediaFileName",
skip_serializing_if = "Option::is_none",
deserialize_with = "non_empty_string",
default
)]
pub media_name: Option<String>,
#[serde(rename = "Facet", skip_serializing_if = "Vec::is_empty", default)]
pub facets: Vec<PrismFacet>,
#[serde(rename = "AnimationSystem", skip_serializing_if = "Option::is_none")]
pub animation_system: Option<AnimationSystem>,
}
impl WheelSlot {
pub fn filter<'s>(&self, parent_fixture_type: &'s FixtureType) -> Option<&'s Filter> {
let WheelSlotOptic::Filter(node) = &self.optic else {
return None;
};
let filter_name = node.single()?;
parent_fixture_type
.physical_descriptions
.filter(filter_name)
}
pub fn validate(
&self,
parent_fixture_type: &FixtureType,
resource_map: &mut ResourceMap,
result: &mut ValidationResult,
) {
if self.name.is_none() {
result.errors.push(ValidationError::new(
ValidationObject::WheelSlot,
None,
ValidationErrorType::MissingName,
));
}
let name = self.name.as_ref();
if let Some(media_name) = &self.media_name {
let media_exists = resource_map.read_wheel_media(media_name).is_ok();
if !media_exists {
result.errors.push(ValidationError::new(
ValidationObject::WheelSlot,
name.map(Name::to_string),
ValidationErrorType::MediaNotFound(media_name.clone()),
));
}
}
if let (WheelSlotOptic::Filter(node), None) =
(&self.optic, self.filter(parent_fixture_type))
{
result.errors.push(ValidationError::new(
ValidationObject::WheelSlot,
name.map(Name::to_string),
ValidationErrorType::LinkNotFound(ValidationObject::Filter, node.clone()),
));
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WheelSlotOptic {
#[serde(rename = "@Color")]
Color(ColorCie),
#[serde(rename = "@Filter")]
Filter(Node),
}
impl Default for WheelSlotOptic {
fn default() -> Self {
WheelSlotOptic::Color(ColorCie::white())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PrismFacet {
#[serde(rename = "@Color", default = "ColorCie::white")]
pub color: ColorCie,
#[serde(rename = "@Rotation")]
pub rotation: Rotation,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AnimationSystem {
#[serde(
rename = "@P1",
serialize_with = "serialize_xy_array",
deserialize_with = "deserialize_xy_array"
)]
pub p1: [f64; 2],
#[serde(
rename = "@P2",
serialize_with = "serialize_xy_array",
deserialize_with = "deserialize_xy_array"
)]
pub p2: [f64; 2],
#[serde(
rename = "@P3",
serialize_with = "serialize_xy_array",
deserialize_with = "deserialize_xy_array"
)]
pub p3: [f64; 2],
#[serde(rename = "@Radius")]
pub radius: f64,
}
fn serialize_xy_array<S>(value: &[f64; 2], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{},{}", value[0], value[1]))
}
fn deserialize_xy_array<'de, D>(deserializer: D) -> Result<[f64; 2], D::Error>
where
D: Deserializer<'de>,
{
struct XyArrayVisitor;
impl<'de> Visitor<'de> for XyArrayVisitor {
type Value = [f64; 2];
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a floating point array in the format float,float")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
let values = v.split(',').map(|value_str| {
value_str.trim().parse::<f64>().map_err(|_| {
E::invalid_value(Unexpected::Str(value_str), &"a floating point number")
})
});
take_two(values, || E::invalid_value(Unexpected::Str(v), &self))
}
}
deserializer.deserialize_str(XyArrayVisitor)
}
fn take_two<I: Iterator<Item = Result<T, E>>, T, E>(
mut iter: I,
on_missing: impl FnOnce() -> E,
) -> Result<[T; 2], E> {
let val0 = match iter.next() {
Some(val0) => val0?,
None => return Err(on_missing()),
};
let val1 = match iter.next() {
Some(val1) => val1?,
None => return Err(on_missing()),
};
if iter.next().is_some() {
Err(on_missing())
} else {
Ok([val0, val1])
}
}