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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//! Provides different helper functions.

use crate::comparison::best_segments;
use crate::settings::SemanticColor;
use crate::{Run, Segment, TimeSpan, Timer, TimerPhase, TimingMethod};

/// Gets the last non-live delta in the run starting from `segment_index`.
///
/// - `run`: The current run.
/// - `segment_index`: The split number to start checking deltas from.
/// - `comparison`: The comparison that you are comparing with.
/// - `method`: The timing method that you are using.
///
/// Returns the last non-live delta or None if there have been no deltas yet.
pub fn last_delta(
    run: &Run,
    segment_index: usize,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    find_previous_non_empty_split_and_comparison_time(
        &run.segments()[..=segment_index],
        comparison,
        method,
    )
    .map(|(split_time, comparison_time)| split_time - comparison_time)
}

fn find_previous_non_empty_segment<F, T>(segments: &[Segment], check_segment: F) -> Option<T>
where
    F: FnMut(&Segment) -> Option<T>,
{
    segments.iter().rev().find_map(check_segment)
}

fn find_previous_non_empty_split_time(
    segments: &[Segment],
    method: TimingMethod,
) -> Option<TimeSpan> {
    find_previous_non_empty_segment(segments, |s| s.split_time()[method])
}

fn find_previous_non_empty_comparison_time(
    segments: &[Segment],
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    find_previous_non_empty_segment(segments, |s| s.comparison(comparison)[method])
}

fn find_previous_non_empty_split_and_comparison_time(
    segments: &[Segment],
    comparison: &str,
    method: TimingMethod,
) -> Option<(TimeSpan, TimeSpan)> {
    find_previous_non_empty_segment(segments, |s| {
        catch! {
            (s.split_time()[method]?, s.comparison(comparison)[method]?)
        }
    })
}

/// Calculates the comparison's segment time of the segment with the timing
/// method specified. This is not calculating the current attempt's segment
/// times.
pub fn comparison_segment_time(
    run: &Run,
    segment_index: usize,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    let current_comparison_time = run.segment(segment_index).comparison(comparison)[method]?;

    let previous_comparison_time = find_previous_non_empty_comparison_time(
        &run.segments()[..segment_index],
        comparison,
        method,
    )
    .unwrap_or_default();

    Some(current_comparison_time - previous_comparison_time)
}

fn segment_delta(
    run: &Run,
    segment_index: usize,
    current_time: TimeSpan,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    let segment_index_comparison = run.segment(segment_index).comparison(comparison)[method]?;

    Some(
        find_previous_non_empty_split_and_comparison_time(
            &run.segments()[..segment_index],
            comparison,
            method,
        )
        .map(|(split_time, comparison_time)| {
            (current_time - segment_index_comparison) - (split_time - comparison_time)
        })
        .unwrap_or_else(|| current_time - segment_index_comparison),
    )
}

fn segment_time(
    run: &Run,
    segment_index: usize,
    current_time: TimeSpan,
    method: TimingMethod,
) -> TimeSpan {
    find_previous_non_empty_split_time(&run.segments()[..segment_index], method)
        .map(|split_time| current_time - split_time)
        .unwrap_or(current_time)
}

/// Gets the length of the last segment that leads up to a certain split.
///
/// - `timer`: The current timer.
/// - `segment_index`: The index of the split that represents the end of the
///   segment.
/// - `method`: The timing method that you are using.
///
/// Returns the length of the segment leading up to `segment_index`, returning
/// None if the split is not completed yet.
pub fn previous_segment_time(
    timer: &Timer,
    segment_index: usize,
    method: TimingMethod,
) -> Option<TimeSpan> {
    segment_time(
        timer.run(),
        segment_index,
        timer.run().segment(segment_index).split_time()[method]?,
        method,
    )
    .into()
}

/// Gets the length of the last segment that leads up to a certain split, using
/// the live segment time if the split is not completed yet.
///
/// - `timer`: The current timer.
/// - `segment_index`: The index of the split that represents the end of the
///   segment.
/// - `method`: The timing method that you are using.
///
/// Returns the length of the segment leading up to `segment_index`, returning
/// the live segment time if the split is not completed yet.
pub fn live_segment_time(
    timer: &Timer,
    segment_index: usize,
    method: TimingMethod,
) -> Option<TimeSpan> {
    segment_time(
        timer.run(),
        segment_index,
        timer.current_time()[method]?,
        method,
    )
    .into()
}

/// Gets the amount of time lost or gained on a certain split.
///
/// - `timer`: The current timer.
/// - `segment_index`: The index of the split for which the delta is calculated.
/// - `comparison`: The comparison that you are comparing with.
/// - `method`: The timing method that you are using.
///
/// Returns the segment delta for a certain split, returning None if the split
/// is not completed yet.
pub fn previous_segment_delta(
    timer: &Timer,
    segment_index: usize,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    segment_delta(
        timer.run(),
        segment_index,
        timer.run().segment(segment_index).split_time()[method]?,
        comparison,
        method,
    )
}

