use crate::{error::Result, timer::Run};
use ron::{
de::from_str,
ser::{to_string_pretty, to_writer_pretty, PrettyConfig},
};
use serde::Deserialize;
use std::{
io::{BufRead, BufReader, Read, Write},
str::FromStr,
};
#[derive(Deserialize)]
struct LegacyRun {
game_title: String,
category: String,
offset: Option<u128>,
pb: u128,
splits: Vec<String>,
pb_times: Vec<u128>,
gold_times: Vec<u128>,
}
#[derive(Deserialize)]
struct RunV1 {
game_title: String,
category: String,
offset: Option<u128>,
pb: u128,
splits: Vec<String>,
pb_times: Vec<u128>,
gold_times: Vec<u128>,
sum_times: Vec<(u128, u128)>,
}
impl From<LegacyRun> for Run {
fn from(r: LegacyRun) -> Run {
Run::new(
r.category,
r.game_title,
r.offset.into(),
r.pb.into(),
&r.splits,
&r.pb_times.iter().map(|&t| t.into()).collect::<Vec<_>>(),
&r.gold_times.iter().map(|&t| t.into()).collect::<Vec<_>>(),
&r.pb_times
.iter()
.map(|&t| (1u128, t.into()))
.collect::<Vec<_>>(),
)
}
}
impl From<RunV1> for Run {
fn from(r: RunV1) -> Run {
Run::new(
r.category,
r.game_title,
r.offset.into(),
r.pb.into(),
&r.splits,
&r.pb_times.iter().map(|&t| t.into()).collect::<Vec<_>>(),
&r.gold_times.iter().map(|&t| t.into()).collect::<Vec<_>>(),
&r.sum_times
.iter()
.map(|&(n, t)| (n, t.into()))
.collect::<Vec<_>>(),
)
}
}
impl Run {
pub fn from_reader_msf(msf: impl Read) -> Result<Self> {
let r = BufReader::new(msf);
Self::parse_impl(r.lines().map_while(|r| r.ok()))
}
pub fn from_str_msf(msf: &str) -> Result<Self> {
Self::parse_impl(msf.lines())
}
pub fn to_writer(&self, mut writer: impl Write) -> Result<()> {
let run = super::sanify_run(self);
writer.write_all(b"version 2\n")?;
to_writer_pretty(&mut writer, &run, PrettyConfig::new())?;
Ok(())
}
pub fn to_string(&self) -> Result<String> {
let run = super::sanify_run(self);
let mut s = String::from("version 2\n");
s.push_str(&to_string_pretty(&run, PrettyConfig::new())?);
Ok(s)
}
fn parse_impl<S: AsRef<str>>(mut lines: impl Iterator<Item = S>) -> Result<Self> {
let ver_info = String::from_str(lines.next().ok_or("Input was empty.")?.as_ref()).unwrap();
let version: u32 = match ver_info.rsplit_once(' ') {
Some(num) => num.1.parse::<u32>().unwrap_or(0),
None => 0,
};
let data = {
let mut s = String::new();
if version == 0 {
s.push_str(&ver_info);
}
for line in lines {
s.push_str(line.as_ref());
s.push('\n');
}
s
};
let run = match version {
1 => from_str::<RunV1>(&data)?.into(),
2 => from_str::<Run>(&data)?,
_ => from_str::<LegacyRun>(&data)?.into(),
};
Ok(super::sanify_run(&run))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::timer::TimeType::{self, *};
const V2_RUN: &str = "version 2\n
(
game_title: \"test\",
category: \"test\",
offset: Time(200),
pb: Time(1234),
splits: [\"test\"],
pb_times: [Skipped(1234)],
gold_times: [Time(1234)],
sum_times: [(2, None)],
)";
#[test]
fn test_parse_v2() {
assert_eq!(
Run::from_str_msf(V2_RUN).unwrap(),
Run::new(
"test",
"test",
Time(200),
Time(1234),
&["test".into()],
&[Skipped(1234)],
&[Time(1234)],
&[(2, TimeType::None)]
)
);
}
const V1_RUN: &str = "version 1\n
(
game_title: \"test\",
category: \"test\",
offset: Some(200),
pb: 1234,
splits: [\"test\"],
pb_times: [1234],
gold_times: [1234],
sum_times: [(2, 2480)],
)";
#[test]
fn test_parse_v1() {
assert_eq!(
Run::from_str_msf(V1_RUN).unwrap(),
Run::new(
"test",
"test",
Time(200),
Time(1234),
&["test".into()],
&[Time(1234)],
&[Time(1234)],
&[(2, Time(2480))]
)
);
}
const LEGACY_RUN: &str = "(
game_title: \"test\",
category: \"test\",
offset: Some(200),
pb: 1234,
splits: [\"test\"],
pb_times: [1234],
gold_times: [1234],
)";
#[test]
fn test_parse_legacy() {
assert_eq!(
Run::from_str_msf(LEGACY_RUN).unwrap(),
Run::new(
"test",
"test",
Time(200),
Time(1234),
&["test".into()],
&[Time(1234)],
&[Time(1234)],
&[(1, Time(1234))]
)
);
}
const INSANE_RUN: &str = "version 1\n
(
game_title: \"test\",
category: \"test\",
offset: Some(200),
pb: 1234,
splits: [\"test\", \"test2\"],
pb_times: [1234],
gold_times: [1234],
sum_times: [(2, 1234), (1, 1243), (5, 420)],
)";
#[test]
fn test_sanity_check() {
let run = Run::from_str_msf(INSANE_RUN).unwrap();
let run = crate::parse::sanify_run(&run);
assert_eq!(
run,
Run::new(
"test",
"test",
Time(200),
Time(1234),
&["test".into(), "test2".into()],
&[Time(1234), TimeType::None],
&[Time(1234), TimeType::None],
&[(2, Time(1234)), (1, Time(1243))]
)
);
}
}