use unicode_segmentation::UnicodeSegmentation;
use crate::{
common::{DecoderRXingResult, Result},
Exceptions,
};
use once_cell::sync::Lazy;
const SHIFTA: &str = "\u{FFF0}";
const SHIFTB: &str = "\u{FFF1}";
const SHIFTC: &str = "\u{FFF2}";
const SHIFTD: &str = "\u{FFF3}";
const SHIFTE: &str = "\u{FFF4}";
const TWOSHIFTA: &str = "\u{FFF5}";
const THREESHIFTA: &str = "\u{FFF6}";
const LATCHA: &str = "\u{FFF7}";
const LATCHB: &str = "\u{FFF8}";
const LOCK: &str = "\u{FFF9}";
const ECI: &str = "\u{FFFA}";
const NS: &str = "\u{FFFB}";
const PAD: &str = "\u{FFFC}";
const FS: &str = "\u{001C}";
const GS: &str = "\u{001D}";
const RS: &str = "\u{001E}";
const COUNTRY_BYTES: [u8; 10] = [53, 54, 43, 44, 45, 46, 47, 48, 37, 38];
const SERVICE_CLASS_BYTES: [u8; 10] = [55, 56, 57, 58, 59, 60, 49, 50, 51, 52];
const POSTCODE_2_LENGTH_BYTES: [u8; 6] = [39, 40, 41, 42, 31, 32];
const POSTCODE_2_BYTES: [u8; 30] = [
33, 34, 35, 36, 25, 26, 27, 28, 29, 30, 19, 20, 21, 22, 23, 24, 13, 14, 15, 16, 17, 18, 7, 8,
9, 10, 11, 12, 1, 2,
];
const POSTCODE_3_BYTES: [[u8; 6]; 6] = [
[39, 40, 41, 42, 31, 32],
[33, 34, 35, 36, 25, 26],
[27, 28, 29, 30, 19, 20],
[21, 22, 23, 24, 13, 14],
[15, 16, 17, 18, 7, 8],
[9, 10, 11, 12, 1, 2],
];
static SETS: Lazy<[String; 5]> = Lazy::new(|| {
[
format!("\rABCDEFGHIJKLMNOPQRSTUVWXYZ{ECI}{FS}{GS}{RS}{NS} {PAD}\"#$%&'()*+,-./0123456789:{SHIFTB}{SHIFTC}{SHIFTD}{SHIFTE}{LATCHB}" ),
format!("`abcdefghijklmnopqrstuvwxyz{ECI}{FS}{GS}{RS}{NS}{{{PAD}}}~\u{007F};<=>?[\\]^_ ,./:@!|{PAD}{TWOSHIFTA}{THREESHIFTA}{PAD}{SHIFTA}{SHIFTC}{SHIFTD}{SHIFTE}{LATCHA}" ),
format!("\u{00C0}\u{00C1}\u{00C2}\u{00C3}\u{00C4}\u{00C5}\u{00C6}\u{00C7}\u{00C8}\u{00C9}\u{00CA}\u{00CB}\u{00CC}\u{00CD}\u{00CE}\u{00CF}\u{00D0}\u{00D1}\u{00D2}\u{00D3}\u{00D4}\u{00D5}\u{00D6}\u{00D7}\u{00D8}\u{00D9}\u{00DA}{}{}{}{}{}{}{}{}{}{}{}{}" ,
ECI , FS , GS , RS , NS ,
"\u{00DB}\u{00DC}\u{00DD}\u{00DE}\u{00DF}\u{00AA}\u{00AC}\u{00B1}\u{00B2}\u{00B3}\u{00B5}\u{00B9}\u{00BA}\u{00BC}\u{00BD}\u{00BE}\u{0080}\u{0081}\u{0082}\u{0083}\u{0084}\u{0085}\u{0086}\u{0087}\u{0088}\u{0089}" ,
LATCHA , ' ' , LOCK , SHIFTD , SHIFTE , LATCHB),
format!("\u{00E0}\u{00E1}\u{00E2}\u{00E3}\u{00E4}\u{00E5}\u{00E6}\u{00E7}\u{00E8}\u{00E9}\u{00EA}\u{00EB}\u{00EC}\u{00ED}\u{00EE}\u{00EF}\u{00F0}\u{00F1}\u{00F2}\u{00F3}\u{00F4}\u{00F5}\u{00F6}\u{00F7}\u{00F8}\u{00F9}\u{00FA}{}{}{}{}{}{}{}{}{}{}{}{}" ,
ECI , FS , GS , RS , NS ,
"\u{00FB}\u{00FC}\u{00FD}\u{00FE}\u{00FF}\u{00A1}\u{00A8}\u{00AB}\u{00AF}\u{00B0}\u{00B4}\u{00B7}\u{00B8}\u{00BB}\u{00BF}\u{008A}\u{008B}\u{008C}\u{008D}\u{008E}\u{008F}\u{0090}\u{0091}\u{0092}\u{0093}\u{0094}" ,
LATCHA , ' ' , SHIFTC , LOCK , SHIFTE , LATCHB),
format!("\u{0000}\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{0008}\u{0009}\n\u{000B}\u{000C}\r\u{000E}\u{000F}\u{0010}\u{0011}\u{0012}\u{0013}\u{0014}\u{0015}\u{0016}\u{0017}\u{0018}\u{0019}\u{001A}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}" ,
ECI , PAD , PAD , '\u{001B}' , NS , FS , GS , RS ,
"\u{001F}\u{009F}\u{00A0}\u{00A2}\u{00A3}\u{00A4}\u{00A5}\u{00A6}\u{00A7}\u{00A9}\u{00AD}\u{00AE}\u{00B6}\u{0095}\u{0096}\u{0097}\u{0098}\u{0099}\u{009A}\u{009B}\u{009C}\u{009D}\u{009E}" ,
LATCHA , ' ' , SHIFTC , SHIFTD , LOCK , LATCHB),
]
});
static GRAPHEME_SETS: Lazy<[Vec<&str>; 5]> = Lazy::new(|| {
[
SETS[0].graphemes(true).collect(),
SETS[1].graphemes(true).collect(),
SETS[2].graphemes(true).collect(),
SETS[3].graphemes(true).collect(),
SETS[4].graphemes(true).collect(),
]
});
pub fn decode(bytes: &[u8], mode: u8) -> Result<DecoderRXingResult> {
let mut result = String::with_capacity(144);
match mode {
2 | 3 => {
let postcode = if mode == 2 {
let pc = getPostCode2(bytes);
let ps2Length = getPostCode2Length(bytes) as usize;
if ps2Length > 10 {
return Err(Exceptions::FORMAT);
}
format!("{pc:0>ps2Length$}")
} else {
getPostCode3(bytes)
};
let country = format!("{:0>3}", getCountry(bytes)); let service = format!("{:0>3}", getServiceClass(bytes));
result.push_str(&getMessage(bytes, 10, 84));
if result.starts_with(&format!("[)>{}{}{}", RS, "01", GS)) {
result.insert_str(9, &format!("{postcode}{GS}{country}{GS}{service}{GS}"));
} else {
result.insert_str(0, &format!("{postcode}{GS}{country}{GS}{service}{GS}"));
}
}
4 => {
result.push_str(&getMessage(bytes, 1, 93));
}
5 => {
result.push_str(&getMessage(bytes, 1, 77));
}
_ => {}
}
Ok(DecoderRXingResult::new(
bytes.to_vec(),
result,
Vec::new(),
mode.to_string(),
))
}
fn getBit(bit: u8, bytes: &[u8]) -> u8 {
let bit = bit - 1;
u8::from((bytes[bit as usize / 6] & (1 << (5 - (bit % 6)))) != 0)
}
fn getInt(bytes: &[u8], x: &[u8]) -> u32 {
let mut val: u32 = 0;
for i in 0..x.len() {
val += (getBit(x[i], bytes) as u32) << ((x.len() - i - 1) as u32);
}
val
}
fn getCountry(bytes: &[u8]) -> u32 {
getInt(bytes, &COUNTRY_BYTES)
}
fn getServiceClass(bytes: &[u8]) -> u32 {
getInt(bytes, &SERVICE_CLASS_BYTES)
}
fn getPostCode2Length(bytes: &[u8]) -> u32 {
getInt(bytes, &POSTCODE_2_LENGTH_BYTES)
}
fn getPostCode2(bytes: &[u8]) -> u32 {
getInt(bytes, &POSTCODE_2_BYTES)
}
fn getPostCode3(bytes: &[u8]) -> String {
let mut sb = String::with_capacity(POSTCODE_3_BYTES.len());
let graphemes = SETS[0].graphemes(true).collect::<Vec<_>>();
for p3bytes in &POSTCODE_3_BYTES {
if let Some(c) = graphemes.get(getInt(bytes, p3bytes) as usize) {
sb.push_str(c);
}
}
sb
}
fn getMessage(bytes: &[u8], start: u32, len: u32) -> String {
let mut sb = String::new();
let mut shift = -1;
let mut set = 0;
let mut lastset = 0;
let mut i = start;
while i < start + len {
let Some(c) = GRAPHEME_SETS[set].get(bytes[i as usize] as usize).copied() else {
break;
};
match c {
LATCHA => {
set = 0;
shift = -1;
}
LATCHB => {
set = 1;
shift = -1;
}
SHIFTA | SHIFTB | SHIFTC | SHIFTD | SHIFTE => {
lastset = set;
set = subtract_two_single_char_strings(c, SHIFTA);
shift = 1;
}
TWOSHIFTA => {
lastset = set;
set = 0;
shift = 2;
}
THREESHIFTA => {
lastset = set;
set = 0;
shift = 3;
}
NS => {
i += 1;
let mut nsval = (bytes[i as usize] as u32) << 24;
i += 1;
nsval += (bytes[i as usize] as u32) << 18;
i += 1;
nsval += (bytes[i as usize] as u32) << 12;
i += 1;
nsval += (bytes[i as usize] as u32) << 6;
i += 1;
nsval += bytes[i as usize] as u32;
sb.push_str(&format!("{nsval:0>9}"));
}
LOCK => {
shift = -1;
}
_ => {
sb.push_str(c);
}
}
if shift == 0 {
set = lastset;
}
shift -= 1;
i += 1;
}
String::from(sb.trim_end_matches(PAD))
}
fn subtract_two_single_char_strings(str1: &str, str2: &str) -> usize {
let str1_bytes = str1.as_bytes();
let str2_bytes = str2.as_bytes();
let str1_u16 =
((str1_bytes[0] as usize) << 4) + ((str1_bytes[1] as usize) << 2) + str1_bytes[2] as usize;
let str2_u16 =
((str2_bytes[0] as usize) << 4) + ((str2_bytes[1] as usize) << 2) + str2_bytes[2] as usize;
str1_u16 - str2_u16
}