use std::{
collections::HashMap,
fs::{self, File},
io,
path::{Path, PathBuf},
vec,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Error;
use thiserror::Error;
use crate::{CardInner, Card, CardIdx};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Deck {
pub path: PathBuf,
pub cards: Vec<Card>,
}
impl Deck {
pub fn new_from_path(path: &Path) -> Result<Self, DeckError> {
if let Some(cards) = Deck::gather_cards(path.to_path_buf())? {
Ok(Deck {
path: path.to_path_buf(),
cards,
})
} else {
Err(DeckError::ContentError { path: path.to_path_buf() })
}
}
fn gather_cards(path: PathBuf) -> Result<Option<Vec<Card>>, DeckError> {
if path.is_dir() {
let mut cards = vec![];
for ancestor in fs::read_dir(path)? {
let ancestor_path = ancestor?.path();
if let Some(mut c) = Deck::gather_cards(ancestor_path)? {
cards.append(&mut c);
}
}
Ok(Some(cards))
} else {
let mut f = File::open(&path)?;
match serde_json::from_reader::<&mut File, Vec<CardInner>>(&mut f) {
Ok(f) => {
Ok(Some(f.into_iter()
.map(|x| Card {
inner: x,
path: path.clone(),
score: 0,
due_date: None,
})
.collect()))
},
Err(e) => {
if path.file_stem().unwrap().to_str().unwrap().ends_with(".score") {
Ok(None)
} else {
Err(DeckError::ParseError { path, source: e })
}
},
}
}
}
pub fn dues(&self) -> HashMap<CardIdx, Option<DateTime<Utc>>> {
self.cards
.iter()
.enumerate()
.map(|x| (x.0 as CardIdx, x.1.due_date))
.collect()
}
pub fn scores(&self) -> HashMap<CardIdx, usize> {
self.cards
.iter()
.enumerate()
.map(|x| (x.0 as CardIdx, x.1.score))
.collect()
}
}
impl From<Vec<CardInner>> for Deck {
fn from(cards: Vec<CardInner>) -> Self {
Deck {
cards: cards
.iter()
.map(|x| <CardInner as std::convert::Into<Card>>::into(x.clone()))
.collect(),
..Default::default()
}
}
}
impl From<Deck> for Vec<CardInner> {
fn from(value: Deck) -> Self {
let mut cards = vec![];
for card in value.cards {
cards.push(card.inner);
}
cards
}
}
impl From<Vec<Card>> for Deck {
fn from(cards: Vec<Card>) -> Self {
Deck {
cards,
..Default::default()
}
}
}
#[derive(Debug, Error)]
pub enum DeckError{
#[error("IO Error: {source}")]
IoError {
#[from] #[source] source: io::Error
},
#[error("Error parsing file {path}: {source}")]
ParseError {
path: PathBuf,
#[source]
source: Error,
},
#[error("Directory {path} contains only score files. How did you acomplish this?")]
ContentError {
path: PathBuf
}
}