1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use crate::prelude::*;

/// Ordered from oldest to newest.
/// newest > oldest
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumDiscriminants, PartialOrd, Ord)]
pub enum IPhoneVariant {
	SE {
		generation: Generation,
	},

	Number {
		num: NonZeroU8,
		plus: bool,
		pro: bool,
		max: bool,
	},
}

impl NomFromStr for IPhoneVariant {
	#[tracing::instrument(level = "trace", skip(input))]
	fn nom_from_str(input: &str) -> IResult<&str, Self> {
		let (remaining, discriminate) = preceded(
			ws(tag("iPhone")),
			cut(alt((
				value(IPhoneVariantDiscriminants::SE, ws(tag("SE"))),
				value(IPhoneVariantDiscriminants::Number, peek(ws(digit1))),
			))),
		)(input)?;

		#[cfg(test)]
		trace!(
			?discriminate,
			?remaining,
			"Discriminant found for parsing [IPhoneVariant]"
		);

		match discriminate {
			IPhoneVariantDiscriminants::SE => {
				let (remaining, generation) = Generation::nom_from_str(remaining)?;
				Ok((remaining, IPhoneVariant::SE { generation }))
			}
			IPhoneVariantDiscriminants::Number => {
				let (remaining, num) = NonZeroU8::nom_from_str(remaining)?;
				let (remaining, plus) = alt((value(true, ws(tag("Plus"))), success(false)))(remaining)?;
				let (remaining, pro) = alt((value(true, ws(tag("Pro"))), success(false)))(remaining)?;
				let (remaining, max) = alt((value(true, ws(tag("Max"))), success(false)))(remaining)?;
				Ok((
					remaining,
					IPhoneVariant::Number {
						num,
						plus,
						pro,
						max,
					},
				))
			}
		}
	}
}

impl Display for IPhoneVariant {
	#[tracing::instrument(level = "trace", skip(self, f))]
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self {
			IPhoneVariant::SE { generation } => write!(f, "iPhone SE {}", generation),
			IPhoneVariant::Number {
				num,
				plus,
				pro,
				max,
			} => {
				write!(f, "iPhone {}", num)?;
				if *plus {
					write!(f, " Plus")?;
				}
				if *pro {
					write!(f, " Pro")?;
				}
				if *max {
					write!(f, " Max")?;
				}
				Ok(())
			}
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn iphone_ordering() {
		let old = IPhoneVariant::SE {
			generation: Generation::testing_num(1.try_into().unwrap()),
		};
		let new = IPhoneVariant::Number {
			num: NonZeroU8::new(69).unwrap(),
			plus: true,
			pro: true,
			max: true,
		};
		assert!(new > old);
	}
}