use std::fmt::{Display, Formatter};
use sim_lib_music_core::ObjectId;
use crate::{SerialOrigin, SerialPlanError, SerialRole};
fn validate_id_text(
kind: &'static str,
value: impl Into<String>,
) -> Result<String, SerialPlanError> {
let value = value.into();
if value.trim().is_empty() {
return Err(SerialPlanError::InvalidId {
kind,
value,
reason: "value cannot be empty",
});
}
if value
.chars()
.any(|ch| !(ch.is_ascii_alphanumeric() || matches!(ch, '/' | '-' | '_' | '.')))
{
return Err(SerialPlanError::InvalidId {
kind,
value,
reason: "value must use ASCII letters, digits, /, -, _, or .",
});
}
Ok(value)
}
macro_rules! stable_id {
($name:ident, $kind:literal, $doc:literal) => {
#[doc = $doc]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self, SerialPlanError> {
Ok(Self(validate_id_text($kind, value)?))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for $name {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
};
}
stable_id!(
RowInstanceId,
"row-instance",
"Stable identity for one row instance in a serial plan."
);
stable_id!(
SerialEventId,
"serial-event",
"Stable identity for one planned serial event."
);
stable_id!(
SimultaneousGroupId,
"simultaneous-group",
"Stable identity for one equal-onset simultaneous event group."
);
stable_id!(
StructuralReadingId,
"structural-reading",
"Stable identity for one structural reading or deployment witness."
);
pub type VoiceId = ObjectId;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OrdinalRef {
pub row_id: RowInstanceId,
pub ordinal: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StructuralLicense {
pub reading_id: StructuralReadingId,
pub rationale: String,
}
impl StructuralLicense {
pub fn new(
reading_id: StructuralReadingId,
rationale: impl Into<String>,
) -> Result<Self, SerialPlanError> {
let rationale = rationale.into();
if rationale.trim().is_empty() {
return Err(SerialPlanError::EmptyStructuralLicenseRationale(reading_id));
}
Ok(Self {
reading_id,
rationale,
})
}
}
impl OrdinalRef {
pub fn new(row_id: RowInstanceId, ordinal: usize) -> Self {
Self { row_id, ordinal }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EventPlacement {
simultaneous_group: Option<SimultaneousGroupId>,
}
impl EventPlacement {
pub const fn independent() -> Self {
Self {
simultaneous_group: None,
}
}
pub fn simultaneous(group: SimultaneousGroupId) -> Self {
Self {
simultaneous_group: Some(group),
}
}
pub fn simultaneous_group(&self) -> Option<&SimultaneousGroupId> {
self.simultaneous_group.as_ref()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlannedSerialEvent {
pub id: SerialEventId,
pub ordinals: Vec<OrdinalRef>,
pub role: SerialRole,
pub origin: SerialOrigin,
pub voice: VoiceId,
pub placement: EventPlacement,
pub parents: Vec<SerialEventId>,
pub licenses: Vec<StructuralLicense>,
}