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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! Timeframes
//!
//! A [`TimeFrame`] represents a [`Timeline`] that is split into discrete slots

//!

use crate::timeline::Timeline;
use std::time::{Duration, SystemTime};

/// Identify a slot in a *specific* timeframe
///
/// The slots are not comparable to others slots made on a
/// different time frame
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(
    any(test, feature = "property-test-api"),
    derive(test_strategy::Arbitrary)
)]
pub struct Slot(pub u64);

impl From<u64> for Slot {
    fn from(slot_number: u64) -> Slot {
        Slot(slot_number)
    }
}

impl From<Slot> for u64 {
    fn from(s: Slot) -> u64 {
        s.0
    }
}

/// Identify a slot in a specific timeframe and a leftover duration
#[derive(Debug)]
pub struct SlotAndDuration {
    /// The slot
    pub slot: Slot,
    /// The offset of a specific time frame in
    pub offset: Duration,
}

/// Time frame which is a timeline that is configured to be split in discrete slots
#[derive(Debug, Clone)]
pub struct TimeFrame {
    timeline: Timeline,
    pub(crate) slot_offset: Slot,
    slot_duration: SlotDuration,
}

/// Duration of a slot
///
/// For now we only supports duration down to the seconds
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlotDuration(u64);

impl SlotDuration {
    /// Create a [`SlotDuration`] from a number of seconds
    ///
    /// # Panics
    ///
    /// Panics if `seconds >= 600`
    pub fn from_secs(seconds: u32) -> Self {
        assert!(seconds < 600);
        SlotDuration(seconds as u64)
    }

    /// Convert this [`SlotDuration`] to a [`core::time::Duration`]
    pub fn to_duration(self) -> Duration {
        Duration::from_secs(self.0)
    }
}

impl TimeFrame {
    /// Create a new time frame with a specific slot size
    ///
    /// ```text
    ///
    /// 0        1        2        3        4        5
    /// x--------x--------x--------x--------x--------x  frame ticking at per_slot
    ///
    /// ^
    /// |
    /// timeline
    /// ```
    ///
    pub fn new(timeline: Timeline, per_slot: SlotDuration) -> Self {
        TimeFrame {
            timeline,
            slot_offset: Slot(0),
            slot_duration: per_slot,
        }
    }

    /// Change time frame at a specific slot
    ///
    /// Note this also change the beginning of this time frame, to start
    ///
    /// ```text
    /// 0        1        2        3        4        5
    /// x--------x--------┳--------x--------x--------x  frame ticking at SlotDuration::from_secs(9)
    ///                   |
    ///                   ┕---x---x---x---x---x         returned frame
    ///                   2   3   4   5   6   7
    ///                   ↑
    ///                   |
    ///                   frame.change_frame(Slot(2), SlotDuration::from_secs(4))
    /// ```
    ///
    pub fn change_frame(&self, slot: Slot, duration_per_slot: SlotDuration) -> Self {
        let d = Duration::from_secs(slot.0 * self.slot_duration.0);
        let new_timeline = self.timeline.advance(d);
        TimeFrame {
            timeline: new_timeline,
            slot_offset: Slot(self.slot_offset.0 + slot.0),
            slot_duration: duration_per_slot,
        }
    }

    /// The current slot offset
    pub fn slot0(&self) -> Slot {
        self.slot_offset
    }

    /// Given a system time get the slot and associated duration leftover
    pub fn slot_at_precise(&self, at: &SystemTime) -> Option<SlotAndDuration> {
        match self.timeline.differential(at) {
            None => None,
            Some(t) => {
                let slot_nb = t.0.as_secs() / self.slot_duration.0;
                let e = slot_nb * self.slot_duration.0;
                let d = t.0 - Duration::from_secs(e); // cannot wrap
                Some(SlotAndDuration {
                    slot: Slot(self.slot_offset.0 + slot_nb),
                    offset: d,
                })
            }
        }
    }

    /// Get the slot associated with the given system time.
    ///
    /// It returns None if the system time doesn't represent a valid slot in this time frame, for
    /// example if the system time is before the time frame starting point.
    pub fn slot_at(&self, at: &SystemTime) -> Option<Slot> {
        match self.timeline.differential(at) {
            None => None,
            Some(t) => {
                let slot_nb = t.0.as_secs() / self.slot_duration.0;
                Some(Slot(self.slot_offset.0 + slot_nb))
            }
        }
    }

    /// Get the system time associated with a slot on a specific timeframe
    ///
    /// Note if the slot is not supposed to be in this reference frame, then
    /// None is returned
    pub fn slot_to_systemtime(&self, slot: Slot) -> Option<SystemTime> {
        slot.0
            .checked_sub(self.slot_offset.0)
            .map(|sd| self.timeline.0 + Duration::from_secs(sd * self.slot_duration.0))
    }

    /// Returns slot duration value.
    pub fn slot_duration(&self) -> u64 {
        self.slot_duration.0
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::timeline::Timeline;

    #[test]
    pub fn it_works() {
        let now = SystemTime::now();
        let t0 = Timeline::new(now);

        let f0 = SlotDuration::from_secs(5);

        let tf0 = TimeFrame::new(t0, f0);

        {
            let expected_slot = Slot(16);
            let x = now + Duration::from_secs(expected_slot.0 * f0.0);
            assert_eq!(tf0.slot_at(&x), Some(expected_slot));
        }

        let f1 = SlotDuration::from_secs(2);
        let tf1_start = now + Duration::from_secs(10);
        let s0 = tf0.slot_at(&tf1_start);
        assert_eq!(s0, Some(Slot(2)));
        let s0 = s0.unwrap();

        let tf1 = tf0.change_frame(s0, f1);

        assert_eq!(tf1.slot_at(&tf1_start), Some(Slot(2)));
        assert_eq!(tf1.slot_at(&now), None);

        let t2 = tf1_start + Duration::from_secs(10);
        assert_eq!(tf1.slot_at(&t2), Some(Slot(7)));

        assert_eq!(tf0.slot_at(&t2), Some(Slot(4)));
    }
}