use crate::types::{Handle, Vector3};
use crate::xdata::XDataValue;
pub(crate) fn encode_values(
wide: bool,
values: &[XDataValue],
layer_handle: impl Fn(&str) -> u64,
) -> Vec<u8> {
let mut b = Vec::new();
let push_string = |b: &mut Vec<u8>, s: &str| {
b.push(0);
if wide {
let units: Vec<u16> = s.encode_utf16().collect();
b.extend_from_slice(&(units.len() as u16).to_le_bytes());
for u in units {
b.extend_from_slice(&u.to_le_bytes());
}
} else {
let (encoded, _, _) = encoding_rs::WINDOWS_1252.encode(s);
b.push(encoded.len() as u8);
b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&encoded);
}
};
let push_point = |b: &mut Vec<u8>, code: u8, p: &Vector3| {
b.push(code);
b.extend_from_slice(&p.x.to_le_bytes());
b.extend_from_slice(&p.y.to_le_bytes());
b.extend_from_slice(&p.z.to_le_bytes());
};
let push_real = |b: &mut Vec<u8>, code: u8, v: f64| {
b.push(code);
b.extend_from_slice(&v.to_le_bytes());
};
for value in values {
match value {
XDataValue::String(s) => push_string(&mut b, s),
XDataValue::ControlString(s) => {
b.push(2);
b.push(if s == "}" { 1 } else { 0 });
}
XDataValue::LayerName(name) => {
b.push(3);
b.extend_from_slice(&layer_handle(name).to_le_bytes());
}
XDataValue::BinaryData(data) => {
for chunk in data.chunks(255) {
b.push(4);
b.push(chunk.len() as u8);
b.extend_from_slice(chunk);
}
}
XDataValue::Handle(h) => {
b.push(5);
b.extend_from_slice(&h.value().to_le_bytes());
}
XDataValue::Point3D(p) => push_point(&mut b, 10, p),
XDataValue::Position3D(p) => push_point(&mut b, 11, p),
XDataValue::Displacement3D(p) => push_point(&mut b, 12, p),
XDataValue::Direction3D(p) => push_point(&mut b, 13, p),
XDataValue::Real(v) => push_real(&mut b, 40, *v),
XDataValue::Distance(v) => push_real(&mut b, 41, *v),
XDataValue::ScaleFactor(v) => push_real(&mut b, 42, *v),
XDataValue::Integer16(v) => {
b.push(70);
b.extend_from_slice(&v.to_le_bytes());
}
XDataValue::Integer32(v) => {
b.push(71);
b.extend_from_slice(&v.to_le_bytes());
}
}
}
b
}
pub(crate) fn decode_values(
bytes: &[u8],
wide: bool,
layer_name: impl Fn(u64) -> Option<String>,
) -> Option<Vec<XDataValue>> {
let mut values = Vec::new();
let mut i = 0usize;
let read_u16 = |b: &[u8], i: usize| -> Option<u16> {
Some(u16::from_le_bytes([*b.get(i)?, *b.get(i + 1)?]))
};
let read_f64 = |b: &[u8], i: usize| -> Option<f64> {
let s: [u8; 8] = b.get(i..i + 8)?.try_into().ok()?;
Some(f64::from_le_bytes(s))
};
let read_u64 = |b: &[u8], i: usize| -> Option<u64> {
let s: [u8; 8] = b.get(i..i + 8)?.try_into().ok()?;
Some(u64::from_le_bytes(s))
};
while i < bytes.len() {
let code = bytes[i];
i += 1;
match code {
0 => {
let s = if wide {
let n = read_u16(bytes, i)? as usize;
i += 2;
let mut units = Vec::with_capacity(n);
for _ in 0..n {
units.push(read_u16(bytes, i)?);
i += 2;
}
String::from_utf16_lossy(&units)
} else {
let n = *bytes.get(i)? as usize;
i += 1 + 2; let slice = bytes.get(i..i + n)?;
i += n;
encoding_rs::WINDOWS_1252.decode(slice).0.into_owned()
};
values.push(XDataValue::String(s));
}
2 => {
let c = *bytes.get(i)?;
i += 1;
values.push(XDataValue::ControlString(
if c == 1 { "}" } else { "{" }.to_string(),
));
}
3 => {
let h = read_u64(bytes, i)?;
i += 8;
values.push(XDataValue::LayerName(layer_name(h).unwrap_or_default()));
}
4 => {
let n = *bytes.get(i)? as usize;
i += 1;
let slice = bytes.get(i..i + n)?;
i += n;
values.push(XDataValue::BinaryData(slice.to_vec()));
}
5 => {
let h = read_u64(bytes, i)?;
i += 8;
values.push(XDataValue::Handle(Handle::new(h)));
}
10..=13 => {
let x = read_f64(bytes, i)?;
let y = read_f64(bytes, i + 8)?;
let z = read_f64(bytes, i + 16)?;
i += 24;
let p = Vector3::new(x, y, z);
values.push(match code {
10 => XDataValue::Point3D(p),
11 => XDataValue::Position3D(p),
12 => XDataValue::Displacement3D(p),
_ => XDataValue::Direction3D(p),
});
}
40..=42 => {
let v = read_f64(bytes, i)?;
i += 8;
values.push(match code {
40 => XDataValue::Real(v),
41 => XDataValue::Distance(v),
_ => XDataValue::ScaleFactor(v),
});
}
70 => {
let v = i16::from_le_bytes([*bytes.get(i)?, *bytes.get(i + 1)?]);
i += 2;
values.push(XDataValue::Integer16(v));
}
71 => {
let v = i32::from_le_bytes([
*bytes.get(i)?,
*bytes.get(i + 1)?,
*bytes.get(i + 2)?,
*bytes.get(i + 3)?,
]);
i += 4;
values.push(XDataValue::Integer32(v));
}
_ => return None,
}
if i > bytes.len() {
return None;
}
}
Some(values)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Vec<XDataValue> {
vec![
XDataValue::String("survey point 1 — ünïcödé".to_string()),
XDataValue::ControlString("{".to_string()),
XDataValue::Integer16(-42),
XDataValue::Integer32(1_000_000),
XDataValue::Real(3.141592653589793),
XDataValue::Distance(2.5),
XDataValue::ScaleFactor(0.5),
XDataValue::Point3D(Vector3::new(1.0, 2.0, 3.0)),
XDataValue::Position3D(Vector3::new(-4.0, 5.0, -6.0)),
XDataValue::Handle(Handle::new(0xABCDEF)),
XDataValue::BinaryData(vec![0, 1, 2, 250, 255]),
XDataValue::ControlString("}".to_string()),
]
}
fn roundtrip(wide: bool) {
let values = sample();
let bytes = encode_values(wide, &values, |_| 0);
let decoded = decode_values(&bytes, wide, |_| None).expect("decode");
assert_eq!(decoded, values);
}
#[test]
fn roundtrip_wide() {
roundtrip(true);
}
#[test]
fn roundtrip_narrow() {
roundtrip(false);
}
#[test]
fn layer_name_resolves_through_handle() {
let values = vec![XDataValue::LayerName("WALLS".to_string())];
let bytes = encode_values(true, &values, |n| if n == "WALLS" { 7 } else { 0 });
let decoded = decode_values(&bytes, true, |h| {
(h == 7).then(|| "WALLS".to_string())
})
.expect("decode");
assert_eq!(decoded, values);
}
#[test]
fn binary_over_255_bytes_splits_and_rejoins_as_chunks() {
let values = vec![XDataValue::BinaryData((0..300).map(|n| n as u8).collect())];
let bytes = encode_values(true, &values, |_| 0);
let decoded = decode_values(&bytes, true, |_| None).expect("decode");
let mut joined = Vec::new();
for v in &decoded {
if let XDataValue::BinaryData(d) = v {
joined.extend_from_slice(d);
}
}
assert_eq!(joined, (0..300).map(|n| n as u8).collect::<Vec<u8>>());
}
#[test]
fn unknown_code_bails_out() {
assert_eq!(decode_values(&[0x63, 0, 0], true, |_| None), None);
}
}