1use std::{fmt, str::FromStr};
2
3use lazy_static::lazy_static;
4use rand::{self, Rng};
5use regex::Regex;
6
7#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
8pub struct LogicLong<T = u32>
9where
10 T: Copy + ?Sized + From<u32>,
11 u32: From<T>,
12{
13 pub high: T,
14 pub low: T,
15 pub tag: String,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub enum LogicLongError {
20 InvalidTag(String),
21 InvalidHighID(u32),
22}
23
24lazy_static! {
25 pub static ref VALID_REGEX: Regex = Regex::new("^#[oO0289PYLQGRJCUVpylqgrjcuv]+$").unwrap();
26 pub static ref FIX_REGEX: Regex = Regex::new("[^A-Z0-9]+").unwrap();
27}
28
29const ORDER: [char; 14] = ['0', '2', '8', '9', 'P', 'Y', 'L', 'Q', 'G', 'R', 'J', 'C', 'U', 'V'];
30const BASE: u64 = 14;
31
32impl<T> LogicLong<T>
33where
34 T: Copy + ?Sized + From<u32>,
35 u32: From<T>,
36{
37 pub fn new(high: T, low: T) -> Self {
38 let mut logic_long = Self { high, low, tag: String::new() };
39 logic_long.tag = logic_long.to_tag();
40 logic_long
41 }
42
43 pub(crate) fn parse_tag(tag: &str) -> Result<Self, LogicLongError> {
44 let tag = Self::fix_tag(tag);
45 let mut total: u64 = 0;
46
47 for (index, char) in tag.replace('#', "").chars().rev().enumerate() {
49 let position = ORDER
51 .iter()
52 .position(|&x| x == char)
53 .ok_or_else(|| LogicLongError::InvalidTag(tag.clone()))?;
54 total += position as u64 * BASE.pow(index as u32);
56 }
57
58 let (high, low) = (((total % 256) as u32), ((total / 256) as u32));
59
60 Ok(Self { high: high.into(), low: low.into(), tag })
61 }
62
63 #[must_use]
65 pub fn is_valid_tag(tag: &str) -> bool {
66 VALID_REGEX.is_match(&tag.to_uppercase().replace('O', "0"))
67 }
68
69 #[must_use]
70 pub fn fix_tag(tag: &str) -> String {
71 "#".to_owned() + &FIX_REGEX.replace_all(&tag.to_uppercase(), "").replace('O', "0")
72 }
73
74 #[must_use]
75 pub fn random(max_high: u32) -> Self {
77 let mut rng = rand::thread_rng();
78 let high = rng.gen_range(0..max_high).into();
79 let low = rng.gen::<u32>().into();
80
81 Self::new(high, low)
82 }
83
84 pub fn to_tag(&self) -> String {
85 let mut tag = String::new();
86 let mut total = u64::from(u32::from(self.high)) + u64::from(u32::from(self.low)) * 0x100;
88 let mut b14;
89
90 while total != 0 {
91 b14 = total % 14;
92 total /= 14;
93 tag.insert(0, ORDER[b14 as usize]);
94 }
95 Self::fix_tag(&tag)
96 }
97}
98
99impl<T> fmt::Display for LogicLong<T>
100where
101 T: Copy + ?Sized + From<u32>,
102 u32: From<T>,
103{
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 write!(f, "{}", self.tag)
106 }
107}
108
109impl<T> fmt::Debug for LogicLong<T>
110where
111 T: Copy + ?Sized + From<u32> + fmt::Display,
112 u32: From<T>,
113{
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 write!(f, "LogicLong {{ high: {}, low: {}, tag: {} }}", self.high, self.low, self.tag)
116 }
117}
118
119impl<T> FromStr for LogicLong<T>
120where
121 T: Copy + ?Sized + From<u32>,
122 u32: From<T>,
123{
124 type Err = LogicLongError;
125 fn from_str(s: &str) -> Result<Self, Self::Err> {
126 Self::parse_tag(s)
127 }
128}
129
130impl<T> From<LogicLong<T>> for String
131where
132 T: Copy + ?Sized + From<u32>,
133 u32: From<T>,
134{
135 fn from(logic_long: LogicLong<T>) -> Self {
136 logic_long.tag
137 }
138}
139
140impl fmt::Display for LogicLongError {
141 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142 match self {
143 Self::InvalidTag(tag) => write!(f, "{tag} is not a valid tag."),
144 Self::InvalidHighID(high) => write!(f, "Invalid high ID: {high}"),
145 }
146 }
147}
148
149impl std::error::Error for LogicLongError {}