use alloc::string::String;
use alloc::vec::Vec;
use geometry_cs::Cartesian;
use geometry_io_wkb::{ByteOrder, WriteWkb};
use geometry_model::DynGeometry;
use geometry_srid::Srid;
use geometry_trait::Geometry;
use crate::ewkb::{Ewkb, from_ewkb, to_ewkb};
use crate::ewkb_error::EwkbError;
const UPPER: &[u8; 16] = b"0123456789ABCDEF";
const fn digit(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
fn decode(s: &str) -> Result<Vec<u8>, EwkbError> {
let src = s.as_bytes();
if let Some(index) = src.iter().position(|&b| digit(b).is_none()) {
return Err(EwkbError::InvalidHex { index });
}
if src.len() % 2 != 0 {
return Err(EwkbError::InvalidHex {
index: src.len() - 1,
});
}
let mut out = Vec::with_capacity(src.len() / 2);
for pair in src.chunks_exact(2) {
let (hi, lo) = (digit(pair[0]), digit(pair[1]));
match (hi, lo) {
(Some(hi), Some(lo)) => out.push((hi << 4) | lo),
_ => unreachable!("every byte passed the scan above"),
}
}
Ok(out)
}
fn encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(UPPER[usize::from(b >> 4)] as char);
out.push(UPPER[usize::from(b & 0x0F)] as char);
}
out
}
pub fn from_ewkb_hex(s: &str) -> Result<Ewkb<DynGeometry<f64, Cartesian>>, EwkbError> {
from_ewkb(&decode(s)?)
}
#[must_use]
pub fn to_ewkb_hex<G: Geometry + WriteWkb>(g: &G, srid: Option<Srid>, order: ByteOrder) -> String {
encode(&to_ewkb(g, srid, order))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn odd_length_names_the_unpaired_digit() {
assert_eq!(decode("ABC"), Err(EwkbError::InvalidHex { index: 2 }));
}
#[test]
fn non_hex_names_its_own_offset() {
assert_eq!(decode("00GG"), Err(EwkbError::InvalidHex { index: 2 }));
assert_eq!(decode("0G"), Err(EwkbError::InvalidHex { index: 1 }));
}
#[test]
fn a_bad_digit_outranks_an_odd_length() {
assert_eq!(decode("0Z1"), Err(EwkbError::InvalidHex { index: 1 }));
}
#[test]
fn the_offset_is_always_a_char_boundary() {
let s = "0é";
assert_eq!(decode(s), Err(EwkbError::InvalidHex { index: 1 }));
assert_eq!(s.get(..1), Some("0"));
}
#[test]
fn empty_decodes_to_empty() {
assert_eq!(decode(""), Ok(Vec::new()));
assert_eq!(encode(&[]), "");
}
#[test]
fn encode_is_uppercase_per_row_a11() {
assert_eq!(encode(&[0x00, 0x0F, 0xAB, 0xFF]), "000FABFF");
}
#[test]
fn round_trips_both_cases() {
let bytes = [0x01u8, 0xE6, 0x10, 0xAB, 0xFF, 0x00];
let up = encode(&bytes);
assert_eq!(decode(&up).unwrap(), bytes);
assert_eq!(decode(&up.to_lowercase()).unwrap(), bytes);
}
}