1use rand::Rng;
2use std::{cmp, fmt};
3use wasm_bindgen::prelude::*;
4
5pub const ALPHABETS: [&'static str; 2] = ["bcdfglmnprstvz", "aeiou"];
6
7#[derive(Debug, PartialEq)]
8pub enum BananaError {
9 InvalidBanana,
10 InvalidAlphabet,
11}
12
13impl fmt::Display for BananaError {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 match self {
16 BananaError::InvalidBanana => write!(f, "Invalid banana number"),
17 BananaError::InvalidAlphabet => write!(f, "Invalid alphabet"),
18 }
19 }
20}
21
22fn valid_alphabet(alphabet: &Vec<char>) -> bool {
23 for (i, c) in alphabet.iter().enumerate() {
24 if alphabet.iter().rposition(|v| c == v).unwrap() != i {
25 return false;
26 }
27 }
28
29 true
30}
31
32fn get_alphabets(alphabets: Option<&[String]>) -> Result<Vec<Vec<char>>, BananaError> {
33 let result = alphabets
34 .as_deref()
35 .map(|a| a.iter().map(|s| &**s).collect::<Vec<&str>>())
36 .unwrap_or(ALPHABETS.to_vec())
37 .iter()
38 .map(|alphabet| alphabet.chars().collect())
39 .collect();
40
41 for alphabet in &result {
42 if !valid_alphabet(alphabet) {
43 return Err(BananaError::InvalidAlphabet);
44 }
45 }
46
47 Ok(result)
48}
49
50#[derive(Default, Debug)]
51pub struct EncodeParams {
52 pub alphabet_shift: Option<usize>,
53 pub alphabet_end: Option<usize>,
54 pub min_length: Option<usize>,
55 pub alphabets: Option<Vec<String>>,
56}
57
58pub fn encode(num: u64, params: &EncodeParams) -> Result<String, BananaError> {
59 let alphabets = get_alphabets(params.alphabets.as_deref())?;
60
61 let n_alphabets = alphabets.len();
62 let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
63 let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
64 let min_length = cmp::max(1, params.min_length.unwrap_or(1));
65
66 let mut v = num;
67 let mut word = Vec::new();
68
69 let mut idx = (n_alphabets - 1 + alphabet_shift + alphabet_end) % n_alphabets;
70 while v != 0
71 || (idx != (n_alphabets - 1 + alphabet_shift) % n_alphabets)
72 || word.len() < min_length
73 {
74 let alphabet_len = alphabets[idx].len() as u64;
75 if alphabet_len == 0 {
76 return Err(BananaError::InvalidAlphabet);
77 }
78
79 let r: usize = usize::try_from(v % alphabet_len).unwrap();
80 v = v / alphabet_len;
81
82 word.push(alphabets[idx][r]);
83 idx = (idx + n_alphabets - 1) % n_alphabets;
84 }
85
86 word.reverse();
87 Ok(word.iter().collect())
88}
89
90pub fn random(params: &EncodeParams) -> Result<String, BananaError> {
91 let alphabets = get_alphabets(params.alphabets.as_deref())?;
92
93 let n_alphabets = alphabets.len();
94
95 let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
96 let alphabet_end = params.alphabet_end.unwrap_or(0);
97 let min_length = cmp::max(1, params.min_length.unwrap_or(1));
98
99 if min_length == 0 {
100 return Ok("".to_string());
101 }
102
103 let mut word = Vec::new();
104 let mut rng = rand::thread_rng();
105
106 let mut idx = (n_alphabets - 1 + alphabet_shift + alphabet_end) % n_alphabets;
107
108 while idx != (n_alphabets - 1 + alphabet_shift) % n_alphabets || word.len() < min_length {
109 let alphabet_len = alphabets[idx].len();
110 if alphabet_len == 0 {
111 return Err(BananaError::InvalidAlphabet);
112 }
113 let r = rng.gen_range(0..alphabet_len);
114 word.push(alphabets[idx][r]);
115 idx = (idx + n_alphabets - 1) % n_alphabets;
116 }
117
118 word.reverse();
119 Ok(word.iter().collect())
120}
121
122#[derive(Default, Debug)]
123pub struct DecodeParams {
124 pub alphabet_shift: Option<usize>,
125 pub alphabet_end: Option<usize>,
126 pub alphabets: Option<Vec<String>>,
127}
128
129pub fn decode(word: &str, params: &DecodeParams) -> Result<u64, BananaError> {
130 let alphabets = get_alphabets(params.alphabets.as_deref())?;
131
132 let n_alphabets = alphabets.len();
133
134 let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
135 let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
136
137 if (word.chars().count() - alphabet_end) % n_alphabets != 0 {
138 return Err(BananaError::InvalidBanana);
139 }
140
141 let mut v: u64 = 0;
142 for (i, c) in word.chars().enumerate() {
143 let idx = (n_alphabets + i + alphabet_shift) % n_alphabets;
144 let alphabet = &alphabets[idx];
145 match alphabet.iter().position(|&x| x == c) {
146 Some(pos) => v = v * alphabet.len() as u64 + pos as u64,
147 None => return Err(BananaError::InvalidBanana),
148 };
149 }
150
151 Ok(v)
152}
153
154pub fn is_valid(word: &str, params: &DecodeParams) -> Result<bool, BananaError> {
155 let alphabets = get_alphabets(params.alphabets.as_deref())?;
156
157 let n_alphabets = alphabets.len();
158
159 let alphabet_shift = params.alphabet_shift.unwrap_or(0) % n_alphabets;
160 let alphabet_end = params.alphabet_end.unwrap_or(0) % n_alphabets;
161
162 if (word.chars().count() - alphabet_end) % n_alphabets != 0 {
163 return Ok(false);
164 }
165
166 for (i, c) in word.chars().enumerate() {
167 let idx = (n_alphabets + i + alphabet_shift) % n_alphabets;
168 let alphabet = &alphabets[idx];
169 if alphabet.iter().position(|&x| x == c).is_none() {
170 return Ok(false);
171 }
172 }
173
174 return Ok(true);
175}
176
177#[macro_export]
178macro_rules! encode {
179 ($a: expr) => {
180 encode($a, &EncodeParams::default())
181 };
182}
183
184#[macro_export]
185macro_rules! decode {
186 ($a: expr) => {
187 decode($a, &DecodeParams::default())
188 };
189}
190
191#[macro_export]
192macro_rules! random {
193 () => {
194 random(&EncodeParams::default())
195 };
196}
197
198#[macro_export]
199macro_rules! is_valid {
200 ($a: expr) => {
201 is_valid($a, &DecodeParams::default())
202 };
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_macro() {
211 for test in [
212 (1, "be"),
213 (1000, "duga"),
214 (100000000000, "tumuzadofopa"),
215 (u64::MAX, "cenicovutinazamofevafa"),
216 ] {
217 assert_eq!(encode!(test.0).unwrap(), test.1);
218 assert_eq!(decode!(test.1).unwrap(), test.0);
219 assert_eq!(is_valid!(&encode!(test.0).unwrap()).unwrap(), true);
220 }
221
222 assert_eq!(is_valid!("123").unwrap(), false);
223 assert!(random!().unwrap().len() > 0)
224 }
225
226 #[test]
227 fn test_alphabet_invalid() {
228 assert_eq!(
229 encode(
230 1,
231 &EncodeParams {
232 alphabets: Some(vec!["abc".to_string(), "123".to_string()]),
233 ..Default::default()
234 }
235 )
236 .unwrap(),
237 "a2"
238 );
239 assert_eq!(
240 encode(
241 1,
242 &EncodeParams {
243 alphabets: Some(vec!["aba".to_string(), "123".to_string()]),
244 ..Default::default()
245 }
246 )
247 .err()
248 .unwrap(),
249 BananaError::InvalidAlphabet
250 );
251 assert_eq!(
252 encode(
253 1,
254 &EncodeParams {
255 alphabets: Some(vec!["abcdefak".to_string(), "123".to_string()]),
256 ..Default::default()
257 }
258 )
259 .err()
260 .unwrap(),
261 BananaError::InvalidAlphabet
262 );
263 assert_eq!(
264 encode(
265 1,
266 &EncodeParams {
267 alphabets: Some(vec!["a".to_string(), "1232789".to_string()]),
268 ..Default::default()
269 }
270 )
271 .err()
272 .unwrap(),
273 BananaError::InvalidAlphabet
274 );
275 }
276
277 #[test]
278 fn test_alphabet_shift() {
279 for test in [
280 (1, "be", 0),
281 (1, "ac", 1),
282 (1000, "ebem", 3),
283 (1, "ac", usize::MAX),
284 ] {
285 assert_eq!(
286 encode(
287 test.0,
288 &EncodeParams {
289 alphabet_shift: Some(test.2),
290 ..Default::default()
291 },
292 )
293 .unwrap(),
294 test.1
295 );
296
297 assert_eq!(
298 decode(
299 test.1,
300 &DecodeParams {
301 alphabet_shift: Some(test.2),
302 ..Default::default()
303 },
304 )
305 .unwrap(),
306 test.0
307 );
308 }
309 }
310
311 #[test]
312 fn test_alphabet_end() {
313 for test in [
314 (1, "be", 0),
315 (1, "c", 1),
316 (1000, "bebem", 3),
317 (1, "c", usize::MAX),
318 ] {
319 assert_eq!(
320 encode(
321 test.0,
322 &EncodeParams {
323 alphabet_end: Some(test.2),
324 ..Default::default()
325 },
326 )
327 .unwrap(),
328 test.1
329 );
330
331 assert_eq!(
332 decode(
333 test.1,
334 &DecodeParams {
335 alphabet_end: Some(test.2),
336 ..Default::default()
337 },
338 )
339 .unwrap(),
340 test.0
341 );
342 }
343 }
344
345 #[test]
346 fn test_min_length() {
347 for test in [
348 (1, "be", 0),
349 (1, "be", 1),
350 (1, "bababababe", 10),
351 (1000, "bababaduga", 10),
352 ] {
353 assert_eq!(
354 encode(
355 test.0,
356 &EncodeParams {
357 min_length: Some(test.2),
358 ..Default::default()
359 },
360 )
361 .unwrap(),
362 test.1
363 );
364
365 assert_eq!(decode!(test.1).unwrap(), test.0);
366 }
367 }
368
369 #[test]
370 fn test_alphabets() {
371 for test in [
372 (1, "aw", vec!["abc".to_string(), "qwe".to_string()]),
373 (8, "ce", vec!["abc".to_string(), "qwe".to_string()]),
374 (9, "awaq", vec!["abc".to_string(), "qwe".to_string()]),
375 (
376 9,
377 "bq1",
378 vec!["abc".to_string(), "qwe".to_string(), "123".to_string()],
379 ),
380 (
381 27,
382 "aq2aq1",
383 vec!["abc".to_string(), "qwe".to_string(), "123".to_string()],
384 ),
385 (
386 0,
387 "πΌπΆπ±",
388 vec![
389 "πΌπ΅π¦".to_string(),
390 "πΆπΊπ¦".to_string(),
391 "π±π¦π―".to_string(),
392 ],
393 ),
394 (
395 27,
396 "πΌπΆπ¦πΌπΆπ±",
397 vec![
398 "πΌπ΅π¦".to_string(),
399 "πΆπΊπ¦".to_string(),
400 "π±π¦π―".to_string(),
401 ],
402 ),
403 ] {
404 assert_eq!(
405 encode(
406 test.0,
407 &EncodeParams {
408 alphabets: Some(test.2.clone()),
409 ..Default::default()
410 },
411 )
412 .unwrap(),
413 test.1
414 );
415
416 assert_eq!(
417 decode(
418 test.1,
419 &DecodeParams {
420 alphabets: Some(test.2.clone()),
421 ..Default::default()
422 }
423 )
424 .unwrap(),
425 test.0
426 );
427 }
428 }
429}
430
431#[wasm_bindgen]
432pub fn banana_encode(
433 num: u64,
434 alphabet_shift: Option<usize>,
435 alphabet_end: Option<usize>,
436 min_length: Option<usize>,
437 alphabets: Option<Vec<String>>,
438) -> String {
439 encode(
440 num,
441 &EncodeParams {
442 alphabet_shift: alphabet_shift,
443 alphabet_end: alphabet_end,
444 min_length: min_length,
445 alphabets: alphabets,
446 },
447 )
448 .unwrap_or("".to_string())
449}
450
451#[wasm_bindgen]
452pub fn banana_random(
453 alphabet_shift: Option<usize>,
454 alphabet_end: Option<usize>,
455 min_length: Option<usize>,
456 alphabets: Option<Vec<String>>,
457) -> String {
458 random(&EncodeParams {
459 alphabet_shift: alphabet_shift,
460 alphabet_end: alphabet_end,
461 min_length: min_length,
462 alphabets: alphabets,
463 })
464 .unwrap_or("".to_string())
465}
466
467#[wasm_bindgen]
468pub fn banana_decode(
469 word: &str,
470 alphabet_shift: Option<usize>,
471 alphabet_end: Option<usize>,
472 alphabets: Option<Vec<String>>,
473) -> u64 {
474 decode(
475 word,
476 &DecodeParams {
477 alphabet_shift: alphabet_shift,
478 alphabet_end: alphabet_end,
479 alphabets: alphabets,
480 },
481 )
482 .unwrap_or(0)
483}
484
485#[wasm_bindgen]
486pub fn banana_is_valid(
487 word: &str,
488 alphabet_shift: Option<usize>,
489 alphabet_end: Option<usize>,
490 alphabets: Option<Vec<String>>,
491) -> bool {
492 is_valid(
493 word,
494 &DecodeParams {
495 alphabet_shift: alphabet_shift,
496 alphabet_end: alphabet_end,
497 alphabets: alphabets,
498 },
499 )
500 .unwrap_or(false)
501}