1
// This file is part of hnefatafl-copenhagen.
2
//
3
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU Affero General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU Affero General Public License for more details.
12
//
13
// You should have received a copy of the GNU Affero General Public License
14
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
//
16
// SPDX-License-Identifier: AGPL-3.0-or-later
17
// SPDX-FileCopyrightText: 2025 David Campbell <david@hnefatafl.org>
18

            
19
use std::fmt;
20

            
21
use anyhow::Context;
22
use serde::{Deserialize, Serialize};
23

            
24
pub const DAY: i64 = 24 * 60 * 60 * 1_000;
25
pub const HOUR: i64 = 60 * 60 * 1_000;
26
pub const MINUTE: i64 = 60 * 1_000;
27
pub const SECOND: i64 = 1_000;
28

            
29
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
30
pub struct Time {
31
    pub add_seconds: i64,
32
    pub milliseconds_left: i64,
33
}
34

            
35
impl Time {
36
    #[must_use]
37
    pub fn time_left(&self) -> String {
38
        time_left(self.milliseconds_left)
39
    }
40
}
41

            
42
impl Default for Time {
43
49410
    fn default() -> Self {
44
49410
        Self {
45
49410
            add_seconds: 10,
46
49410
            milliseconds_left: 15 * MINUTE,
47
49410
        }
48
49410
    }
49
}
50

            
51
impl fmt::Display for Time {
52
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53
        let days = self.milliseconds_left / DAY;
54
        let hours = (self.milliseconds_left % DAY) / HOUR;
55
        let minutes = (self.milliseconds_left % HOUR) / MINUTE;
56
        let seconds = (self.milliseconds_left % MINUTE) / SECOND;
57

            
58
        let add_hours = self.add_seconds / (60 * 60);
59
        let add_minutes = (self.add_seconds % (60 * 60)) / 60;
60
        let add_seconds = self.add_seconds % 60;
61

            
62
        if days == 0 {
63
            write!(
64
                f,
65
                "{hours:02}:{minutes:02}:{seconds:02} | {add_hours:02}:{add_minutes:02}:{add_seconds:02}"
66
            )
67
        } else {
68
            write!(
69
                f,
70
                "{days} {hours:02}:{minutes:02}:{seconds:02} | {add_hours:02}:{add_minutes:02}:{add_seconds:02}"
71
            )
72
        }
73
    }
74
}
75

            
76
#[derive(Clone, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
77
pub enum TimeSettings {
78
    Timed(Time),
79
    UnTimed,
80
}
81

            
82
impl TimeSettings {
83
    #[must_use]
84
    pub fn time_left(&self) -> String {
85
        match self {
86
            Self::Timed(time) => time.time_left(),
87
            Self::UnTimed => "-".to_string(),
88
        }
89
    }
90
}
91

            
92
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
93
pub struct TimeLeft {
94
    pub milliseconds_left: i64,
95
}
96

            
97
impl fmt::Display for TimeLeft {
98
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99
        write!(f, "{}", time_left(self.milliseconds_left))
100
    }
101
}
102

            
103
impl From<Time> for TimeLeft {
104
    fn from(time: Time) -> Self {
105
        TimeLeft {
106
            milliseconds_left: time.milliseconds_left,
107
        }
108
    }
109
}
110

            
111
impl TryFrom<TimeSettings> for TimeLeft {
112
    type Error = anyhow::Error;
113

            
114
31556
    fn try_from(time: TimeSettings) -> Result<TimeLeft, anyhow::Error> {
115
31556
        match time {
116
31556
            TimeSettings::Timed(time) => Ok(TimeLeft {
117
31556
                milliseconds_left: time.milliseconds_left,
118
31556
            }),
119
            TimeSettings::UnTimed => Err(anyhow::Error::msg("the time settings are un-timed")),
120
        }
121
31556
    }
122
}
123

            
124
impl Default for TimeSettings {
125
49410
    fn default() -> Self {
126
49410
        Self::Timed(Time {
127
49410
            ..Default::default()
128
49410
        })
129
49410
    }
130
}
131

            
132
impl fmt::Debug for TimeSettings {
133
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134
        match self {
135
            Self::Timed(time) => {
136
                write!(f, "fischer {} {}", time.milliseconds_left, time.add_seconds)
137
            }
138
            Self::UnTimed => write!(f, "un-timed _ _"),
139
        }
140
    }
