use chrono::{NaiveDate, NaiveTime};
use crate::{Error, Result};
struct OptionalFields {
sampling_frequency: Option<f64>,
counter_frequency: Option<f64>,
base_counter: Option<f64>,
num_samples: Option<u64>,
base_time: Option<NaiveTime>,
base_date: Option<NaiveDate>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum FieldType {
Frequency,
NumSamples,
Time,
Date,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
enum ParseState {
Start,
AfterFrequency,
AfterNumSamples,
AfterTime,
AfterDate,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Metadata {
pub name: String,
pub num_segments: Option<usize>,
pub num_signals: usize,
pub sampling_frequency: Option<f64>,
pub counter_frequency: Option<f64>,
pub base_counter: Option<f64>,
pub num_samples: Option<u64>,
pub base_time: Option<NaiveTime>,
pub base_date: Option<NaiveDate>,
}
impl Metadata {
pub const DEFAULT_SAMPLING_FREQUENCY: f64 = 250.0;
pub const DEFAULT_BASE_COUNTER: f64 = 0.0;
pub fn from_record_line(line: &str) -> Result<Self> {
let line = line.trim();
let mut parts = line.split_whitespace();
let (name, num_segments) = Self::parse_record_name(
parts
.next()
.ok_or_else(|| Error::InvalidHeader("Missing record name".to_string()))?,
)?;
let num_signals = parts
.next()
.ok_or_else(|| Error::InvalidHeader("Missing number of signals".to_string()))?
.parse()
.map_err(|e| Error::InvalidHeader(format!("Invalid number of signals: {e}")))?;
let remaining: Vec<&str> = parts.collect();
let optional_fields = Self::parse_optional_fields(&remaining)?;
Ok(Self {
name,
num_segments,
num_signals,
sampling_frequency: optional_fields.sampling_frequency,
counter_frequency: optional_fields.counter_frequency,
base_counter: optional_fields.base_counter,
num_samples: optional_fields.num_samples,
base_time: optional_fields.base_time,
base_date: optional_fields.base_date,
})
}
fn parse_optional_fields(fields: &[&str]) -> Result<OptionalFields> {
let mut sampling_frequency = None;
let mut counter_frequency = None;
let mut base_counter = None;
let mut num_samples = None;
let mut base_time = None;
let mut base_date = None;
let mut state = ParseState::Start;
for field in fields {
let field_type = Self::detect_field_type(field, state)?;
match field_type {
FieldType::Frequency => {
(sampling_frequency, counter_frequency, base_counter) =
Self::parse_frequency_field(field)?;
state = ParseState::AfterFrequency;
}
FieldType::NumSamples => {
let n = field.parse().map_err(|e| {
Error::InvalidHeader(format!("Invalid number of samples: {e}"))
})?;
num_samples = Some(n);
state = ParseState::AfterNumSamples;
}
FieldType::Time => {
base_time = Self::parse_base_time(field)?;
state = ParseState::AfterTime;
}
FieldType::Date => {
base_date = Self::parse_base_date(field)?;
state = ParseState::AfterDate;
}
}
}
Ok(OptionalFields {
sampling_frequency,
counter_frequency,
base_counter,
num_samples,
base_time,
base_date,
})
}
fn detect_field_type(field: &str, state: ParseState) -> Result<FieldType> {
if field.contains(':') {
if state >= ParseState::AfterTime {
return Err(Error::InvalidHeader(
"Duplicate or out-of-order time field".to_string(),
));
}
return Ok(FieldType::Time);
}
if field.matches('/').count() == 2 {
if state >= ParseState::AfterTime {
if state >= ParseState::AfterDate {
return Err(Error::InvalidHeader(
"Duplicate or out-of-order date field".to_string(),
));
}
return Ok(FieldType::Date);
}
return Err(Error::InvalidHeader(
"Date field appears before time field".to_string(),
));
}
if field.contains('/') || field.contains('(') {
if state >= ParseState::AfterFrequency {
return Err(Error::InvalidHeader(
"Duplicate or out-of-order frequency field".to_string(),
));
}
return Ok(FieldType::Frequency);
}
match state {
ParseState::Start => Ok(FieldType::Frequency),
ParseState::AfterFrequency => Ok(FieldType::NumSamples),
_ => Err(Error::InvalidHeader(format!(
"Unexpected numeric field '{field}' after time/date"
))),
}
}
fn parse_record_name(field: &str) -> Result<(String, Option<usize>)> {
let (name, num_segments) = match field.split_once('/') {
Some((name, num_segments)) => {
let num_segments = num_segments.parse().map_err(|e| {
Error::InvalidHeader(format!("Invalid number of segments: {e}"))
})?;
if num_segments == 0 {
return Err(Error::InvalidHeader(
"Number of segments must be greater than zero".to_string(),
));
}
(name, Some(num_segments))
}
None => (field, None),
};
if name.is_empty() {
return Err(Error::InvalidHeader("Record name is empty".to_string()));
}
if !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
return Err(Error::InvalidHeader(format!(
"Record name '{name}' contains invalid characters, expected letters, digits, and underscores"
)));
}
Ok((name.to_string(), num_segments))
}
fn parse_frequency_field(field: &str) -> Result<(Option<f64>, Option<f64>, Option<f64>)> {
let (sampling_part, counter_part) = match field.split_once('/') {
Some((s, c)) => (s, Some(c)),
None => (field, None),
};
if sampling_part.is_empty() {
return Err(Error::InvalidHeader(
"Sampling frequency is empty".to_string(),
));
}
let sampling_frequency = Some(
sampling_part
.parse()
.map_err(|e| Error::InvalidHeader(format!("Invalid sampling frequency: {e}")))?,
);
if let Some(sampling_frequency) = sampling_frequency
&& sampling_frequency <= 0.0
{
return Err(Error::InvalidHeader(format!(
"Sampling frequency must be greater than zero, got {sampling_frequency}"
)));
}
let (counter_frequency, base_counter) = match counter_part {
Some(counter_str) => Self::parse_counter_frequency(counter_str)?,
None => (None, None),
};
Ok((sampling_frequency, counter_frequency, base_counter))
}
fn parse_counter_frequency(field: &str) -> Result<(Option<f64>, Option<f64>)> {
if let Some(paren_start) = field.find('(') {
let paren_end = field.find(')').ok_or_else(|| {
Error::InvalidHeader("Missing closing parenthesis in counter frequency".to_string())
})?;
let counter_freq = field[..paren_start]
.parse()
.map_err(|e| Error::InvalidHeader(format!("Invalid counter frequency: {e}")))?;
let base_counter = field[paren_start + 1..paren_end]
.parse()
.map_err(|e| Error::InvalidHeader(format!("Invalid base counter value: {e}")))?;
if counter_freq <= 0.0 {
return Ok((None, None));
}
Ok((Some(counter_freq), Some(base_counter)))
} else {
let counter_freq = field
.parse()
.map_err(|e| Error::InvalidHeader(format!("Invalid counter frequency: {e}")))?;
if counter_freq <= 0.0 {
return Ok((None, None));
}
Ok((Some(counter_freq), None))
}
}
fn parse_base_time(field: &str) -> Result<Option<NaiveTime>> {
let time = NaiveTime::parse_from_str(field, "%H:%M:%S").map_err(|_| {
Error::InvalidHeader(format!("Invalid base time '{field}', expected HH:MM:SS"))
})?;
Ok(Some(time))
}
fn parse_base_date(field: &str) -> Result<Option<NaiveDate>> {
let date = NaiveDate::parse_from_str(field, "%d/%m/%Y").map_err(|_| {
Error::InvalidHeader(format!("Invalid base date '{field}', expected DD/MM/YYYY"))
})?;
Ok(Some(date))
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn num_segments(&self) -> Option<usize> {
self.num_segments
}
#[must_use]
pub const fn num_signals(&self) -> usize {
self.num_signals
}
#[must_use]
pub fn sampling_frequency(&self) -> f64 {
self.sampling_frequency
.unwrap_or(Self::DEFAULT_SAMPLING_FREQUENCY)
}
#[must_use]
pub fn counter_frequency(&self) -> f64 {
self.counter_frequency
.unwrap_or_else(|| self.sampling_frequency())
}
#[must_use]
pub fn base_counter(&self) -> f64 {
self.base_counter.unwrap_or(Self::DEFAULT_BASE_COUNTER)
}
#[must_use]
pub const fn num_samples(&self) -> Option<u64> {
self.num_samples
}
#[must_use]
pub const fn base_time(&self) -> Option<NaiveTime> {
self.base_time
}
#[must_use]
pub const fn base_date(&self) -> Option<NaiveDate> {
self.base_date
}
}