use crate::error::N3gbError;
use crate::index::constants::{IDENTIFIER_VERSION, SCALE_FACTOR};
use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
pub fn generate_hex_identifier(easting: f64, northing: f64, zoom_level: u8) -> String {
let easting_int = (easting * SCALE_FACTOR as f64).round() as u64;
let northing_int = (northing * SCALE_FACTOR as f64).round() as u64;
let mut buf = [0u8; 19];
buf[0] = IDENTIFIER_VERSION;
buf[1..9].copy_from_slice(&easting_int.to_be_bytes());
buf[9..17].copy_from_slice(&northing_int.to_be_bytes());
buf[17] = zoom_level;
buf[18] = buf[..18].iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
URL_SAFE_NO_PAD.encode(buf)
}
pub fn decode_hex_identifier(identifier: &str) -> Result<(u8, f64, f64, u8), N3gbError> {
let binary_data = URL_SAFE_NO_PAD
.decode(identifier)
.map_err(|_| N3gbError::Base64DecodeError)?;
if binary_data.len() != 19 {
return Err(N3gbError::InvalidIdentifierLength);
}
let (data, checksum_bytes) = binary_data.split_at(18);
let checksum = checksum_bytes[0];
let calculated_checksum: u8 = data.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
if calculated_checksum != checksum {
return Err(N3gbError::InvalidChecksum);
}
let version = data[0];
let easting_bytes: [u8; 8] = data[1..9]
.try_into()
.map_err(|_| N3gbError::InvalidIdentifierLength)?;
let northing_bytes: [u8; 8] = data[9..17]
.try_into()
.map_err(|_| N3gbError::InvalidIdentifierLength)?;
let easting_int = u64::from_be_bytes(easting_bytes);
let northing_int = u64::from_be_bytes(northing_bytes);
let zoom = data[17];
if version != IDENTIFIER_VERSION {
return Err(N3gbError::UnsupportedVersion(version));
}
let easting = easting_int as f64 / SCALE_FACTOR as f64;
let northing = northing_int as f64 / SCALE_FACTOR as f64;
Ok((version, easting, northing, zoom))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_and_decode_identifier() -> Result<(), N3gbError> {
let easting = 252086.123;
let northing = 847702.123;
let zoom = 10;
let id = generate_hex_identifier(easting, northing, zoom);
assert!(!id.is_empty());
let (version, decoded_e, decoded_n, decoded_z) = decode_hex_identifier(&id)?;
assert_eq!(version, IDENTIFIER_VERSION);
assert!((decoded_e - easting).abs() < 0.001);
assert!((decoded_n - northing).abs() < 0.001);
assert_eq!(decoded_z, zoom);
Ok(())
}
#[test]
fn test_invalid_identifier() {
let result = decode_hex_identifier("invalid");
assert!(result.is_err());
}
#[test]
fn test_identifier_output() {
let id = generate_hex_identifier(457500.0, 340000.0, 10);
println!("Generated identifier: {}", id);
println!("Length: {} chars", id.len());
let (version, easting, northing, zoom) = decode_hex_identifier(&id).unwrap();
println!(
"Decoded: version={}, easting={}, northing={}, zoom={}",
version, easting, northing, zoom
);
}
}