use std::io::BufRead;
use crate::{Error, Result};
use super::{Metadata, SegmentInfo, SignalInfo};
#[derive(Debug, Clone, PartialEq)]
pub enum Specifications {
SingleSegment {
signals: Vec<SignalInfo>,
},
MultiSegment {
segments: Vec<SegmentInfo>,
},
}
impl Specifications {
#[must_use]
pub fn signals(&self) -> Option<&[SignalInfo]> {
if let Self::SingleSegment { signals } = self {
Some(signals)
} else {
None
}
}
#[must_use]
pub fn segments(&self) -> Option<&[SegmentInfo]> {
if let Self::MultiSegment { segments } = self {
Some(segments)
} else {
None
}
}
#[must_use]
pub const fn is_multi_segment(&self) -> bool {
matches!(self, Self::MultiSegment { .. })
}
#[must_use]
pub const fn is_single_segment(&self) -> bool {
matches!(self, Self::SingleSegment { .. })
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Header {
pub metadata: Metadata,
pub specifications: Specifications,
pub info_strings: Vec<String>,
}
impl Header {
pub fn from_reader<R: BufRead>(reader: &mut R) -> Result<Self> {
let lines: Vec<String> = reader.lines().collect::<std::io::Result<Vec<String>>>()?;
Self::from_lines(&lines)
}
fn from_lines(lines: &[String]) -> Result<Self> {
let record_line_idx = lines
.iter()
.position(|line| {
let trimmed = line.trim();
!trimmed.is_empty() && !trimmed.starts_with('#')
})
.ok_or_else(|| Error::InvalidHeader("Missing record line in header".to_string()))?;
let metadata = Metadata::from_record_line(&lines[record_line_idx])?;
let mut line_idx = record_line_idx + 1;
let is_multi_segment = metadata.num_segments.is_some();
let (signals, segments) = if is_multi_segment {
#[allow(clippy::expect_used)]
let num_segments = metadata.num_segments.expect("num_segments should be Some");
let mut segment_specs = Vec::new();
while line_idx < lines.len() && segment_specs.len() < num_segments {
let line = lines[line_idx].trim();
if line.is_empty() || line.starts_with('#') {
line_idx += 1;
continue;
}
segment_specs.push(SegmentInfo::from_segment_line(line)?);
line_idx += 1;
}
if segment_specs.len() < num_segments {
return Err(Error::InvalidHeader(format!(
"Expected {} segment specifications, found {}",
num_segments,
segment_specs.len()
)));
}
(None, Some(segment_specs))
} else {
let num_signals = metadata.num_signals;
let mut signal_specs = Vec::new();
while line_idx < lines.len() && signal_specs.len() < num_signals {
let line = lines[line_idx].trim();
if line.is_empty() || line.starts_with('#') {
line_idx += 1;
continue;
}
signal_specs.push(SignalInfo::from_signal_line(line)?);
line_idx += 1;
}
if signal_specs.len() < num_signals {
return Err(Error::InvalidHeader(format!(
"Expected {} signal specifications, found {}",
num_signals,
signal_specs.len()
)));
}
(Some(signal_specs), None)
};
let mut info_strings = Vec::new();
while line_idx < lines.len() {
let line = lines[line_idx].trim();
if line.starts_with('#') {
let info = line.trim_start_matches('#').to_string();
info_strings.push(info);
}
line_idx += 1;
}
#[allow(clippy::expect_used)]
let specifications = match (signals, segments) {
(Some(signals), None) => Specifications::SingleSegment { signals },
(None, Some(segments)) => Specifications::MultiSegment { segments },
_ => unreachable!("Either signals or segments should be Some, but not both"),
};
Ok(Self {
metadata,
specifications,
info_strings,
})
}
#[must_use]
pub const fn metadata(&self) -> &Metadata {
&self.metadata
}
#[must_use]
pub const fn specifications(&self) -> &Specifications {
&self.specifications
}
#[must_use]
pub fn signals(&self) -> Option<&[SignalInfo]> {
self.specifications.signals()
}
#[must_use]
pub fn segments(&self) -> Option<&[SegmentInfo]> {
self.specifications.segments()
}
#[must_use]
pub fn info_strings(&self) -> &[String] {
&self.info_strings
}
#[must_use]
pub const fn is_multi_segment(&self) -> bool {
self.specifications.is_multi_segment()
}
#[must_use]
pub const fn is_single_segment(&self) -> bool {
self.specifications.is_single_segment()
}
#[must_use]
pub const fn num_signals(&self) -> usize {
self.metadata.num_signals
}
#[must_use]
pub const fn num_segments(&self) -> Option<usize> {
self.metadata.num_segments
}
}