use std::fmt::Write;
use cranpose_services::{SaveableScope, Saver};
use super::{BackStack, Entry};
const VERSION: &str = "2";
const VERSION_ONE: &str = "1";
pub(super) fn encode<R>(stack: &BackStack<R>, saver: &Saver<R>) -> String {
let mut saved = format!("{VERSION} {}", stack.next_id);
encode_entries(&mut saved, &stack.entries, saver);
let _ = write!(saved, " {}", stack.saved.len());
for segment in &stack.saved {
encode_entries(&mut saved, segment, saver);
}
saved
}
fn encode_entries<R>(saved: &mut String, entries: &[Entry<R>], saver: &Saver<R>) {
let _ = write!(saved, " {}", entries.len());
for entry in entries {
let route = saver.save(&entry.route);
let _ = write!(saved, " {} {} {route}", entry.id, route.len());
}
}
pub(super) fn decode<R>(
saved: &str,
saver: &Saver<R>,
saveables: &SaveableScope,
) -> Option<BackStack<R>> {
if let Some(rest) = saved.strip_prefix(VERSION_ONE) {
let (next_id, rest) = rest.strip_prefix(' ')?.split_once(' ')?;
let next_id: u64 = next_id.parse().ok()?;
let mut raw = Vec::new();
let mut rest = rest;
while let Some((id, route, tail)) = entry(rest) {
raw.push((id, route));
rest = tail;
}
let entries = readable_prefix(&raw, next_id, saver, saveables);
return stack(next_id, entries, Vec::new(), saveables);
}
let rest = saved.strip_prefix(VERSION)?.strip_prefix(' ')?;
let (next_id, rest) = rest.split_once(' ')?;
let next_id: u64 = next_id.parse().ok()?;
let (raw, mut rest) = entries(rest)?;
let main = readable_prefix(&raw, next_id, saver, saveables);
let (count, tail) = number(rest)?;
rest = tail;
let mut segments = Vec::new();
for _ in 0..count {
let (raw, tail) = entries(rest)?;
rest = tail;
let segment = readable_prefix(&raw, next_id, saver, saveables);
if !segment.is_empty() && segment.len() == raw.len() {
segments.push(segment);
}
}
stack(next_id, main, segments, saveables)
}
fn stack<R>(
next_id: u64,
entries: Vec<Entry<R>>,
saved: Vec<Vec<Entry<R>>>,
saveables: &SaveableScope,
) -> Option<BackStack<R>> {
(!entries.is_empty()).then(|| BackStack {
entries,
saved,
next_id,
saveables: saveables.clone(),
})
}
fn readable_prefix<R>(
raw: &[(u64, &str)],
next_id: u64,
saver: &Saver<R>,
saveables: &SaveableScope,
) -> Vec<Entry<R>> {
let mut entries: Vec<Entry<R>> = Vec::with_capacity(raw.len());
for &(id, route) in raw {
let in_order = id < next_id && entries.last().is_none_or(|last| last.id < id);
let Some(route) = saver.restore(route).filter(|_| in_order) else {
break;
};
entries.push(Entry::new(id, route, saveables));
}
entries
}
fn entries(saved: &str) -> Option<(Vec<(u64, &str)>, &str)> {
let (count, mut rest) = number(saved)?;
let mut raw = Vec::with_capacity(count.min(64));
for _ in 0..count {
let (id, route, tail) = entry(rest)?;
raw.push((id, route));
rest = tail;
}
Some((raw, rest))
}
fn number(saved: &str) -> Option<(usize, &str)> {
let (number, rest) = saved.split_once(' ').unwrap_or((saved, ""));
Some((number.parse().ok()?, rest))
}
fn entry(saved: &str) -> Option<(u64, &str, &str)> {
let (id, rest) = saved.split_once(' ')?;
let (length, rest) = rest.split_once(' ')?;
let (route, rest) = rest.split_at_checked(length.parse().ok()?)?;
let rest = match rest.strip_prefix(' ') {
Some(rest) => rest,
None if rest.is_empty() => rest,
None => return None,
};
Some((id.parse().ok()?, route, rest))
}