use crate::blocks::{Block, BlockTag};
use crate::error::{MeshError, Result};
pub const TAG_CHAR_BASE: u32 = 0xE0061;
pub const PLANE15_BASE: u32 = 0xF0000;
pub const PLANE16_BASE: u32 = 0x100000;
pub const SUBRANGE_SIZE: u32 = 4096;
pub const MAX_PAYLOAD: usize = 0xFF_FFFF;
#[must_use]
pub fn tag_char(tag: BlockTag) -> char {
char::from_u32(TAG_CHAR_BASE + tag.index() as u32).unwrap_or('\u{E0061}')
}
#[must_use]
pub fn tag_from_char(c: char) -> Option<BlockTag> {
let v = c as u32;
let idx = v.checked_sub(TAG_CHAR_BASE)?;
BlockTag::from_index(u8::try_from(idx).ok()?)
}
fn plane15_char(tag: BlockTag, chunk: u16) -> Result<char> {
if chunk >= SUBRANGE_SIZE as u16 {
return Err(MeshError::Unicode(format!("chunk {chunk} out of range")));
}
let v = PLANE15_BASE + tag.index() as u32 * SUBRANGE_SIZE + chunk as u32;
char::from_u32(v).ok_or_else(|| MeshError::Unicode(format!("invalid char U+{v:X}")))
}
fn plane15_value(c: char, tag: BlockTag) -> Result<u16> {
let v = c as u32;
let base = PLANE15_BASE + tag.index() as u32 * SUBRANGE_SIZE;
if !(base..base + SUBRANGE_SIZE).contains(&v) {
return Err(MeshError::Unicode(format!(
"expected plane-15 length char for {:?}, got U+{:X}",
tag, v
)));
}
Ok((v - base) as u16)
}
fn plane16_char(value: u16) -> Result<char> {
let v = PLANE16_BASE + value as u32;
char::from_u32(v).ok_or_else(|| MeshError::Unicode(format!("invalid char U+{v:X}")))
}
fn plane16_value(c: char) -> Result<u16> {
let v = c as u32;
if !(PLANE16_BASE..=PLANE16_BASE + 0xFFFF).contains(&v) {
return Err(MeshError::Unicode(format!(
"expected plane-16 data char, got U+{v:X}"
)));
}
Ok((v - PLANE16_BASE) as u16)
}
pub fn encode_blocks(blocks: &[Block]) -> Result<String> {
let mut out = String::new();
for block in blocks {
let tag = block.tag();
let payload = block.payload()?;
if payload.len() > MAX_PAYLOAD {
return Err(MeshError::Unicode(format!(
"payload of {} bytes exceeds the 24-bit limit",
payload.len()
)));
}
out.push(tag_char(tag));
let len = payload.len() as u32;
out.push(plane15_char(tag, ((len >> 12) & 0xFFF) as u16)?);
out.push(plane15_char(tag, (len & 0xFFF) as u16)?);
let mut chunks = payload.chunks_exact(2);
for pair in &mut chunks {
out.push(plane16_char(u16::from_be_bytes([pair[0], pair[1]]))?);
}
let rem = chunks.remainder();
if let [last] = rem {
out.push(plane16_char(u16::from_be_bytes([*last, 0]))?);
}
}
Ok(out)
}
pub fn decode_blocks(s: &str) -> Result<Vec<Block>> {
let mut blocks = Vec::new();
let mut chars = s.chars();
while let Some(c) = chars.next() {
let Some(tag) = tag_from_char(c) else { continue };
let hi = next_char(&mut chars).and_then(|c| plane15_value(c, tag))?;
let lo = next_char(&mut chars).and_then(|c| plane15_value(c, tag))?;
let len = ((hi as usize) << 12) | lo as usize;
let count = len.div_ceil(2);
let mut payload = Vec::with_capacity(count * 2);
for _ in 0..count {
let value = next_char(&mut chars).and_then(plane16_value)?;
payload.extend_from_slice(&value.to_be_bytes());
}
payload.truncate(len);
blocks.push(Block::from_payload(tag, &payload)?);
}
Ok(blocks)
}
fn next_char(chars: &mut std::str::Chars<'_>) -> Result<char> {
chars
.next()
.ok_or_else(|| MeshError::Unicode("truncated envelope".into()))
}