1use crate::card;
2use crate::locale;
3use crate::pokemon;
4use crate::series;
5use crate::set;
6use crate::{Card, Locale, Map, Pokemon, Series, Set};
7
8use serde::de::DeserializeOwned;
9use serde::{Deserialize, Serialize};
10use std::collections::{BTreeMap, BTreeSet};
11use std::fmt;
12use std::path::Path;
13
14#[derive(Clone)]
15pub struct Database {
16 pub pokemon: Map<pokemon::Id, Pokemon>,
17 pub series: Map<series::Id, Series>,
18 pub sets: Map<set::Id, Set>,
19 pub cards: Map<card::Id, Card>,
20}
21
22impl Database {
23 pub async fn load() -> Result<Self, anywho::Error> {
24 use tokio::task;
25
26 Ok(task::spawn_blocking(|| {
27 let pokemon = load_pokemon();
28 let series: Vec<Series> = decompress(include_bytes!("../data/series.ron.gz"));
29 let sets: Vec<Set> = decompress(include_bytes!("../data/sets.ron.gz"));
30 let cards: Vec<Card> = decompress(include_bytes!("../data/cards.ron.gz"));
31
32 log::info!("Loaded database with {} cards", cards.len());
33
34 Self {
35 pokemon: Map::new(pokemon, |pokemon| pokemon.id),
36 series: Map::new(series, |series| series.id.clone()),
37 sets: Map::new(sets, |set| set.id.clone()),
38 cards: Map::new(cards, |card| card.id.clone()),
39 }
40 })
41 .await?)
42 }
43
44 pub fn generate(data: impl AsRef<Path>) -> Result<Self, anywho::Error> {
45 use std::fs::{self, File};
46 use std::io::BufReader;
47
48 let pokemon = load_pokemon();
49
50 let mut series: BTreeMap<String, Series> = BTreeMap::new();
51 let mut sets: BTreeMap<String, Set> = BTreeMap::new();
52 let mut cards: BTreeMap<String, Card> = BTreeMap::new();
53
54 let entries = fs::read_dir(&data)?;
55
56 for entry in entries {
57 let entry = entry?;
58
59 if !entry.metadata()?.is_dir() {
60 continue;
61 }
62
63 let locale = Locale(entry.file_name().to_string_lossy().to_string());
64
65 if locale == Locale("ko".to_owned()) {
66 continue;
67 }
68 dbg!(&locale);
69
70 #[derive(Serialize, Deserialize)]
72 #[serde(rename_all = "camelCase")]
73 struct LocalizedSeries {
74 id: String,
75 name: String,
76 release_date: String,
77 }
78
79 dbg!(entry.path().join("series.json"));
80
81 let localized_series_list: Vec<LocalizedSeries> = {
82 let file = BufReader::new(File::open(entry.path().join("series.json"))?);
83 serde_json::from_reader(file)?
84 };
85
86 for localized_series in localized_series_list {
87 let series = series
88 .entry(localized_series.id.clone())
89 .or_insert_with(|| Series {
90 id: series::Id(localized_series.id),
91 name: locale::Map::new(),
92 release_date: localized_series.release_date,
93 });
94
95 series.name.insert(locale.clone(), localized_series.name);
96 }
97
98 #[derive(Serialize, Deserialize)]
100 #[serde(rename_all = "camelCase")]
101 struct LocalizedSet {
102 id: String,
103 name: String,
104 serie: Serie,
105 release_date: String,
106 card_count: CardCount,
107 abbreviation: Option<Abbreviation>,
108 }
109
110 #[derive(Serialize, Deserialize)]
111 struct Serie {
112 id: String,
113 }
114
115 #[derive(Serialize, Deserialize)]
116 struct CardCount {
117 total: usize,
118 }
119
120 #[derive(Serialize, Deserialize)]
121 struct Abbreviation {
122 official: String,
123 }
124
125 dbg!(entry.path().join("sets.json"));
126
127 let localized_sets: Vec<LocalizedSet> = {
128 let file = BufReader::new(File::open(entry.path().join("sets.json"))?);
129 serde_json::from_reader(file)?
130 };
131
132 for localized_set in localized_sets {
133 let set = sets.entry(localized_set.id.clone()).or_insert_with(|| Set {
134 id: set::Id(localized_set.id),
135 name: locale::Map::new(),
136 series: series::Id(localized_set.serie.id),
137 release_date: localized_set.release_date,
138 total_cards: localized_set.card_count.total,
139 abbreviation: None,
140 });
141
142 set.name.insert(locale.clone(), localized_set.name);
143
144 match localized_set.abbreviation {
145 Some(abbreviation) if set.abbreviation.is_none() => {
146 set.abbreviation = Some(abbreviation.official);
147 }
148 _ => {}
149 }
150 }
151
152 #[derive(Serialize, Deserialize)]
154 #[serde(rename_all = "camelCase")]
155 struct LocalizedCard {
156 id: String,
157 name: String,
158 set: CardSet,
159 #[serde(default)]
160 rarity: Option<String>,
161 #[serde(default)]
162 types: Vec<String>,
163 variants: LocalizedVariants,
164 #[serde(default)]
165 illustrator: Option<String>,
166 #[serde(default)]
167 dex_id: Vec<f64>,
168 }
169
170 #[derive(Serialize, Deserialize)]
171 struct CardSet {
172 id: String,
173 }
174
175 #[derive(Serialize, Deserialize)]
176 #[serde(rename_all = "camelCase")]
177 struct LocalizedVariants {
178 first_edition: bool,
179 holo: bool,
180 normal: bool,
181 reverse: bool,
182 w_promo: bool,
183 }
184
185 dbg!(entry.path().join("cards.json"));
186
187 let localized_cards: Vec<LocalizedCard> = {
188 let file = BufReader::new(File::open(entry.path().join("cards.json"))?);
189 serde_json::from_reader(file)?
190 };
191
192 for localized_card in localized_cards {
193 let Some(set) = sets.get(&localized_card.set.id) else {
194 continue;
195 };
196
197 if set.series.as_str() == "tcgp" {
198 continue;
199 }
200
201 let card = cards
202 .entry(localized_card.id.clone())
203 .or_insert_with(|| Card {
204 id: card::Id(localized_card.id),
205 name: locale::Map::new(),
206 set: set::Id(localized_card.set.id),
207 types: BTreeSet::new(),
208 rarity: card::Rarity::None,
209 variants: card::Variants {
210 first_edition: localized_card.variants.first_edition,
211 holo: localized_card.variants.holo,
212 normal: localized_card.variants.normal,
213 reverse: localized_card.variants.reverse,
214 w_promo: localized_card.variants.w_promo,
215 },
216 illustrator: localized_card.illustrator,
217 pokedex: localized_card
218 .dex_id
219 .into_iter()
220 .map(|id| id as usize)
221 .map(pokemon::Id)
222 .collect(),
223 });
224
225 if locale.0 == "en" && card.pokedex.is_empty() {
227 for pokemon in &pokemon {
228 let name = pokemon.name();
229
230 if let Some(start) = localized_card.name.find(name) {
231 let end = start + name.len();
232 let previous = start.saturating_sub(1);
233
234 let left = localized_card.name[previous..start].chars();
235 let right = localized_card.name
236 [end..(end + 1).min(localized_card.name.len())]
237 .chars();
238
239 if left.chain(right).all(|c| c.is_whitespace() || c == '-') {
240 card.pokedex = vec![pokemon.id];
241 break;
242 }
243 }
244 }
245 }
246
247 card.name.insert(locale.clone(), localized_card.name);
248 card.rarity = card.rarity.max(
249 localized_card
250 .rarity
251 .and_then(|rarity| parse_rarity(rarity).ok())
252 .unwrap_or_default(),
253 );
254
255 for type_ in localized_card.types {
256 if let Ok(type_) = parse_type(type_) {
257 card.types.insert(type_);
258 }
259 }
260 }
261 }
262
263 let mut cards: Vec<_> = cards.into_values().collect();
264 cards.sort_by_key(|card| {
265 sets.get(&card.set.0)
266 .map(|set| {
267 format!(
268 "{release_date}-{:0>5}",
269 card.id.0.split("-").last().unwrap_or_default(),
270 release_date = set.release_date,
271 )
272 })
273 .unwrap_or_default()
274 });
275
276 let mut series: Vec<_> = series.into_values().collect();
277 series.sort_by(|a, b| a.release_date.cmp(&b.release_date));
278
279 let mut sets: Vec<_> = sets.into_values().collect();
280 sets.sort_by(|a, b| a.release_date.cmp(&b.release_date));
281
282 Ok(Self {
283 pokemon: Map::new(pokemon, |pokemon| pokemon.id),
284 series: Map::new(series, |series| series.id.clone()),
285 sets: Map::new(sets, |set| set.id.clone()),
286 cards: Map::new(cards, |card| card.id.clone()),
287 })
288 }
289
290 pub fn find(&self, set: &str, number: &str) -> Option<&Card> {
291 if set.len() < 2 {
292 return None;
293 }
294
295 let mut set_matches: Vec<_> = self
296 .sets
297 .values()
298 .iter()
299 .filter_map(|candidate| {
300 let abbreviation = candidate.abbreviation.as_ref()?;
301
302 if abbreviation == set {
303 return Some((candidate, 0));
304 }
305
306 let distance = abbreviation
307 .chars()
308 .zip(set.chars())
309 .map(|(a, b)| if a == b { 0 } else { 1 })
310 .sum::<u64>();
311
312 Some((candidate, distance + 1))
313 })
314 .collect();
315
316 set_matches.sort_by_key(|(_, distance)| *distance);
317
318 let (best_set, _) = set_matches.first()?;
319 let card_id = card::Id(format!("{}-{number}", best_set.id));
320
321 self.cards.get(&card_id)
322 }
323}
324
325fn parse_type(type_: String) -> Result<card::Type, String> {
326 Ok(match type_.as_str() {
327 "Grass" => card::Type::Grass,
328 "Fire" => card::Type::Fire,
329 "Water" => card::Type::Water,
330 "Lightning" => card::Type::Lightning,
331 "Psychic" => card::Type::Psychic,
332 "Fighting" => card::Type::Fighting,
333 "Darkness" => card::Type::Darkness,
334 "Metal" => card::Type::Metal,
335 "Fairy" => card::Type::Fairy,
336 "Dragon" => card::Type::Dragon,
337 "Colorless" => card::Type::Colorless,
338 _ => Err(format!("invalid type: {type_}"))?,
339 })
340}
341
342fn parse_rarity(rarity: String) -> Result<card::Rarity, String> {
343 Ok(match rarity.as_str() {
344 "None" => card::Rarity::None,
345 "Common" | "One Diamond" => card::Rarity::Common,
346 "Uncommon" | "Two Diamond" => card::Rarity::Uncommon,
347 "Rare" | "Three Diamond" => card::Rarity::Rare,
348 "Holo Rare" | "Rare Holo" => card::Rarity::HoloRare,
349 "Rare Holo LV.X" => card::Rarity::HoloRareLvx,
350 "Holo Rare V" => card::Rarity::HoloRareV,
351 "Holo Rare VMAX" => card::Rarity::HoloRareVmax,
352 "Holo Rare VSTAR" => card::Rarity::HoloRareVstar,
353 "Shiny rare" | "One Shiny" => card::Rarity::ShinyRare,
354 "Shiny rare V" => card::Rarity::ShinyRareV,
355 "Shiny rare VMAX" => card::Rarity::ShinyRareVmax,
356 "Double rare" => card::Rarity::DoubleRare,
357 "ACE SPEC Rare" => card::Rarity::AceSpecRare,
358 "Amazing Rare" => card::Rarity::AmazingRare,
359 "Radiant Rare" => card::Rarity::RadiantRare,
360 "Rare PRIME" => card::Rarity::RarePrime,
361 "LEGEND" => card::Rarity::Legend,
362 "Classic Collection" => card::Rarity::ClassicCollection,
363 "Ultra Rare" | "Four Diamond" => card::Rarity::UltraRare,
364 "Shiny Ultra Rare" | "Two Shiny" => card::Rarity::ShinyUltraRare,
365 "Secret Rare" => card::Rarity::SecretRare,
366 "Full Art Trainer" => card::Rarity::FullArtTrainer,
367 "Illustration rare" | "One Star" => card::Rarity::IllustrationRare,
368 "Special illustration rare" | "Two Star" | "Three Star" => {
369 card::Rarity::SpecialIllustrationRare
370 }
371 "Hyper rare" | "Crown" => card::Rarity::HyperRare,
372 _ => {
373 dbg!(&rarity);
374
375 Err(format!("invalid rarity: {rarity}"))?
376 }
377 })
378}
379
380fn load_pokemon() -> Vec<Pokemon> {
381 let pokemon: Vec<String> = decompress(include_bytes!("../data/pokemon.ron.gz"));
382
383 pokemon
384 .into_iter()
385 .enumerate()
386 .map(|(i, name)| Pokemon {
387 id: pokemon::Id(i + 1),
388 name: locale::Map::from_iter([(Locale("en".to_owned()), name)]),
389 })
390 .collect()
391}
392
393fn decompress<T: DeserializeOwned>(bytes: &[u8]) -> T {
394 use flate2::read::GzDecoder;
395
396 let decoder = GzDecoder::new(bytes);
397
398 ron::de::from_reader(decoder).expect("Database is corrupt! Decompression failed.")
399}
400
401impl fmt::Debug for Database {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 f.debug_struct("Database")
404 .field("pokemon", &self.pokemon.len())
405 .field("series", &self.series.len())
406 .field("sets", &self.sets.len())
407 .field("cards", &self.cards.len())
408 .finish()
409 }
410}