use std::ops::{
Bound,
RangeBounds
};
use conciliator::{
Inline,
Buffer,
Print,
Conciliator,
Paint
};
use rusqlite::{
Row,
Error as SqlError
};
use rusqlite::types::FromSqlError;
use greg::{
Point,
Calendar
};
use greg_tz::{
ZONES,
Zone
};
use crate::util::TryFrames;
pub struct TimeZone {
pub id: u64,
pub zone: Zone
}
pub struct Entry {
start: Point,
stop: Option<Point>,
zone: Zone
}
pub struct List<'z> {
zones: &'z [(Point, Zone)]
}
impl<'r> TryFrom<&Row<'r>> for TimeZone {
type Error = SqlError;
fn try_from(value: &Row<'r>) -> Result<Self, Self::Error> {
let (id, name): (u64, String) = value.try_into()?;
let zone = *ZONES.get(&name)
.ok_or_else(|| format!("invalid zone {name}"))
.map_err(Into::into)
.map_err(FromSqlError::Other)?;
Ok(Self {id, zone})
}
}
impl RangeBounds<Point> for Entry {
fn start_bound(&self) -> Bound<&Point> {Bound::Included(&self.start)}
fn end_bound(&self) -> Bound<&Point> {
self.stop.as_ref().map_or(Bound::Unbounded, Bound::Excluded)
}
}
impl<'z> List<'z> {
pub fn new(zones: &'z [(Point, Zone)]) -> Self {Self {zones}}
pub fn current(&self) -> Option<Entry> {
let now = Point::now();
TryFrames::new(self.zones.iter().copied())
.map(|(start, stop, zone)| Entry {start, stop, zone})
.find(|ent| ent.contains(&now))
}
}
impl<'z> Print for List<'z> {
fn print<C: Conciliator + ?Sized>(self, con: &C) {
let frames = TryFrames::new(self.zones.iter().copied());
match frames.len() {
0 => {con.warn("No time zones recorded!");},
1 => {con.status("1 time zone recorded:");},
n => {con.status(..).push(n).push(" time zones recorded:");}
};
for (start, stop, zone) in frames {
con.line(" ").push(Entry{start, stop, zone});
}
}
}
impl Inline for Entry {
fn inline(&self, buffer: &mut Buffer) {
let cal = Calendar(self.zone);
let start = cal.lookup(self.start);
buffer.push_iota(self.zone.name());
match self.stop {
Some(stop) => buffer
.push(" ")
.push(start)
.push(" → ")
.push(cal.lookup(stop)),
None => buffer
.push(" since ")
.push(start)
};
}
}