use crate::{platform::prelude::*, Run, Segment, Time, TimeSpan};
use alloc::borrow::Cow;
use core::result::Result as StdResult;
use serde::Deserialize;
use serde_json::Error as JsonError;
#[derive(Debug, snafu::Snafu)]
#[snafu(context(suffix(false)))]
pub enum Error {
Json {
#[cfg_attr(not(feature = "std"), snafu(source(false)))]
source: JsonError,
},
}
pub type Result<T> = StdResult<T, Error>;
#[derive(Deserialize)]
struct SplitsFormat<'a> {
#[serde(borrow)]
splits: Splits<'a>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Splits<'a> {
#[serde(borrow)]
game: GameInfo<'a>,
start_delay: Option<i64>,
#[serde(borrow)]
segments: Vec<SplitterinoSegment<'a>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum SplitterinoTimingMethod {
Igt,
Rta,
}
#[derive(Deserialize, Default)]
#[serde(default)]
struct GameInfo<'a> {
#[serde(borrow)]
name: Cow<'a, str>,
#[serde(borrow)]
category: Cow<'a, str>,
#[serde(borrow)]
platform: Cow<'a, str>,
#[serde(borrow)]
region: Cow<'a, str>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SplitterinoSegment<'a> {
#[serde(borrow)]
name: Cow<'a, str>,
personal_best: Option<SegmentTime>,
overall_best: Option<SegmentTime>,
#[serde(default)]
skipped: bool,
}
#[derive(Deserialize)]
struct SegmentTime {
igt: DetailedTime,
rta: DetailedTime,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct DetailedTime {
raw_time: u64,
pause_time: u64,
}
fn parse_segment_time(SegmentTime { igt, rta }: SegmentTime) -> [TimeSpan; 2] {
[
TimeSpan::from_milliseconds((rta.raw_time - rta.pause_time) as _),
TimeSpan::from_milliseconds((igt.raw_time - igt.pause_time) as _),
]
}
fn parse_best_segment_time(segment_time: SegmentTime) -> Time {
let [rta, igt] = parse_segment_time(segment_time);
let mut time = Time::new();
if rta != TimeSpan::zero() {
time.real_time = Some(rta);
}
if igt != TimeSpan::zero() {
time.game_time = Some(igt);
}
time
}
fn parse_split_time(
total_rta: &mut TimeSpan,
total_igt: &mut TimeSpan,
segment_time: SegmentTime,
) -> Time {
let [rta, igt] = parse_segment_time(segment_time);
let real_time = if rta != TimeSpan::zero() {
*total_rta += rta;
Some(*total_rta)
} else {
None
};
let game_time = if igt != TimeSpan::zero() {
*total_igt += igt;
Some(*total_igt)
} else {
None
};
Time {
real_time,
game_time,
}
}
pub fn parse(source: &str) -> Result<Run> {
let SplitsFormat::<'_> { splits, .. } =
serde_json::from_str(source).map_err(|source| Error::Json { source })?;
let mut run = Run::new();
run.set_game_name(splits.game.name);
run.set_category_name(splits.game.category);
run.metadata_mut().set_platform_name(splits.game.platform);
run.metadata_mut().set_region_name(splits.game.region);
if let Some(start_delay) = splits.start_delay {
run.set_offset(TimeSpan::from_milliseconds(-start_delay as _));
}
let (mut total_rta, mut total_igt) = (TimeSpan::zero(), TimeSpan::zero());
run.segments_mut()
.extend(splits.segments.into_iter().map(|split| {
let mut segment = Segment::new(split.name);
if !split.skipped {
if let Some(personal_best) = split.personal_best {
segment.set_personal_best_split_time(parse_split_time(
&mut total_rta,
&mut total_igt,
personal_best,
));
}
}
if let Some(overall_best) = split.overall_best {
segment.set_best_segment_time(parse_best_segment_time(overall_best));
}
segment
}));
Ok(run)
}