1use std::{fmt::Display, str::FromStr};
2
3use crate::{enums::FlightRules, errors::FsdMessageParseError, util::parse_altitude};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct TransponderCode(u16);
7impl TryFrom<u16> for TransponderCode {
8 type Error = FsdMessageParseError;
9 fn try_from(mut code: u16) -> Result<Self, Self::Error> {
10 let mut digits = [0; 4];
11 digits[0] = code / 1000;
12 code -= digits[0] * 1000;
13 digits[1] = code / 100;
14 code -= digits[1] * 100;
15 digits[2] = code / 10;
16 code -= digits[2] * 10;
17 digits[3] = code;
18
19 if digits.into_iter().any(|x| x > 7) {
20 Err(FsdMessageParseError::InvalidTransponderCode(format!(
21 "{:04}",
22 code
23 )))
24 } else {
25 let code = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
26 Ok(TransponderCode(code))
27 }
28 }
29}
30impl TransponderCode {
31 pub fn try_from_bcd_format(
32 bcd: impl AsRef<str>,
33 ) -> Result<TransponderCode, FsdMessageParseError> {
34 let bcd = bcd.as_ref();
35 let num = bcd
36 .parse::<u32>()
37 .map_err(|_| FsdMessageParseError::InvalidTransponderCode(bcd.to_owned()))?;
38 let hex_value = format!("{:X}", num);
39 let result = hex_value
40 .parse::<u16>()
41 .map_err(|_| FsdMessageParseError::InvalidTransponderCode(bcd.to_owned()))?;
42 Self::try_from(result)
43 }
44 pub fn as_bcd_format(&self) -> u32 {
45 let as_dec_str = self.to_string();
46 u32::from_str_radix(&as_dec_str, 16).unwrap()
47 }
48}
49
50impl FromStr for TransponderCode {
51 type Err = FsdMessageParseError;
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 let code: u16 = s
54 .parse()
55 .map_err(|_| FsdMessageParseError::InvalidTransponderCode(s.to_string()))?;
56 code.try_into()
57 }
58}
59impl Display for TransponderCode {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "{:04}", self.0)
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct RadioFrequency(pub(crate) u16, pub(crate) u16);
70impl RadioFrequency {
71 pub fn new(left: u16, right: u16) -> Result<RadioFrequency, FsdMessageParseError> {
80 if !((118..=137).contains(&left)
81 || left == 199 && right == 998
82 || left == 149 && right == 999)
83 {
84 return Err(FsdMessageParseError::InvalidFrequency(format!(
85 "{}.{:03}",
86 left, right
87 )));
88 }
89 Ok(RadioFrequency(left, right))
90 }
91
92 pub fn frequency(&self) -> (u16, u16) {
93 (self.0, self.1)
94 }
95
96 pub fn to_human_readable_string(&self) -> String {
106 format!("{}.{:03}", self.0, self.1)
107 }
108 pub fn try_from_human_readable_string(
109 input: impl AsRef<str>,
110 ) -> Result<RadioFrequency, FsdMessageParseError> {
111 let input = input.as_ref();
112 let mut split = input.split('.');
113
114 let left = split
115 .next()
116 .ok_or_else(|| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
117 let left: u16 = left
118 .parse()
119 .map_err(|_| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
120 let right = split
121 .next()
122 .ok_or_else(|| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
123 let right: u16 = right
124 .parse()
125 .map_err(|_| FsdMessageParseError::InvalidFrequency(input.to_string()))?;
126 RadioFrequency::new(left, right)
127 }
128}
129
130impl FromStr for RadioFrequency {
131 type Err = FsdMessageParseError;
132 fn from_str(short_form: &str) -> Result<Self, Self::Err> {
133 if short_form.len() != 5 {
134 return Err(FsdMessageParseError::InvalidFrequency(
135 short_form.to_string(),
136 ));
137 }
138 let left: u16 = short_form[0..2]
139 .parse()
140 .map_err(|_| FsdMessageParseError::InvalidFrequency(short_form.to_string()))?;
141 let right: u16 = short_form[2..]
142 .parse()
143 .map_err(|_| FsdMessageParseError::InvalidFrequency(short_form.to_string()))?;
144 RadioFrequency::new(left + 100, right)
145 }
146}
147
148impl Display for RadioFrequency {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 write!(f, "{}{:03}", self.0 - 100, self.1)
151 }
152}
153
154#[derive(Debug, Default, Clone)]
155pub struct PlaneInfo {
156 pub equipment: Option<String>,
157 pub airline: Option<String>,
158 pub livery: Option<String>,
159}
160impl From<&[&str]> for PlaneInfo {
161 fn from(value: &[&str]) -> Self {
162 let mut plane_info = PlaneInfo::default();
163
164 if value.is_empty() {
165 return plane_info;
166 }
167
168 for entry in value {
169 let mut split = entry.split('=');
170 let k = match split.next() {
171 Some(k) => k,
172 None => continue,
173 };
174
175 let v = match split.next() {
176 Some(v) => v.to_string(),
177 None => continue,
178 };
179
180 match k.to_uppercase().as_str() {
181 "EQUIPMENT" => plane_info.equipment = Some(v),
182 "AIRLINE" => plane_info.airline = Some(v),
183 "LIVERY" => plane_info.livery = Some(v),
184 _ => {}
185 }
186 }
187 plane_info
188 }
189}
190impl Display for PlaneInfo {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 let mut need_delimiter = false;
193 if let Some(ref equipment) = self.equipment {
194 write!(f, "EQUIPMENT={}", equipment)?;
195 need_delimiter = true;
196 }
197 if let Some(ref airline) = self.airline {
198 if need_delimiter {
199 write!(f, ":")?;
200 }
201 write!(f, "AIRLINE={}", airline)?;
202 need_delimiter = true;
203 }
204 if let Some(ref livery) = self.livery {
205 if need_delimiter {
206 write!(f, ":")?;
207 }
208 write!(f, "LIVERY={}", livery)?;
209 }
210 Ok(())
211 }
212}
213
214impl PlaneInfo {
215 pub fn new(
216 equipment: Option<impl Into<String>>,
217 airline: Option<impl Into<String>>,
218 livery: Option<impl Into<String>>,
219 ) -> PlaneInfo {
220 let equipment = equipment.map(|x| x.into());
221 let airline = airline.map(|x| x.into());
222 let livery = livery.map(|x| x.into());
223 PlaneInfo {
224 equipment,
225 airline,
226 livery,
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct FlightPlan {
233 pub flight_rules: FlightRules,
234 pub ac_type: String,
235 pub filed_tas: u16,
236 pub origin: String,
237 pub etd: u16,
238 pub atd: u16,
239 pub cruise_level: u32,
240 pub destination: String,
241 pub hours_enroute: u8,
242 pub mins_enroute: u8,
243 pub hours_fuel: u8,
244 pub mins_fuel: u8,
245 pub alternate: String,
246 pub remarks: String,
247 pub route: String,
248}
249
250impl Display for FlightPlan {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 write!(
253 f,
254 "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
255 self.flight_rules,
256 self.ac_type,
257 self.filed_tas,
258 self.origin,
259 self.etd,
260 self.atd,
261 self.cruise_level,
262 self.destination,
263 self.hours_enroute,
264 self.mins_enroute,
265 self.hours_fuel,
266 self.mins_fuel,
267 self.alternate,
268 self.remarks,
269 self.route
270 )
271 }
272}
273
274impl TryFrom<&[&str]> for FlightPlan {
275 type Error = FsdMessageParseError;
276 fn try_from(fields: &[&str]) -> Result<Self, Self::Error> {
277 if fields.len() != 15 {
278 return Err(FsdMessageParseError::InvalidFieldCount(15, fields.len()));
279 }
280
281 let filed_tas = if fields[2].is_empty() {
282 0
283 } else {
284 fields[2]
285 .parse()
286 .map_err(|_| FsdMessageParseError::InvalidSpeed(fields[2].to_string()))?
287 };
288 let etd = if fields[4].is_empty() {
289 0
290 } else {
291 fields[4]
292 .parse()
293 .map_err(|_| FsdMessageParseError::InvalidTime(fields[4].to_string()))?
294 };
295 let atd = if fields[5].is_empty() {
296 0
297 } else {
298 fields[5]
299 .parse()
300 .map_err(|_| FsdMessageParseError::InvalidTime(fields[5].to_string()))?
301 };
302 let hours_enroute = if fields[8].is_empty() {
303 0
304 } else {
305 fields[8]
306 .parse()
307 .map_err(|_| FsdMessageParseError::InvalidTime(fields[8].to_string()))?
308 };
309 let hours_fuel = if fields[10].is_empty() {
310 0
311 } else {
312 fields[10]
313 .parse()
314 .map_err(|_| FsdMessageParseError::InvalidTime(fields[10].to_string()))?
315 };
316 let mins_enroute = if fields[9].is_empty() {
317 0
318 } else {
319 let mins = fields[9]
320 .parse()
321 .map_err(|_| FsdMessageParseError::InvalidTime(fields[9].to_string()))?;
322 if mins > 59 {
323 return Err(FsdMessageParseError::InvalidMinute(fields[9].to_string()));
324 }
325 mins
326 };
327 let mins_fuel = if fields[11].is_empty() {
328 0
329 } else {
330 let mins = fields[11]
331 .parse()
332 .map_err(|_| FsdMessageParseError::InvalidTime(fields[11].to_string()))?;
333 if mins > 59 {
334 return Err(FsdMessageParseError::InvalidMinute(fields[11].to_string()));
335 }
336 mins
337 };
338
339 Ok(FlightPlan::new(
340 fields[0].parse()?,
341 fields[1],
342 filed_tas,
343 fields[3],
344 etd,
345 atd,
346 parse_altitude(fields[6])? as u32,
347 fields[7],
348 hours_enroute,
349 mins_enroute,
350 hours_fuel,
351 mins_fuel,
352 fields[12],
353 fields[13],
354 fields[14],
355 ))
356 }
357}
358
359impl FlightPlan {
360 pub fn new(
361 flight_rules: FlightRules,
362 ac_type: impl AsRef<str>,
363 filed_tas: u16,
364 origin: impl AsRef<str>,
365 etd: u16,
366 atd: u16,
367 cruise_level: u32,
368 destination: impl AsRef<str>,
369 hours_enroute: u8,
370 mins_enroute: u8,
371 hours_fuel: u8,
372 mins_fuel: u8,
373 alternate: impl AsRef<str>,
374 remarks: impl Into<String>,
375 route: impl AsRef<str>,
376 ) -> FlightPlan {
377 FlightPlan {
378 flight_rules,
379 ac_type: ac_type.as_ref().to_uppercase(),
380 filed_tas,
381 origin: origin.as_ref().to_uppercase(),
382 etd,
383 atd,
384 cruise_level,
385 destination: destination.as_ref().to_uppercase(),
386 hours_enroute,
387 mins_enroute,
388 hours_fuel,
389 mins_fuel,
390 alternate: alternate.as_ref().to_uppercase(),
391 remarks: remarks.into(),
392 route: route.as_ref().to_uppercase(),
393 }
394 }
395}