use std::collections::BTreeSet;
use std::error::Error;
use std::fmt::{Display, Formatter};
use phasesmith_model::{DomainError, ProjectRecord, RecordId};
use crate::{TofLeBailCheckpoint, TofLeBailError, TofLeBailInput, TofLeBailOptions};
#[derive(Clone, Debug, PartialEq)]
pub struct TofLeBailAnalysis {
pub histogram_id: RecordId,
pub input: TofLeBailInput,
pub options: TofLeBailOptions,
pub checkpoint: Option<TofLeBailCheckpoint>,
}
impl TofLeBailAnalysis {
pub fn validate(&self) -> Result<(), TofProjectError> {
self.input.validate()?;
self.options.validate()?;
if let Some(checkpoint) = &self.checkpoint {
checkpoint.validate_for(&self.input, &self.options)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TofLeBailProjectState {
pub project: ProjectRecord,
pub analyses: Vec<TofLeBailAnalysis>,
}
impl TofLeBailProjectState {
pub fn validate(&self) -> Result<(), TofProjectError> {
self.project.validate()?;
let mut histogram_ids = BTreeSet::new();
for analysis in &self.analyses {
analysis.validate()?;
if !histogram_ids.insert(analysis.histogram_id.clone()) {
return Err(TofProjectError::DuplicateAnalysis {
histogram_id: analysis.histogram_id.clone(),
});
}
let histogram = self
.project
.tof_histograms
.iter()
.find(|item| item.histogram_id == analysis.histogram_id)
.ok_or_else(|| TofProjectError::UnknownHistogram {
histogram_id: analysis.histogram_id.clone(),
})?;
if analysis.input.pattern != histogram.pattern
|| analysis.input.instrument != histogram.experiment.instrument
{
return Err(TofProjectError::HistogramStateMismatch {
histogram_id: analysis.histogram_id.clone(),
});
}
let phase_ids = analysis
.input
.phases
.iter()
.map(crate::TofLeBailPhase::phase_id)
.collect::<Vec<_>>();
if phase_ids != histogram.phase_ids.iter().collect::<Vec<_>>() {
return Err(TofProjectError::PhaseOrderMismatch {
histogram_id: analysis.histogram_id.clone(),
});
}
for phase in &analysis.input.phases {
let stored = self
.project
.phases
.iter()
.find(|item| &item.phase_id == phase.phase_id())
.ok_or_else(|| TofProjectError::PhaseOrderMismatch {
histogram_id: analysis.histogram_id.clone(),
})?;
if stored.name != phase.name() {
return Err(TofProjectError::PhaseStateMismatch {
phase_id: stored.phase_id.clone(),
});
}
}
}
Ok(())
}
}
#[derive(Debug)]
pub enum TofProjectError {
Domain(DomainError),
Workflow(TofLeBailError),
DuplicateAnalysis {
histogram_id: RecordId,
},
UnknownHistogram {
histogram_id: RecordId,
},
HistogramStateMismatch {
histogram_id: RecordId,
},
PhaseOrderMismatch {
histogram_id: RecordId,
},
PhaseStateMismatch {
phase_id: RecordId,
},
}
impl Display for TofProjectError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Domain(error) => Display::fmt(error, formatter),
Self::Workflow(error) => Display::fmt(error, formatter),
Self::DuplicateAnalysis { histogram_id } => {
write!(
formatter,
"duplicate TOF analysis for histogram {histogram_id}"
)
}
Self::UnknownHistogram { histogram_id } => {
write!(formatter, "unknown TOF histogram {histogram_id}")
}
Self::HistogramStateMismatch { histogram_id } => write!(
formatter,
"TOF analysis state differs from histogram {histogram_id}"
),
Self::PhaseOrderMismatch { histogram_id } => write!(
formatter,
"TOF analysis phase order differs from histogram {histogram_id}"
),
Self::PhaseStateMismatch { phase_id } => {
write!(formatter, "TOF analysis phase state differs for {phase_id}")
}
}
}
}
impl Error for TofProjectError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Domain(error) => Some(error),
Self::Workflow(error) => Some(error),
_ => None,
}
}
}
impl From<DomainError> for TofProjectError {
fn from(value: DomainError) -> Self {
Self::Domain(value)
}
}
impl From<TofLeBailError> for TofProjectError {
fn from(value: TofLeBailError) -> Self {
Self::Workflow(value)
}
}