use core::slice::Iter;
use alloc::vec::Vec;
use crate::models::{archived::Archived, class::Class, output_data::OutputData, types::Type};
#[derive(Debug)]
pub enum Property<'a, 'b> {
Object {
class: &'a Class,
name: &'a str,
data: PropertyIterator<'a, 'b>,
},
Group(Vec<Property<'a, 'b>>),
Primitive(&'b OutputData<'a>),
}
#[derive(Debug, Clone)]
pub struct PropertyIterator<'a, 'b> {
object_table: &'b [Archived<'a>],
type_table: &'b [Vec<Type<'a>>],
property_groups: Iter<'b, Vec<OutputData<'a>>>,
}
impl<'a, 'b> PropertyIterator<'a, 'b> {
pub(crate) fn new(
object_table: &'b [Archived<'a>],
type_table: &'b [Vec<Type<'a>>],
root_object_index: usize,
) -> Option<Self> {
let root_object = object_table.get(root_object_index)?;
let Archived::Object {
data: properties, ..
} = root_object
else {
return None;
};
Some(Self {
object_table,
type_table,
property_groups: properties.iter(),
})
}
}
impl<'a, 'b: 'a> PropertyIterator<'a, 'b> {
#[must_use]
pub fn primitives(self) -> Vec<&'b OutputData<'a>> {
self.primitives_with_limits(100, 1_000_000)
}
#[must_use]
pub fn primitives_with_limits(
self,
max_depth: usize,
max_items: usize,
) -> Vec<&'b OutputData<'a>> {
let mut primitives = Vec::new();
let mut processed_items = 0;
let initial_props: Vec<Property<'a, 'b>> = self.collect();
let mut stack: Vec<(Property<'a, 'b>, usize)> =
initial_props.into_iter().map(|p| (p, 0)).collect();
while let Some((prop, depth)) = stack.pop() {
if processed_items >= max_items {
break;
}
if depth >= max_depth {
continue;
}
processed_items += 1;
match prop {
Property::Primitive(p) => primitives.push(p),
Property::Group(mut group) => {
while let Some(child) = group.pop() {
stack.push((child, depth + 1));
}
}
Property::Object { data, .. } => {
let mut nested: Vec<_> = data.collect();
while let Some(child) = nested.pop() {
stack.push((child, depth + 1));
}
}
}
}
primitives.reverse();
primitives
}
}
impl<'a, 'b: 'a> Iterator for PropertyIterator<'a, 'b> {
type Item = Property<'a, 'b>;
fn next(&mut self) -> Option<Self::Item> {
let groups = self.property_groups.next()?;
let mut resolved = Vec::with_capacity(groups.len());
for group in groups {
match group {
OutputData::Object(idx) => {
if let Some(Archived::Object {
class: cls,
data: _,
}) = self.object_table.get(*idx)
&& let Some(Archived::Class(cls)) = self.object_table.get(*cls)
{
let class_name = self
.type_table
.get(cls.name_index)
.and_then(|types| types.first())
.and_then(|t| match t {
Type::String(name) => Some(*name),
_ => None,
})
.unwrap_or("Unknown Class");
let sub_iter =
PropertyIterator::new(self.object_table, self.type_table, *idx)?;
resolved.push(Property::Object {
class: cls,
name: class_name,
data: sub_iter,
});
}
}
prim => resolved.push(Property::Primitive(prim)),
}
}
Some(Property::Group(resolved))
}
}
#[cfg(any(feature = "std", test))]
pub fn print_resolved(iter: PropertyIterator<'_, '_>, indent: usize) {
print_resolved_with_limits(iter, indent, 100, 1_000_000);
}
#[cfg(any(feature = "std", test))]
fn print_resolved_with_limits(
iter: PropertyIterator<'_, '_>,
indent: usize,
max_depth: usize,
max_items: usize,
) {
extern crate std;
use std::println;
let mut stack: Vec<(Property<'_, '_>, usize)> = Vec::new();
let mut items_printed = 0;
for prop in iter {
stack.push((prop, indent));
}
while let Some((prop, current_indent)) = stack.pop() {
if items_printed >= max_items {
println!(
"{:indent$}... (truncated after {max_items} items)",
"",
indent = current_indent
);
break;
}
let depth = (current_indent - indent) / 2;
if depth >= max_depth {
println!(
"{:indent$}... (max depth {max_depth} reached)",
"",
indent = current_indent
);
continue;
}
items_printed += 1;
match prop {
Property::Object {
class: _,
name,
data,
} => {
println!("{:indent$}Object: {:?}", "", name, indent = current_indent);
let children: Vec<_> = data.collect();
for child in children.into_iter().rev() {
stack.push((child, current_indent + 2));
}
}
Property::Group(group) => {
println!("{:indent$}Group:", "", indent = current_indent);
for slot in group.into_iter().rev() {
stack.push((slot, current_indent + 2));
}
}
Property::Primitive(p) => {
println!("{:indent$}Primitive: {:?}", "", p, indent = current_indent);
}
}
}
}