use std::fmt;
use crate::{
BondDescriptor, BondEndpoint, BondKind, BondLength, BondOrder, BondParticipant, BondPolarity,
BondStrength, BondValidationError,
};
#[derive(Clone, Debug, PartialEq)]
pub struct Bond {
kind: BondKind,
order: Option<BondOrder>,
endpoints: Vec<BondEndpoint>,
participants: Vec<BondParticipant>,
polarity: Option<BondPolarity>,
strength: Option<BondStrength>,
length: Option<BondLength>,
angle_label: Option<BondDescriptor>,
descriptors: Vec<BondDescriptor>,
}
impl Bond {
#[must_use]
pub fn new(kind: BondKind) -> Self {
Self {
kind,
order: None,
endpoints: Vec::new(),
participants: Vec::new(),
polarity: None,
strength: None,
length: None,
angle_label: None,
descriptors: Vec::new(),
}
}
#[must_use]
pub fn between(endpoint_a: BondEndpoint, endpoint_b: BondEndpoint, kind: BondKind) -> Self {
Self::new(kind)
.with_endpoint(endpoint_a)
.with_endpoint(endpoint_b)
}
#[must_use]
pub const fn kind(&self) -> BondKind {
self.kind
}
#[must_use]
pub const fn order(&self) -> Option<BondOrder> {
self.order
}
#[must_use]
pub fn endpoints(&self) -> &[BondEndpoint] {
&self.endpoints
}
#[must_use]
pub fn participants(&self) -> &[BondParticipant] {
&self.participants
}
#[must_use]
pub const fn polarity(&self) -> Option<BondPolarity> {
self.polarity
}
#[must_use]
pub const fn strength(&self) -> Option<BondStrength> {
self.strength
}
#[must_use]
pub const fn length(&self) -> Option<&BondLength> {
self.length.as_ref()
}
#[must_use]
pub const fn angle_label(&self) -> Option<&BondDescriptor> {
self.angle_label.as_ref()
}
#[must_use]
pub fn descriptors(&self) -> &[BondDescriptor] {
&self.descriptors
}
#[must_use]
pub const fn with_order(mut self, order: BondOrder) -> Self {
self.order = Some(order);
self
}
#[must_use]
pub fn with_endpoint(mut self, endpoint: BondEndpoint) -> Self {
self.endpoints.push(endpoint);
self
}
pub fn try_with_endpoint(self, endpoint: &str) -> Result<Self, BondValidationError> {
Ok(self.with_endpoint(BondEndpoint::new(endpoint)?))
}
#[must_use]
pub fn with_participant(mut self, participant: BondParticipant) -> Self {
if !self.participants.contains(&participant) {
self.participants.push(participant);
}
self
}
pub fn try_with_participant(self, participant: &str) -> Result<Self, BondValidationError> {
Ok(self.with_participant(BondParticipant::new(participant)?))
}
#[must_use]
pub const fn with_polarity(mut self, polarity: BondPolarity) -> Self {
self.polarity = Some(polarity);
self
}
#[must_use]
pub const fn with_strength(mut self, strength: BondStrength) -> Self {
self.strength = Some(strength);
self
}
#[must_use]
pub fn with_length(mut self, length: BondLength) -> Self {
self.length = Some(length);
self
}
pub fn try_with_length(self, value: f64, unit: &str) -> Result<Self, BondValidationError> {
Ok(self.with_length(BondLength::new(value, unit)?))
}
#[must_use]
pub fn with_angle_label(mut self, angle_label: BondDescriptor) -> Self {
self.angle_label = Some(angle_label);
self
}
pub fn try_with_angle_label(self, angle_label: &str) -> Result<Self, BondValidationError> {
let trimmed = angle_label.trim();
if trimmed.is_empty() {
return Err(BondValidationError::EmptyAngleLabel);
}
Ok(self.with_angle_label(BondDescriptor::new(trimmed)?))
}
#[must_use]
pub fn with_descriptor(mut self, descriptor: BondDescriptor) -> Self {
if !self.descriptors.contains(&descriptor) {
self.descriptors.push(descriptor);
}
self
}
pub fn try_with_descriptor(self, descriptor: &str) -> Result<Self, BondValidationError> {
Ok(self.with_descriptor(BondDescriptor::new(descriptor)?))
}
}
impl fmt::Display for Bond {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some((first, rest)) = self.endpoints.split_first() {
write!(formatter, "{first}")?;
for endpoint in rest {
write!(formatter, "-{endpoint}")?;
}
write!(formatter, " {} bond", self.kind)?;
} else {
write!(formatter, "{} bond", self.kind)?;
}
if let Some(order) = self.order {
write!(formatter, " ({order})")?;
}
Ok(())
}
}