use std::collections::HashMap;
use crate::{
common::{BitArray, Result},
point, BarcodeFormat, Exceptions, RXingResult, RXingResultMetadataType,
RXingResultMetadataValue,
};
use super::{upc_ean_reader, UPCEANReader, STAND_IN};
#[derive(Default)]
pub struct UPCEANExtension5Support;
impl UPCEANExtension5Support {
const CHECK_DIGIT_ENCODINGS: [usize; 10] =
[0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05];
pub fn decodeRow(
&self,
rowNumber: u32,
row: &BitArray,
extensionStartRange: &[usize; 2],
) -> Result<RXingResult> {
let mut result = String::new();
let end = Self::decodeMiddle(row, extensionStartRange, &mut result)?;
let resultString = result;
let extensionData = Self::parseExtensionString(&resultString);
let mut extensionRXingResult = RXingResult::new(
&resultString,
Vec::new(),
vec![
point(
(extensionStartRange[0] + extensionStartRange[1]) as f32 / 2.0,
rowNumber as f32,
),
point(end as f32, rowNumber as f32),
],
BarcodeFormat::UPC_EAN_EXTENSION,
);
if let Some(ed) = extensionData {
extensionRXingResult.putAllMetadata(ed);
}
Ok(extensionRXingResult)
}
fn decodeMiddle(
row: &BitArray,
startRange: &[usize; 2],
resultString: &mut String,
) -> Result<u32> {
let mut counters = [0_u32; 4];
let end = row.get_size();
let mut rowOffset = startRange[1];
let mut lgPatternFound = 0;
let mut x = 0;
while x < 5 && rowOffset < end {
let bestMatch = STAND_IN.decodeDigit(
row,
&mut counters,
rowOffset,
&upc_ean_reader::L_AND_G_PATTERNS,
)?;
resultString
.push(char::from_u32('0' as u32 + bestMatch as u32 % 10).ok_or(Exceptions::PARSE)?);
rowOffset += counters.iter().sum::<u32>() as usize;
if bestMatch >= 10 {
lgPatternFound |= 1 << (4 - x);
}
if x != 4 {
rowOffset = row.getNextSet(rowOffset);
rowOffset = row.getNextUnset(rowOffset);
}
x += 1;
}
if resultString.chars().count() != 5 {
return Err(Exceptions::NOT_FOUND);
}
let checkDigit = Self::determineCheckDigit(lgPatternFound)?;
if Self::extensionChecksum(resultString).ok_or(Exceptions::ILLEGAL_ARGUMENT)?
!= checkDigit as u32
{
return Err(Exceptions::NOT_FOUND);
}
Ok(rowOffset as u32)
}
fn extensionChecksum(s: &str) -> Option<u32> {
let s = s.chars().collect::<Vec<_>>();
let length = s.len();
let mut sum = 0;
let mut i = length as isize - 2;
while i >= 0 {
sum += *s.get(i as usize)? as u32 - '0' as u32;
i -= 2;
}
sum *= 3;
let mut i = length as isize - 1;
while i >= 0 {
sum += *s.get(i as usize)? as u32 - '0' as u32;
i -= 2;
}
sum *= 3;
Some(sum % 10)
}
fn determineCheckDigit(lgPatternFound: usize) -> Result<usize> {
for d in 0..10 {
if lgPatternFound == Self::CHECK_DIGIT_ENCODINGS[d] {
return Ok(d);
}
}
Err(Exceptions::NOT_FOUND)
}
fn parseExtensionString(
raw: &str,
) -> Option<HashMap<RXingResultMetadataType, RXingResultMetadataValue>> {
if raw.chars().count() != 5 {
return None;
}
let value = Self::parseExtension5String(raw)?;
let mut result = HashMap::new();
result.insert(
RXingResultMetadataType::SUGGESTED_PRICE,
RXingResultMetadataValue::SuggestedPrice(value),
);
Some(result)
}
fn parseExtension5String(raw: &str) -> Option<String> {
let currency = match raw.chars().next()? {
'0' => "£",
'5' => "$",
'9' => {
match raw {
"90000" =>
{
return None
}
"99991" =>
{
return Some("0.00".to_owned())
}
"99990" => return Some("Used".to_owned()),
_ => {}
}
""
}
_ => "",
};
let rawAmount = raw[1..].parse::<i32>().ok()?;
let unitsString = (rawAmount / 100).to_string();
let hundredths = rawAmount % 100;
let hundredthsString = if hundredths < 10 {
format!("0{hundredths}")
} else {
hundredths.to_string()
};
Some(format!("{currency}{unitsString}.{hundredthsString}"))
}
}