aboutsummaryrefslogtreecommitdiff
path: root/apps/cassiopeia/src/timeline.rs
blob: 70119b30a72527079847151e68373c1e0dd0ed43 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::{
    data::{Delta, Invoice, Session},
    error::{UserError, UserResult},
    Date, Time,
};

/// A timeline entry of sessions and invoices
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Entry {
    Session(Session),
    Invoice(Invoice),
}

impl From<Session> for Entry {
    fn from(s: Session) -> Self {
        Self::Session(s)
    }
}

impl From<Invoice> for Entry {
    fn from(i: Invoice) -> Self {
        Self::Invoice(i)
    }
}

/// A timeline of sessions and invoices, ordered chronologically
#[derive(Debug, Default, Clone)]
pub(crate) struct Timeline {
    inner: Vec<Entry>,
}

impl Timeline {
    /// Take a set of sessions and invoices to sort into a timeline
    pub(crate) fn build(s: Vec<Session>, i: Vec<Invoice>) -> Self {
        let mut inner: Vec<_> = s.into_iter().map(|s| Entry::Session(s)).collect();
        inner.append(&mut i.into_iter().map(|i| Entry::Invoice(i)).collect());
        Self { inner }
    }

    /// Utility function to get the last session in the timeline
    fn last_session(&mut self) -> Option<&mut Session> {
        self.inner
            .iter_mut()
            .find(|e| match e {
                Entry::Session(_) => true,
                _ => false,
            })
            .map(|e| match e {
                Entry::Session(ref mut s) => s,
                _ => unreachable!(),
            })
    }

    /// Utility function to get the last invoice in the timeline
    fn last_invoice(&self) -> Option<&Invoice> {
        self.inner
            .iter()
            .find(|e| match e {
                Entry::Invoice(_) => true,
                _ => false,
            })
            .map(|e| match e {
                Entry::Invoice(ref s) => s,
                _ => unreachable!(),
            })
    }

    /// Get a list of sessions that happened up to a certain invoice date
    ///
    /// **WARNING** If there is no invoice with the given date, this
    /// function will return garbage data, so don't call it with
    /// invoice dates that don't exist.
    ///
    /// Because: if the date passes other invoices on the way, the accumulator
    /// will be discarded and a new count will be started.
    pub(crate) fn session_iter(&self, date: &Date) -> Vec<&Session> {
        self.inner
            .iter()
            .fold((false, vec![]), |(mut done, mut acc), entry| {
                match (done, entry) {
                    // Put sessions into the accumulator
                    (false, Entry::Session(ref s)) => acc.push(s),
                    // When we reach the target invoice, terminate the iterator
                    (false, Entry::Invoice(ref i)) if &i.date == date => done = true,
                    // When we hit another invoice, empty accumulator
                    (false, Entry::Invoice(_)) => acc.clear(),
                    // When we are ever "done", skip all other entries
                    (true, _) => {}
                }

                (done, acc)
            })
            .1
    }

    /// Start a new session, if no active session is already in progress
    pub(crate) fn start(&mut self, time: Time) -> UserResult<Delta> {
        match self.last_session() {
            Some(s) if !s.finished() => Err(UserError::ActiveSessionExists),
            _ => Ok(()),
        }?;

        self.inner.push(Session::start(time.clone()).into());
        Ok(Delta::Start(time))
    }

    /// Stop an ongoing session, if one exists
    pub(crate) fn stop(&mut self, time: Time) -> UserResult<Delta> {
        match self.last_session() {
            Some(s) if s.finished() => Err(UserError::NoActiveSession),
            _ => Ok(()),
        }?;

        self.last_session().unwrap().stop(time.clone());
        Ok(Delta::Stop(time))
    }

    /// Create a new invoice on the given day
    pub(crate) fn invoice(&mut self, date: Date) -> UserResult<Delta> {
        match self.last_invoice() {
            // If an invoice on the same day exists already
            Some(i) if i.date == date => Err(UserError::SameDayInvoice),
            // If there was no work since the last invoice
            Some(ref i) if self.session_iter(&i.date).len() == 0 => Err(UserError::NoWorkInvoice),
            // Otherwise everything is coolio
            _ => Ok(()),
        }?;

        self.inner.push(Invoice::new(date.clone()).into());
        Ok(Delta::Invoice(date))
    }
}