use std::result;
use anyhow::anyhow;
use serde::{ser::SerializeTuple, Serialize, Serializer};
use crate::Result;
pub const MIN_SPACING: f32 = 0.001;
fn decompose_time(time: f32) -> (u32, u32, f32) {
let mut seconds = time;
let hours = (seconds / 3600.0).floor() as u32;
seconds %= 3600.0;
let mins = (seconds / 60.0).floor() as u32;
seconds %= 60.0;
(hours, mins, seconds)
}
pub fn seconds_to_hhmmss(time: f32) -> String {
let (hours, mins, seconds) = decompose_time(time);
format!("{}:{:02}:{:02}", hours, mins, (seconds.floor() as u32))
}
pub fn seconds_to_hhmmss_sss(time: f32) -> String {
let (hours, mins, seconds) = decompose_time(time);
format!("{}:{:02}:{:06.3}", hours, mins, seconds)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Period {
begin: f32,
end: f32,
}
impl Period {
pub fn new(begin: f32, end: f32) -> Result<Period> {
if begin < end && begin >= 0.0 && end >= 0.0 {
Ok(Period {
begin: begin,
end: end,
})
} else {
Err(anyhow!(
"Beginning of range is before end: {}-{}",
begin,
end
))
}
}
pub fn from_union_opt(p1: Option<Period>, p2: Option<Period>) -> Option<Period> {
match (p1, p2) {
(None, None) => None,
(Some(p), None) => Some(p),
(None, Some(p)) => Some(p),
(Some(p1), Some(p2)) => Some(p1.union(p2)),
}
}
pub fn begin(&self) -> f32 {
self.begin
}
pub fn end(&self) -> f32 {
self.end
}
pub fn duration(&self) -> f32 {
self.end - self.begin
}
pub fn midpoint(&self) -> f32 {
self.begin + self.duration() / 2.0
}
pub fn grow(&self, before: f32, after: f32) -> Period {
let mid = self.midpoint();
Period {
begin: (self.begin - before).min(mid).max(0.0),
end: (self.end + after).max(mid + MIN_SPACING),
}
}
pub fn union(&self, other: Period) -> Period {
Period {
begin: self.begin.min(other.begin),
end: self.end.max(other.end),
}
}
pub fn begin_after(&mut self, limit: f32) -> Result<()> {
if limit > self.end - 2.0 * MIN_SPACING {
Err(anyhow!(
"Cannot begin time period {:?} after {}",
self,
limit
))?;
}
self.begin = self.begin.max(limit + MIN_SPACING);
Ok(())
}
pub fn end_before(&mut self, limit: f32) -> Result<()> {
if limit < self.begin + 2.0 * MIN_SPACING {
Err(anyhow!(
"Cannot truncate time period {:?} at {}",
self,
limit
))?;
}
self.end = self.end.min(limit - MIN_SPACING);
Ok(())
}
pub fn distance(&self, other: Period) -> Option<f32> {
if self.end <= other.begin {
Some((other.begin - self.end).abs())
} else if other.end <= self.begin {
Some((self.begin - other.end).abs())
} else {
None
}
}
pub fn overlap(&self, other: Period) -> f32 {
(self.end.min(other.end) - self.begin.max(other.begin)).max(0.0)
}
}
impl Serialize for Period {
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&self.begin)?;
tuple.serialize_element(&self.end)?;
tuple.end()
}
}
pub trait ToTimestamp {
fn to_timestamp(&self) -> String;
fn to_file_timestamp(&self) -> String {
self.to_timestamp().replace(".", "_")
}
}
impl ToTimestamp for f32 {
fn to_timestamp(&self) -> String {
format!("{:09.3}", *self)
}
}
impl ToTimestamp for Period {
fn to_timestamp(&self) -> String {
format!("{:09.3}-{:09.3}", self.begin(), self.end())
}
}
#[test]
fn test_timestamp() {
assert_eq!("00010.500", (10.5).to_timestamp());
let period = Period::new(10.0, 20.0).unwrap();
assert_eq!("00010.000-00020.000", period.to_timestamp());
assert_eq!("00010_000-00020_000", period.to_file_timestamp());
}