use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use crate::{Error, Header, Result, SegmentInfo, SignalInfo};
#[derive(Debug, Clone)]
enum SegmentState {
NotLoaded,
Loaded(Box<SegmentData>),
Null,
}
#[derive(Debug, Clone)]
pub struct SegmentData {
pub header: Header,
pub base_path: PathBuf,
}
pub struct SegmentManager {
base_path: PathBuf,
segments: Vec<SegmentInfo>,
states: Vec<SegmentState>,
current_segment: usize,
#[allow(dead_code)]
cumulative_samples: Vec<u64>,
}
impl SegmentManager {
pub fn new(base_path: PathBuf, segments: Vec<SegmentInfo>) -> Self {
let num_segments = segments.len();
let states = vec![SegmentState::NotLoaded; num_segments];
let mut cumulative_samples = Vec::with_capacity(num_segments + 1);
cumulative_samples.push(0);
let mut total = 0u64;
for segment in &segments {
total += segment.num_samples;
cumulative_samples.push(total);
}
Self {
base_path,
segments,
states,
current_segment: 0,
cumulative_samples,
}
}
pub fn load_segment(&mut self, index: usize) -> Result<&SegmentData> {
if index >= self.segments.len() {
return Err(Error::InvalidHeader(format!(
"Segment index {} out of bounds (record has {} segments)",
index,
self.segments.len()
)));
}
if matches!(&self.states[index], SegmentState::Loaded(_)) {
match &self.states[index] {
SegmentState::Loaded(data) => return Ok(data),
_ => unreachable!(),
}
}
if matches!(&self.states[index], SegmentState::Null) {
return Err(Error::InvalidHeader("Cannot load null segment".to_string()));
}
let segment_info = &self.segments[index];
if segment_info.record_name == "~" {
self.states[index] = SegmentState::Null;
return Err(Error::InvalidHeader(
"Segment is null (missing data)".to_string(),
));
}
let segment_header_path = self
.base_path
.join(format!("{}.hea", segment_info.record_name));
let file = File::open(&segment_header_path).map_err(|e| {
Error::InvalidPath(format!(
"Failed to open segment header '{}': {}",
segment_header_path.display(),
e
))
})?;
let mut reader = BufReader::new(file);
let header = Header::from_reader(&mut reader)?;
if header.specifications.is_multi_segment() {
return Err(Error::InvalidHeader(
"Nested multi-segment records are not supported".to_string(),
));
}
let segment_base_path = segment_header_path
.parent()
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
let data = SegmentData {
header,
base_path: segment_base_path,
};
self.states[index] = SegmentState::Loaded(Box::new(data));
match &self.states[index] {
SegmentState::Loaded(data) => Ok(data),
_ => unreachable!(),
}
}
pub fn current_segment(&mut self) -> Result<&SegmentData> {
self.load_segment(self.current_segment)
}
pub fn current_signals(&mut self) -> Result<&[SignalInfo]> {
let data = self.current_segment()?;
data.header
.specifications
.signals()
.ok_or_else(|| Error::InvalidHeader("Segment has no signals".to_string()))
}
pub fn current_base_path(&mut self) -> Result<&Path> {
let data = self.current_segment()?;
Ok(&data.base_path)
}
#[allow(dead_code)]
pub fn is_current_null(&self) -> bool {
matches!(self.states[self.current_segment], SegmentState::Null)
}
#[must_use]
pub const fn current_index(&self) -> usize {
self.current_segment
}
#[must_use]
pub const fn num_segments(&self) -> usize {
self.segments.len()
}
#[must_use]
pub fn segment_info(&self, index: usize) -> Option<&SegmentInfo> {
self.segments.get(index)
}
}