use thiserror::Error;
use crate::bms::command::ObjId;
use crate::bms::command::channel::mapper::KeyLayoutMapper;
use crate::bms::model::Bms;
#[cfg(feature = "diagnostics")]
use crate::diagnostics::{SimpleSource, ToAriadne, build_report};
#[cfg(feature = "diagnostics")]
use ariadne::{Color, Report, ReportKind};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlayingWarning {
#[error("The `#TOTAL` is not specified.")]
TotalUndefined,
#[error("There is no displayable notes.")]
NoDisplayableNotes,
#[error("There is no playable notes.")]
NoPlayableNotes,
#[error(
"The `#BPM` is not specified. If there are other bpm changes, the first one will be used. If there are no bpm changes, there will be an [`PlayingError::BpmUndefined`]."
)]
StartBpmUndefined,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PlayingError {
#[error("Invalid BPM value '{raw}': {error}")]
InvalidBpm {
raw: String,
error: String,
},
#[error("Invalid STOP value for #{obj_id:02}: '{raw}'")]
InvalidStop {
obj_id: ObjId,
raw: String,
error: String,
},
#[error("Invalid SPEED value for #{obj_id:02}: '{raw}'")]
InvalidSpeed {
obj_id: ObjId,
raw: String,
error: String,
},
#[error("Invalid SCROLL value for #{obj_id:02}: '{raw}'")]
InvalidScroll {
obj_id: ObjId,
raw: String,
error: String,
},
#[error("Invalid SEEK value for #{obj_id:02}: '{raw}'")]
InvalidSeek {
obj_id: ObjId,
raw: String,
error: String,
},
#[error("There is no bpm defined.")]
BpmUndefined,
#[error("There is no notes.")]
NoNotes,
}
#[cfg(feature = "diagnostics")]
impl ToAriadne for PlayingWarning {
fn to_report<'a>(
&self,
src: &SimpleSource<'a>,
) -> Report<'a, (String, std::ops::Range<usize>)> {
build_report(
src,
ReportKind::Warning,
0..0,
"Playing warning",
&self,
Color::Yellow,
)
}
}
#[cfg(feature = "diagnostics")]
impl ToAriadne for PlayingError {
fn to_report<'a>(
&self,
src: &SimpleSource<'a>,
) -> Report<'a, (String, std::ops::Range<usize>)> {
build_report(
src,
ReportKind::Error,
0..0,
"Playing error",
&self,
Color::Red,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[must_use]
pub struct PlayingCheckOutput {
pub playing_warnings: Vec<PlayingWarning>,
pub playing_errors: Vec<PlayingError>,
}
impl Bms {
pub fn check_playing<T: KeyLayoutMapper>(&self) -> PlayingCheckOutput {
let mut playing_warnings = Vec::new();
let mut playing_errors = Vec::new();
if self.judge.total.is_none() {
playing_warnings.push(PlayingWarning::TotalUndefined);
}
if self.bpm.bpm.is_none() {
if self.bpm.bpm_changes.is_empty() {
playing_errors.push(PlayingError::BpmUndefined);
} else {
playing_warnings.push(PlayingWarning::StartBpmUndefined);
}
}
if self.wav.notes.is_empty() {
playing_errors.push(PlayingError::NoNotes);
} else {
let has_displayable = self.wav.notes.displayables::<T>().next().is_some();
if !has_displayable {
playing_warnings.push(PlayingWarning::NoDisplayableNotes);
}
let has_playable = self.wav.notes.playables::<T>().next().is_some();
if !has_playable {
playing_warnings.push(PlayingWarning::NoPlayableNotes);
}
}
PlayingCheckOutput {
playing_warnings,
playing_errors,
}
}
}