use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
use num::FromPrimitive;
use crate::errors::{ParseError, ParseResult};
use crate::hitsounds::{Additions, SampleInfo, SampleSet};
use crate::math::Point;
use crate::spline::Spline;
use crate::timing::Millis;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum SliderSplineKind {
Linear,
Bezier,
Catmull,
Perfect,
}
impl fmt::Display for SliderSplineKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
SliderSplineKind::Linear => 'L',
SliderSplineKind::Bezier => 'B',
SliderSplineKind::Catmull => 'C',
SliderSplineKind::Perfect => 'P',
}
)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SliderInfo {
pub kind: SliderSplineKind,
pub control_points: Vec<Point<i32>>,
pub num_repeats: u32,
pub pixel_length: f64,
pub edge_additions: Vec<Additions>,
pub edge_samplesets: Vec<(SampleSet, SampleSet)>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpinnerInfo {
pub end_time: Millis,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum HitObjectKind {
Circle,
Slider(SliderInfo),
Spinner(SpinnerInfo),
}
impl HitObjectKind {
pub fn is_circle(&self) -> bool {
matches!(self, HitObjectKind::Circle)
}
pub fn is_slider(&self) -> bool {
matches!(self, HitObjectKind::Slider(_))
}
pub fn is_spinner(&self) -> bool {
matches!(self, HitObjectKind::Spinner(_))
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HitObject {
pub pos: Point<i32>,
pub start_time: Millis,
pub kind: HitObjectKind,
pub new_combo: bool,
pub skip_color: i32,
pub additions: Additions,
pub sample_info: SampleInfo,
}
impl HitObject {
pub fn end_pos(&self) -> Point<f64> {
match &self.kind {
HitObjectKind::Slider(info) => {
if info.num_repeats % 2 == 0 {
self.pos.to_float().expect("f64 converts to float")
} else {
let mut control_points = vec![self.pos];
control_points.extend(&info.control_points);
let spline = Spline::from_control(
info.kind,
control_points.as_ref(),
Some(info.pixel_length),
);
spline.end_point()
}
}
_ => self.pos.to_float().expect("f64 converts to float"),
}
}
}
impl Ord for HitObject {
fn cmp(&self, other: &Self) -> Ordering {
self.start_time.cmp(&other.start_time)
}
}
impl PartialOrd for HitObject {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for HitObject {}
impl PartialEq for HitObject {
fn eq(&self, other: &Self) -> bool {
self.start_time == other.start_time
}
}
impl FromStr for HitObject {
type Err = ParseError;
fn from_str(input: &str) -> Result<HitObject, Self::Err> {
let input = input.trim_end_matches(',');
let parts = input.split(',').collect::<Vec<_>>();
let x = parts[0].parse::<i32>()?;
let y = parts[1].parse::<i32>()?;
let timestamp = parts[2].parse::<i32>()?;
let obj_type = parts[3].parse::<i32>()?;
let additions_bits = parts[4].parse::<u32>()?;
let additions = Additions::from_bits(additions_bits)
.ok_or(ParseError::InvalidAdditions(additions_bits))?;
let start_time = Millis(timestamp);
let skip_color = (obj_type >> 4) & 0b111;
let new_combo = (obj_type & 4) == 4;
let sample_info;
let kind = match obj_type {
o if (o & 1) == 1 => {
sample_info = if let Some(s) = parts.get(5) {
SampleInfo::from_str(s)?
} else {
SampleInfo::default()
};
HitObjectKind::Circle
}
o if (o & 2) == 2 => {
let mut ctl_parts = parts[5].split('|').collect::<Vec<_>>();
let num_repeats = parts[6].parse::<u32>()?;
let slider_type = ctl_parts.remove(0);
let pixel_length = parts[7].parse::<f64>()?;
let edge_additions = if parts.len() > 8 {
parts[8]
.split('|')
.map(|n| {
n.parse::<u32>().map_err(ParseError::from).and_then(|b| {
Additions::from_bits(b).ok_or(ParseError::InvalidAdditions(b))
})
})
.collect::<Result<Vec<_>, _>>()?
} else {
vec![Additions::empty()]
};
let edge_samplesets = if parts.len() > 9 {
parts[9]
.split('|')
.map(|s| {
let s2 = s.split(':').collect::<Vec<_>>();
let normal = s2[0].parse::<u32>()?;
let additions = s2[1].parse::<u32>()?;
Ok((
SampleSet::from_u32(normal).unwrap(),
SampleSet::from_u32(additions).unwrap(),
))
})
.collect::<ParseResult<Vec<_>>>()?
} else {
vec![(SampleSet::Default, SampleSet::Default)]
};
sample_info = if parts.len() > 10 {
SampleInfo::from_str(parts[10])?
} else {
SampleInfo::default()
};
HitObjectKind::Slider(SliderInfo {
num_repeats,
kind: match slider_type {
"L" => SliderSplineKind::Linear,
"B" => SliderSplineKind::Bezier,
"C" => SliderSplineKind::Catmull,
"P" => SliderSplineKind::Perfect,
s => return Err(ParseError::InvalidSliderType(s.to_owned())),
},
control_points: ctl_parts
.into_iter()
.map(|s| {
let p = s.split(':').collect::<Vec<_>>();
Point::new(
p[0].parse::<i32>().unwrap(),
p[1].parse::<i32>().unwrap(),
)
})
.collect(),
pixel_length,
edge_additions,
edge_samplesets,
})
}
o if (o & 8) == 8 => {
let end_time = parts[5].parse::<i32>()?;
sample_info = if let Some(s) = parts.get(6) {
SampleInfo::from_str(s)?
} else {
SampleInfo::default()
};
HitObjectKind::Spinner(SpinnerInfo {
end_time: Millis(end_time),
})
}
o => {
return Err(ParseError::InvalidObjectType(o));
}
};
let hit_obj = HitObject {
kind,
pos: Point::new(x, y),
new_combo,
additions,
skip_color,
start_time,
sample_info,
};
Ok(hit_obj)
}
}
impl fmt::Display for HitObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{},{},{}", self.pos.x, self.pos.y, self.start_time.0)?;
let obj_type = match self.kind {
HitObjectKind::Circle => 1,
HitObjectKind::Slider { .. } => 2,
HitObjectKind::Spinner { .. } => 8,
} | if self.new_combo { 4 } else { 0 }
| self.skip_color;
write!(f, ",{}", obj_type)?;
write!(f, ",{}", self.additions.bits())?;
match &self.kind {
HitObjectKind::Circle => {
}
HitObjectKind::Slider(info) => {
write!(f, ",{}", info.kind)?;
for point in info.control_points.iter() {
write!(f, "|{}:{}", point.x, point.y)?;
}
write!(f, ",{}", info.num_repeats)?;
write!(f, ",{}", info.pixel_length)?;
write!(f, ",")?;
for (i, additions) in info.edge_additions.iter().enumerate() {
if i > 0 {
write!(f, "|")?;
}
write!(f, "{}", additions.bits())?;
}
write!(f, ",")?;
for (i, (normal_set, addition_set)) in
info.edge_samplesets.iter().enumerate()
{
if i > 0 {
write!(f, "|")?;
}
write!(f, "{}:{}", *normal_set as u8, *addition_set as u8)?;
}
}
HitObjectKind::Spinner(info) => {
write!(f, ",{}", info.end_time.0)?;
}
}
write!(f, ",{}", self.sample_info)?;
Ok(())
}
}