use std::{cmp::Ordering, io::Write, sync::Arc};
use crossbeam_channel::Receiver;
use diskit::{diskit_extend::DiskitExt, walkdir::WalkDir, Diskit};
use legacytranslate::MessageHandler;
use rand::random;
use crate::{
api::ApiResponder,
config::{ArcConfig, Config},
csv::Csv,
err::Error,
l10n::{
messages::{LogLevel, Message},
L10n,
},
matcher::BigAction,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Repeat
{
Not,
Once,
Always,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Song
{
pub name: String,
pub num: u32,
pub loud: f32,
}
#[derive(Clone)]
pub(crate) struct L10nHelper
{
l10n: L10n,
err_msg: String,
}
#[derive(Clone)]
pub(crate) struct Songs<D>
where
D: Diskit,
{
pub songs: Vec<Song>,
pub config: Arc<ArcConfig>,
pub l10n_helper: L10nHelper,
pub diskit: D,
}
impl L10nHelper
{
pub fn new(l10n: L10n) -> Self
{
Self {
l10n,
err_msg: l10n.get(Message::SavingStateErr),
}
}
}
impl<D> Drop for Songs<D>
where
D: Diskit,
{
fn drop(&mut self)
{
let s = format!("{}", Csv::from(self));
match Self::save(s.as_bytes(), &self.config, self.diskit.clone())
{
Ok(()) =>
{
self.l10n_helper.l10n.write(Message::StateSaved);
}
Err(e) =>
{
self.l10n_helper.l10n.write(Message::LiteralString(
format!("{}: {:?}", self.l10n_helper.err_msg, e),
LogLevel::Error,
));
self.l10n_helper
.l10n
.write(Message::LiteralString(s, LogLevel::Error));
}
}
}
}
impl<D> Songs<D>
where
D: Diskit,
{
#[must_use]
pub fn total_likelihood(&self) -> u32
{
self.songs.iter().map(|x| x.num).sum()
}
#[allow(clippy::needless_pass_by_value)]
fn save(s: &[u8], config: &Arc<ArcConfig>, diskit: D) -> Result<(), Error>
{
let mut config_file = diskit.create(&config.config_dir.join("songs.csv"))?;
config_file.write_all(s)?;
Ok(())
}
pub fn read(config: Arc<ArcConfig>, l10n: L10n, diskit: D) -> Result<Self, Error>
{
let mut songs = Csv::new(config.config_dir.join("songs.csv"), diskit.clone())?
.get_songs(config, l10n, diskit.clone())
.ok_or(Error::MalformattedSongsCsv)?;
for file in config_dir_handle(&songs.config, diskit)?
{
let file = file?;
if file.file_type().is_dir()
{
continue;
}
let filename = file
.path()
.strip_prefix(&songs.config.conffile.data_dir)?
.to_string_lossy()
.into_owned();
if !songs.songs.iter().any(|x| x.name == filename)
{
l10n.write(Message::NewSongFound(filename.clone()));
songs.songs.push(Song {
name: filename,
num: 10,
loud: 0.1,
});
}
}
Ok(songs)
}
pub fn choose_random<F>(
&mut self,
config: &mut Config,
mut f: F,
api: &ApiResponder,
quit_request: Receiver<()>,
l10n: L10n,
diskit: D,
) -> BigAction
where
F: FnMut(&mut Song, &ApiResponder, Receiver<()>, &mut Config, D) -> BigAction,
{
let total = self.total_likelihood();
l10n.write(Message::TotalPlayingLikelihood(total));
if total == 0
{
l10n.write(Message::NoSongs);
return BigAction::Quit;
}
if config.songlist.len() == config.song_index
{
let mut song_number = (random::<u64>() % total as u64) as _;
for (pos, song) in self.songs.iter_mut().enumerate()
{
if song.num >= song_number
{
config.songlist.push(pos);
break;
}
song_number -= song.num;
}
}
let index = config.songlist[config.song_index];
if config.repeat != Repeat::Not
{
self.songs[index].num = (self.songs[index].num as i64
+ config.arc_config.conffile.repeat_bonus)
.clamp(0, u32::MAX as _)
.try_into()
.unwrap();
match config.arc_config.conffile.repeat_bonus.cmp(&0)
{
Ordering::Greater => l10n.write(Message::PositiveBonus(self.songs[index].num)),
Ordering::Less =>
{
l10n.write(Message::NegativeBonus(self.songs[index].num));
if self.songs[index].num == 0
{
l10n.write(Message::SongNever);
}
}
Ordering::Equal =>
{}
}
}
match config.repeat
{
Repeat::Not => config.song_index += 1,
Repeat::Once =>
{
config.repeat = Repeat::Not;
config.song_index += 1;
}
Repeat::Always =>
{}
}
f(&mut self.songs[index], api, quit_request, config, diskit)
}
}
#[allow(clippy::needless_pass_by_value)]
fn config_dir_handle<D>(config: &Arc<ArcConfig>, diskit: D) -> Result<WalkDir<D>, Error>
where
D: Diskit,
{
let filename = &config.conffile.data_dir;
let dir = diskit.open(filename).or_else(|_| {
diskit.create_dir_all(filename)?;
diskit.open(filename)
})?;
if !dir.metadata()?.is_dir()
{
diskit.trash_delete(filename)?;
diskit.create_dir_all(filename)?;
}
Ok(diskit.walkdir(filename).set_follow_links(true))
}