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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/// Overwolf Lifetime Stats
pub mod lifetime_stats;

pub use self::lifetime_stats::LifetimeStats;
use std::collections::HashMap;
use url::Url;

/// The error that occurs when an OverwolfResponse is in an error state and the data field is accessed.
/// It optionally contains the reason of failure.
#[derive(Debug)]
pub struct InvalidOverwolfResponseError(pub String);

impl std::error::Error for InvalidOverwolfResponseError {}

impl std::fmt::Display for InvalidOverwolfResponseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "the overwolf response was invalid ({})", self.0)
    }
}

/// A Json Overwolf Response
#[derive(Debug)]
pub enum OverwolfResponse<T> {
    /// A Valid response
    Valid(T),

    /// An Invalid Response
    Invalid(String),
}

impl<'de, T> serde::Deserialize<'de> for OverwolfResponse<T>
where
    T: serde::Deserialize<'de>,
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let mut map = serde_json::Map::deserialize(deserializer)?;

        let success = map
            .remove("success")
            .ok_or_else(|| serde::de::Error::missing_field("success"))
            .map(serde::Deserialize::deserialize)?
            .map_err(serde::de::Error::custom)?;
        let rest = serde_json::Value::Object(map);

        if success {
            T::deserialize(rest)
                .map(OverwolfResponse::Valid)
                .map_err(serde::de::Error::custom)
        } else {
            #[derive(serde::Deserialize)]
            struct ErrorReason {
                reason: String,
            }

            ErrorReason::deserialize(rest)
                .map(|e| OverwolfResponse::Invalid(e.reason))
                .map_err(serde::de::Error::custom)
        }
    }
}

impl<T> OverwolfResponse<T> {
    /// Turn this into a Result.
    pub fn into_result(self) -> Result<T, InvalidOverwolfResponseError> {
        match self {
            Self::Valid(data) => Ok(data),
            Self::Invalid(reason) => Err(InvalidOverwolfResponseError(reason)),
        }
    }

    /// Get the valid member as an Option.
    pub fn get_valid(&self) -> Option<&T> {
        match &self {
            Self::Valid(data) => Some(data),
            Self::Invalid(_) => None,
        }
    }

    /// Take the valid member, consuming this struct. Same function as `get_valid` except it is consuming.
    pub fn take_valid(self) -> Option<T> {
        match self {
            Self::Valid(data) => Some(data),
            Self::Invalid(_) => None,
        }
    }
}

/// An Overwolf Player
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfPlayer {
    /// Player ID
    #[serde(rename = "playerId")]
    pub player_id: String,

    /// Player name
    pub name: String,

    /// Avatar URL
    pub avatar: Url,

    /// Player Level
    pub level: u64,

    /// Probably r6tracker premium
    #[serde(rename = "isPremium")]
    pub is_premium: bool,

    /// Whether this person is a suspected cheater
    #[serde(rename = "suspectedCheater")]
    pub suspected_cheater: bool,

    /// The current season
    #[serde(rename = "currentSeason")]
    pub current_season: u64,

    /// Current season best region stats
    #[serde(rename = "currentSeasonBestRegion")]
    pub current_season_best_region: Option<OverwolfSeason>,

    /// Lifetime Stats
    #[serde(rename = "lifetimeStats")]
    pub lifetime_stats: LifetimeStats,

    /// All seasonal stats
    pub seasons: Vec<OverwolfSeason>,

    /// Operator Stats
    pub operators: Vec<OverwolfOperator>,

    /// Seasonal Operator Stats
    #[serde(rename = "seasonalOperators")]
    pub seasonal_operators: Option<SeasonalOperators>,

    /// Unknown keys
    #[serde(flatten)]
    pub unknown: HashMap<String, serde_json::Value>,
}

impl OverwolfPlayer {
    /// Get the current casual season stats for this user
    pub fn get_current_casual_season(&self) -> Option<&OverwolfSeason> {
        self.seasons
            .iter()
            .find(|season| !season.is_ranked && season.season == self.current_season)
    }

    /// Iterate over ranked seasons
    pub fn iter_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
        self.seasons.iter().filter(|season| season.is_ranked)
    }

    /// Iterate over ranked seasons where the user placed
    pub fn iter_placed_ranked_seasons(&self) -> impl Iterator<Item = &OverwolfSeason> {
        self.iter_ranked_seasons()
            .filter(|season| season.current_rank.rank_tier != 0)
    }

    /// Get the season with the max mmr
    pub fn get_max_season(&self) -> Option<&OverwolfSeason> {
        self.iter_placed_ranked_seasons()
            .max_by_key(|season| season.max_mmr)
    }

    /// Get the lifetime K/D for ranked
    pub fn get_lifetime_ranked_kd(&self) -> Option<f64> {
        let mut length = 0;
        let mut sum = 0.0;
        for season in self.iter_placed_ranked_seasons() {
            sum += season.kd;
            length += 1;
        }

        if length == 0 {
            None
        } else {
            Some(sum / (length as f64))
        }
    }

    /// Get lifetime ranked win %
    pub fn get_lifetime_ranked_win_pct(&self) -> Option<f64> {
        let mut length = 0;
        let mut sum = 0.0f64;
        for season in self.iter_placed_ranked_seasons() {
            sum += season.win_pct;
            length += 1;
        }

        if length == 0 {
            None
        } else {
            Some(sum / (length as f64))
        }
    }
}

