1use std::collections::HashMap;
2use std::path::Path;
3use std::sync::OnceLock;
4
5use crate::data::{BodyRow, EngineRow, EuModelRow, ModelRow};
6use crate::maps::{FstMap, FstSet, data_dir};
7use crate::types::{BodyType, DriveType, FuelType, Transmission};
8
9pub struct Catalog {
15 makes: FstSet,
16 make_models: FstMap<ModelRow>,
17 eu_brand_models: Option<FstMap<EuModelRow>>,
18 eu_engines: Option<FstMap<EngineRow>>,
19 eu_body: Option<FstMap<BodyRow>>,
20 model_to_makes: OnceLock<HashMap<String, Vec<String>>>,
22}
23
24impl Catalog {
25 pub fn new() -> crate::Result<Self> {
27 let dir = data_dir();
28 #[cfg(feature = "embedded")]
29 crate::embedded::ensure_installed(&dir).ok();
30 Self::open(&dir)
31 }
32
33 pub fn open(dir: &Path) -> crate::Result<Self> {
36 let eu_brand_models = if dir.join("eu_brand_models.fst").exists() {
37 Some(FstMap::open(dir)?)
38 } else {
39 None
40 };
41 let eu_engines = if dir.join("eu_engines.fst").exists() {
42 Some(FstMap::open(dir)?)
43 } else {
44 None
45 };
46 let eu_body = if dir.join("eu_body.fst").exists() {
47 Some(FstMap::open(dir)?)
48 } else {
49 None
50 };
51 Ok(Catalog {
52 makes: FstSet::open(&dir.join("makes.fst"))?,
53 make_models: FstMap::open(dir)?,
54 eu_brand_models,
55 eu_engines,
56 eu_body,
57 model_to_makes: OnceLock::new(),
58 })
59 }
60
61 pub fn all_makes(&self) -> Vec<String> {
63 self.makes.keys()
64 }
65
66 pub fn has_make(&self, make: &str) -> bool {
68 self.makes.contains(&make.to_ascii_uppercase())
69 }
70
71 pub fn make_count(&self) -> u64 {
73 self.makes.len()
74 }
75
76 pub fn models_for_make(&self, make: &str) -> Vec<String> {
81 let key = crate::decoder::normalize_make(make);
82 let mut models: Vec<String> = self
83 .make_models
84 .get(&key)
85 .map(|rows| rows.into_iter().map(|r| r.name).collect())
86 .unwrap_or_default();
87 if let Some(eu) = &self.eu_brand_models {
88 if let Some(rows) = eu.get(&key) {
89 for r in rows {
90 models.push(r.name);
91 }
92 }
93 }
94 models.sort();
95 models.dedup();
96 models
97 }
98
99 pub fn make_for_model(&self, model: &str) -> Vec<String> {
106 self.model_to_makes
107 .get_or_init(|| self.build_model_index())
108 .get(&model.to_ascii_uppercase())
109 .cloned()
110 .unwrap_or_default()
111 }
112
113 fn build_model_index(&self) -> HashMap<String, Vec<String>> {
114 let mut idx: HashMap<String, Vec<String>> = HashMap::new();
115 for make in self.make_models.keys() {
116 if let Some(rows) = self.make_models.get(&make) {
117 for r in rows {
118 idx.entry(r.name.to_ascii_uppercase())
119 .or_default()
120 .push(make.clone());
121 }
122 }
123 }
124 if let Some(eu) = &self.eu_brand_models {
125 for make in eu.keys() {
126 if let Some(rows) = eu.get(&make) {
127 for r in rows {
128 idx.entry(r.name.to_ascii_uppercase())
129 .or_default()
130 .push(make.clone());
131 }
132 }
133 }
134 }
135 for makes in idx.values_mut() {
136 makes.sort();
137 makes.dedup();
138 }
139 idx
140 }
141
142 pub fn eu_models_for(&self, brand: &str) -> Vec<EuModelRow> {
146 let key = crate::decoder::normalize_make(brand);
147 self.eu_brand_models
148 .as_ref()
149 .and_then(|m| m.get(&key))
150 .unwrap_or_default()
151 }
152
153 pub fn engines_for_brand(&self, brand: &str) -> Vec<EngineRow> {
156 let key = crate::decoder::normalize_make(brand);
157 self.eu_engines
158 .as_ref()
159 .and_then(|m| m.get(&key))
160 .unwrap_or_default()
161 }
162
163 pub fn engines_for(&self, brand: &str, model: &str) -> Vec<EngineRow> {
166 let model_key = model.to_ascii_uppercase();
167 self.engines_for_brand(brand)
168 .into_iter()
169 .filter(|r| r.model == model_key)
170 .collect()
171 }
172
173 pub fn body_for(&self, make: &str, model: &str) -> Vec<BodyType> {
180 let key = crate::decoder::normalize_make(make);
181 let model_key = model.to_ascii_uppercase();
182 self.eu_body
183 .as_ref()
184 .and_then(|m| m.get(&key))
185 .unwrap_or_default()
186 .into_iter()
187 .find(|r| r.model == model_key)
188 .map(|r| r.bodies)
189 .unwrap_or_default()
190 }
191
192 pub fn body_types() -> &'static [BodyType] {
196 crate::types::BODY_TYPES
197 }
198
199 pub fn fuel_types() -> &'static [FuelType] {
201 &[
202 FuelType::Gasoline,
203 FuelType::Diesel,
204 FuelType::Electric,
205 FuelType::Hybrid,
206 FuelType::PluginHybrid,
207 FuelType::Ethanol,
208 FuelType::FlexFuel,
209 FuelType::Cng,
210 FuelType::Lng,
211 FuelType::Lpg,
212 FuelType::Hydrogen,
213 FuelType::FuelCell,
214 FuelType::Methanol,
215 FuelType::NaturalGas,
216 FuelType::Other,
217 ]
218 }
219
220 pub fn drive_types() -> &'static [DriveType] {
222 &[
223 DriveType::Fwd,
224 DriveType::Rwd,
225 DriveType::Awd,
226 DriveType::FourWheelDrive,
227 DriveType::Other,
228 ]
229 }
230
231 pub fn transmissions() -> &'static [Transmission] {
233 &[
234 Transmission::Manual,
235 Transmission::Automatic,
236 Transmission::DualClutch,
237 Transmission::Cvt,
238 Transmission::SingleSpeed,
239 Transmission::AutomatedManual,
240 Transmission::Other,
241 ]
242 }
243}