141
}
142

            
143
impl fmt::Display for TimeSettings {
144
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145
        match self {
146
            Self::Timed(time) => write!(f, "{time}"),
147
            Self::UnTimed => write!(f, "∞"),
148
        }
149
    }
150
}
151

            
152
impl From<TimeSettings> for bool {
153
    fn from(time_settings: TimeSettings) -> Self {
154
        match time_settings {
155
            TimeSettings::Timed(_) => true,
156
            TimeSettings::UnTimed => false,
157
        }
158
    }
159
}
160

            
161
impl TryFrom<Vec<&str>> for TimeSettings {
162
    type Error = anyhow::Error;
163

            
164
    fn try_from(args: Vec<&str>) -> anyhow::Result<Self> {
165
        let err_msg = "expected: 'time_settings un-timed' or 'time_settings fischer MILLISECONDS ADD_SECONDS'";
166

            
167
        if Some("un-timed").as_ref() == args.get(1) {
168
            return Ok(Self::UnTimed);
169
        }
170

            
171
        if args.len() < 4 {
172
            return Err(anyhow::Error::msg(err_msg));
173
        }
174

            
175
        if "fischer" == args[1] {
176
            let arg_2 = args[2]
177
                .parse::<i64>()
178
                .context("time_settings: arg 2 is not an integer")?;
179

            
180
            let arg_3 = args[3]
181
                .parse::<i64>()
182
                .context("time_settings: arg 3 is not an integer")?;
183

            
184
            Ok(Self::Timed(Time {
185
                add_seconds: arg_3,
186
                milliseconds_left: arg_2,
187
            }))
188
        } else {
189
            Err(anyhow::Error::msg(err_msg))
190
        }
191
    }
192
}
193

            
194
fn time_left(milliseconds_left: i64) -> String {
195
    let days = milliseconds_left / DAY;
196
    let hours = (milliseconds_left % DAY) / HOUR;
197
    let minutes = (milliseconds_left % HOUR) / MINUTE;
198
    let seconds = (milliseconds_left % MINUTE) / SECOND;
199

            
200
    if days == 0 {
201
        if hours == 0 {
202
            if minutes == 0 {
203
                format!("{seconds:02}")
204
            } else {
205
                format!("{minutes:02}:{seconds:02}")
206
            }
207
        } else {
208
            format!("{hours:02}:{minutes:02}:{seconds:02}")
209
        }
210
    } else {
211
        format!("{days} {hours:02}:{minutes:02}:{seconds:02}")
212
    }
213
}
214

            
215
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
216
pub enum TimeEnum {
217
    Classical,
218
    Infinity,
219
    Long,
220
    #[default]
221
    Rapid,
222
    VeryLong,
223
}
224

            
225
impl From<TimeEnum> for TimeSettings {
226
    fn from(time_enum: TimeEnum) -> TimeSettings {
227
        match time_enum {
228
            TimeEnum::Classical => TimeSettings::Timed(Time {
229
                add_seconds: 20,
230
                milliseconds_left: 30 * MINUTE,
231
            }),
232
            TimeEnum::Rapid => TimeSettings::Timed(Time {
233
                add_seconds: 10,
234
                milliseconds_left: 15 * MINUTE,
235
            }),
236

            
237
            TimeEnum::Long => TimeSettings::Timed(Time {
238
                add_seconds: 60 * 60 * 6,
239
                milliseconds_left: 3 * DAY,
240
            }),
241
            TimeEnum::VeryLong => TimeSettings::Timed(Time {
242
                add_seconds: 60 * 60 * 15,
243
                milliseconds_left: 12 * 15 * HOUR,
244
            }),
245
            TimeEnum::Infinity => TimeSettings::UnTimed,
246
        }
247
    }
248
}
249

            
250
impl fmt::Display for TimeEnum {
251
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252
        match self {
253
            Self::Classical => write!(f, "00:30:00 | 00:00:20"),
254
            Self::Infinity => write!(f, "∞"),
255
            Self::Long => write!(f, "3 00:00:00 | 6:00:00"),
256
            Self::Rapid => write!(f, "00:15:00 | 00:00:10 "),
257
            Self::VeryLong => write!(f, "7 12:00:00 | 15:00:00 "),
258
        }
259
    }
260
}