/// Seasonal stats. This may also represent hidden casual rankings.
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfSeason {
    /// The rank name
    #[serde(rename = "rankName")]
    pub rank_name: String,

    /// Season image URL
    pub img: Url,

    /// Season #
    pub season: u64,

    /// Season Region
    pub region: String,

    /// The label of the region
    #[serde(rename = "regionLabel")]
    pub region_label: String,

    /// MMR
    pub mmr: u64,

    /// Win Percent
    #[serde(rename = "winPct")]
    pub win_pct: f64,

    /// The # of wins this season
    pub wins: u64,

    /// The K/D this season
    pub kd: f64,

    /// The # of kills this season
    pub kills: u64,

    /// The # of matches this season
    pub matches: u64,

    /// Maybe the change in mmr this season?
    #[serde(rename = "mmrChange")]
    pub mmr_change: Option<i64>,

    /// Whether this season represents a ranked season
    #[serde(rename = "isRanked")]
    pub is_ranked: bool,

    /// The max mmr for this season
    #[serde(rename = "maxMmr")]
    pub max_mmr: u64,

    /// Current Rank Info
    #[serde(rename = "currentRank")]
    pub current_rank: OverwolfRank,

    /// Max Rank Info
    #[serde(rename = "maxRank")]
    pub max_rank: OverwolfRank,

    /// Previous Rank Info
    #[serde(rename = "prevRank")]
    pub prev_rank: Option<OverwolfRank>,

    /// Next Rank Info
    #[serde(rename = "nextRank")]
    pub next_rank: Option<OverwolfRank>,

    /// Unknown keys
    #[serde(flatten)]
    pub unknown: HashMap<String, serde_json::Value>,
}

/// Overwolf Rank Info
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfRank {
    /// Unknown
    #[serde(rename = "rankTier")]
    pub rank_tier: u64,

    /// MMR
    pub mmr: u64,

    /// The icon url for this rank
    #[serde(rename = "rankIcon")]
    pub rank_icon: Url,

    /// The name of this rank
    #[serde(rename = "rankName")]
    pub rank_name: String,

    /// Unknown keys
    #[serde(flatten)]
    pub unknown: HashMap<String, serde_json::Value>,
}

/// An Overwolf Operator
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct OverwolfOperator {
    /// Operator name
    pub name: String,

    /// Operator Image
    pub img: Url,

    /// Whether this operator is attack
    #[serde(rename = "isAttack")]
    pub is_attack: bool,

    /// Whether this operator is this user's top operator
    #[serde(rename = "isTopOperator")]
    pub is_top_operator: bool,

    /// Win %
    #[serde(rename = "winpct")]
    pub win_pct: f64,

    /// The total # of wins with this op
    pub wins: u64,

    /// The K/D with this op
    pub kd: f64,

    /// The total # of kills with this op
    pub kills: u64,

    /// The time played as a user-displayable string
    #[serde(rename = "timePlayedDisplay")]
    pub time_played_display: String,

    /// The time played (in seconds?)
    #[serde(rename = "timePlayed")]
    pub time_played: u64,

    /// Unknown keys
    #[serde(flatten)]
    pub unknown: HashMap<String, serde_json::Value>,
}

/// Seasonal Operator data
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SeasonalOperators {
    /// Operator Stats
    pub operators: Vec<OverwolfOperator>,

    /// Started tracking datetimestamp
    #[serde(rename = "startedTracking")]
    pub started_tracking: String,

    /// Unknown keys
    #[serde(flatten)]
    pub unknown: HashMap<String, serde_json::Value>,
}

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

    const OVERWOLF_PLAYER_1: &str = include_str!("../../test_data/overwolf_player_1.json");
    const OVERWOLF_PLAYER_2: &str = include_str!("../../test_data/overwolf_player_2.json");
    const INVALID_OVERWOLF_RESPONSE: &str =
        include_str!("../../test_data/invalid_overwolf_response.json");
    const SMACK_ASH_OVERWOLF: &str = include_str!("../../test_data/smack_ash_overwolf.json");

    #[test]
    fn parse_overwolf_player_1() {
        let res: OverwolfResponse<OverwolfPlayer> =
            serde_json::from_str(OVERWOLF_PLAYER_1).unwrap();
        dbg!(res.take_valid().unwrap());
    }

    #[test]
    fn parse_overwolf_player_2() {
        let res: OverwolfResponse<OverwolfPlayer> =
            serde_json::from_str(OVERWOLF_PLAYER_2).unwrap();
        let valid = res.take_valid().unwrap();
        dbg!(&valid);
        dbg!(&valid.get_max_season());
    }

    #[test]
    fn parse_smack_ash_overwolf() {
        let res: OverwolfResponse<OverwolfPlayer> =
            serde_json::from_str(SMACK_ASH_OVERWOLF).expect("failed to parse");
        let valid = res.take_valid().expect("data is invalid");
        dbg!(&valid);
        dbg!(&valid.get_max_season());
    }

    #[test]
    fn parse_invalid_overwolf() {
        let res: OverwolfResponse<serde_json::Value> =
            serde_json::from_str(INVALID_OVERWOLF_RESPONSE).unwrap();
        dbg!(res);
    }
}