/// Gets the amount of time lost or gained on a certain split, using the live
/// segment delta if the split is not completed yet.
///
/// - `timer`: The current timer.
/// - `segment_index`: The index of the split for which the delta is calculated.
/// - `comparison`: The comparison that you are comparing with.
/// - `method`: The timing method that you are using.
///
/// Returns the segment delta for a certain split, returning the live segment
/// delta if the split is not completed yet.
pub fn live_segment_delta(
    timer: &Timer,
    segment_index: usize,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    segment_delta(
        timer.run(),
        segment_index,
        timer.current_time()[method]?,
        comparison,
        method,
    )
}

/// Checks whether the live segment should now be shown.
///
/// - `timer`: The current timer.
/// - `split_delta`: Specifies whether to return a split delta
///    rather than a segment delta and to start showing the live
///    segment once you are behind.
/// - `comparison`: The comparison that you are comparing with.
/// - `method`: The timing method that you are using.
///
/// Returns the current live delta.
pub fn check_live_delta(
    timer: &Timer,
    split_delta: bool,
    comparison: &str,
    method: TimingMethod,
) -> Option<TimeSpan> {
    if timer.current_phase() == TimerPhase::Running || timer.current_phase() == TimerPhase::Paused {
        let use_best_segment = true; // FIXME: Make this a parameter
        let current_split = timer
            .current_split()
            .unwrap()
            .comparison_timing_method(comparison, method);
        let current_time = timer.current_time()[method];
        let segment_index = timer.current_split_index().unwrap();
        let current_segment = live_segment_time(timer, segment_index, method);
        let best_segment = timer.run().segment(segment_index).best_segment_time()[method];
        let best_segment_delta =
            live_segment_delta(timer, segment_index, best_segments::NAME, method);
        let comparison_delta = live_segment_delta(timer, segment_index, comparison, method);

        if split_delta && current_time > current_split
            || use_best_segment
                && catch! { current_segment? > best_segment? }.unwrap_or(false)
                && best_segment_delta.map_or(false, |d| d > TimeSpan::zero())
            || comparison_delta.map_or(false, |d| d > TimeSpan::zero())
        {
            if split_delta {
                return catch! { current_time? - current_split? };
            } else {
                return comparison_delta;
            }
        }
    }
    None
}

/// Chooses a split color from the Layout Settings based on the current run.
///
/// - `timer`: The current timer.
/// - `time_difference`: The delta that you want to find a color for.
/// - `segment_index`: The split number that is associated with this delta.
/// - `show_segment_deltas`: Can show ahead gaining and behind losing colors if
///    true.
/// - `show_best_segments`: Can show the best segment color if true.
/// - `comparison`: The comparison that you are comparing this delta to.
/// - `method`: The timing method of this delta.
///
/// Returns the chosen color.
pub fn split_color(
    timer: &Timer,
    time_difference: Option<TimeSpan>,
    segment_index: usize,
    show_segment_deltas: bool,
    show_best_segments: bool,
    comparison: &str,
    method: TimingMethod,
) -> SemanticColor {
    let use_best_segment = true; // FIXME: Make this a parameter

    if show_best_segments && use_best_segment && check_best_segment(timer, segment_index, method) {
        SemanticColor::BestSegment
    } else if let Some(time_difference) = time_difference {
        let last_delta = segment_index
            .checked_sub(1)
            .and_then(|n| last_delta(timer.run(), n, comparison, method));
        if time_difference < TimeSpan::zero() {
            if show_segment_deltas && last_delta.map_or(false, |d| time_difference > d) {
                SemanticColor::AheadLosingTime
            } else {
                SemanticColor::AheadGainingTime
            }
        } else if show_segment_deltas && last_delta.map_or(false, |d| time_difference < d) {
            SemanticColor::BehindGainingTime
        } else {
            SemanticColor::BehindLosingTime
        }
    } else {
        SemanticColor::Default
    }
}

/// Calculates whether or not the Split Times for the indicated split qualify as
/// a Best Segment.
///
/// - `timer`: The current timer.
/// - `segment_index`: The split to check.
/// - `method`: The timing method to use.
///
/// Returns whether or not the indicated split is a Best Segment.
pub fn check_best_segment(timer: &Timer, segment_index: usize, method: TimingMethod) -> bool {
    if timer.run().segment(segment_index).split_time()[method].is_none() {
        return false;
    }

    let delta = previous_segment_delta(timer, segment_index, best_segments::NAME, method);
    let current_segment = previous_segment_time(timer, segment_index, method);
    let best_segment = timer.run().segment(segment_index).best_segment_time()[method];
    best_segment.map_or(true, |b| {
        current_segment.map_or(false, |c| c < b) || delta.map_or(false, |d| d < TimeSpan::zero())
    })
}