Documentation
use clock::{Clock, FakeClock};
use std::{
    collections::HashMap,
    time::{Duration, SystemTime},
};

#[derive(Debug, Clone, thiserror::Error)]
pub enum PunchError {
    #[error("employee: {} has already punched in", .0)]
    AlreadyPunchedIn(String),

    #[error("employee: {} is not punched in", .0)]
    NotPunchedIn(String),

    #[error("invalid punch out time")]
    InvalidPunchOutTime,
}

#[derive(Debug, Default)]
pub struct PunchClock {
    clock: Clock,
    log: HashMap<String, SystemTime>,
}

impl PunchClock {
    pub fn new<C: Into<Clock>>(clock: C) -> Self {
        Self { clock: clock.into(), ..Default::default() }
    }

    pub fn ingress<S: Into<String>>(&mut self, name: S) -> Result<SystemTime, PunchError> {
        let name = name.into();
        if self.log.contains_key(&name) {
            return Err(PunchError::AlreadyPunchedIn(name));
        }
        let now = self.clock.now();
        self.log.insert(name, now);
        Ok(now)
    }

    pub fn egress<S: Into<String>>(&mut self, name: S) -> Result<Duration, PunchError> {
        let name = name.into();
        match self.log.remove(&name) {
            Some(t) => {
                self.clock.now().duration_since(t).map_err(|_| PunchError::InvalidPunchOutTime)
            }
            None => Err(PunchError::NotPunchedIn(name)),
        }
    }
}

fn main() {
    let clock = FakeClock::default();
    let mut punch_clock = PunchClock::new(clock.clone());

    let t = punch_clock.ingress("Jimmy").unwrap();
    println!("Jimmy punched in at: {:?}", t);

    let ttl = Duration::from_secs(42);
    clock.advance(ttl);

    let since = punch_clock.egress("Jimmy").unwrap();
    println!("Jimmy punched out after: {:?}", since);

    assert_eq!(ttl, since)
}