use std::collections::HashMap;
use crate::iwa::archive::{Archive, ArchiveObject, RawMessage};
use crate::iwa::bundle::Bundle;
use crate::iwa::{Error, Result};
#[derive(Debug, Clone)]
pub struct ObjectIndexEntry {
pub id: u64,
pub fragment_name: String,
pub data_offset: u64,
pub data_length: u64,
pub object_type: u32,
}
#[derive(Debug, Clone)]
pub struct ObjectIndex {
entries: HashMap<u64, ObjectIndexEntry>,
fragment_objects: HashMap<String, Vec<u64>>,
}
impl Default for ObjectIndex {
fn default() -> Self {
Self::new()
}
}
impl ObjectIndex {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
fragment_objects: HashMap::new(),
}
}
pub fn from_bundle(bundle: &Bundle) -> Result<Self> {
let mut index = Self::new();
if let Some(metadata_archive) = bundle.get_archive("Index/Metadata.iwa") {
index.parse_metadata_archive(metadata_archive)?;
}
for (archive_name, archive) in bundle.archives() {
index.parse_archive(archive_name, archive)?;
}
Ok(index)
}
fn parse_metadata_archive(&mut self, archive: &Archive) -> Result<()> {
for object in &archive.objects {
if let Some(identifier) = object.archive_info.identifier {
self.parse_object_references(identifier, object)?;
}
}
Ok(())
}
fn parse_archive(&mut self, archive_name: &str, archive: &Archive) -> Result<()> {
for object in &archive.objects {
if let Some(identifier) = object.archive_info.identifier {
let object_type = object.messages.first()
.map(|msg| msg.type_)
.unwrap_or(0);
let entry = ObjectIndexEntry {
id: identifier,
fragment_name: archive_name.to_string(),
data_offset: 0, data_length: 0, object_type,
};
self.entries.insert(identifier, entry);
self.fragment_objects.entry(archive_name.to_string())
.or_default()
.push(identifier);
}
}
Ok(())
}
fn parse_object_references(&mut self, _object_id: u64, _object: &ArchiveObject) -> Result<()> {
Ok(())
}
pub fn get_entry(&self, id: u64) -> Option<&ObjectIndexEntry> {
self.entries.get(&id)
}
pub fn get_fragment_objects(&self, fragment_name: &str) -> Option<&Vec<u64>> {
self.fragment_objects.get(fragment_name)
}
pub fn all_object_ids(&self) -> Vec<u64> {
self.entries.keys().cloned().collect()
}
pub fn all_entries(&self) -> Vec<&ObjectIndexEntry> {
self.entries.values().collect()
}
pub fn find_objects_by_type(&self, object_type: u32) -> Vec<&ObjectIndexEntry> {
self.entries.values()
.filter(|entry| entry.object_type == object_type)
.collect()
}
pub fn resolve_object(&self, bundle: &Bundle, object_id: u64) -> Result<Option<ResolvedObject>> {
let Some(entry) = self.get_entry(object_id) else {
return Ok(None);
};
let Some(archive) = bundle.get_archive(&entry.fragment_name) else {
return Err(Error::Bundle(format!("Archive {} not found", entry.fragment_name)));
};
for object in &archive.objects {
if object.archive_info.identifier == Some(object_id) {
return Ok(Some(ResolvedObject {
id: object_id,
archive_info: object.archive_info.clone(),
messages: object.messages.clone(),
}));
}
}
Ok(None)
}
}
#[derive(Debug, Clone)]
pub struct ResolvedObject {
pub id: u64,
pub archive_info: crate::iwa::archive::ArchiveInfo,
pub messages: Vec<RawMessage>,
}
impl ResolvedObject {
pub fn primary_message_type(&self) -> Option<u32> {
self.messages.first().map(|msg| msg.type_)
}
pub fn message_types(&self) -> Vec<u32> {
self.messages.iter().map(|msg| msg.type_).collect()
}
}
#[derive(Debug, Clone)]
pub struct ReferenceGraph {
incoming_refs: HashMap<u64, Vec<u64>>,
outgoing_refs: HashMap<u64, Vec<u64>>,
}
impl Default for ReferenceGraph {
fn default() -> Self {
Self::new()
}
}
impl ReferenceGraph {
pub fn new() -> Self {
Self {
incoming_refs: HashMap::new(),
outgoing_refs: HashMap::new(),
}
}
pub fn add_reference(&mut self, source_id: u64, target_id: u64) {
self.outgoing_refs.entry(source_id)
.or_default()
.push(target_id);
self.incoming_refs.entry(target_id)
.or_default()
.push(source_id);
}
pub fn get_incoming_refs(&self, object_id: u64) -> Option<&Vec<u64>> {
self.incoming_refs.get(&object_id)
}
pub fn get_outgoing_refs(&self, object_id: u64) -> Option<&Vec<u64>> {
self.outgoing_refs.get(&object_id)
}
pub fn all_objects(&self) -> std::collections::HashSet<u64> {
let mut all = std::collections::HashSet::new();
all.extend(self.incoming_refs.keys());
all.extend(self.outgoing_refs.keys());
all
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_object_index_creation() {
let index = ObjectIndex::new();
assert!(index.entries.is_empty());
assert!(index.fragment_objects.is_empty());
}
#[test]
fn test_object_index_entry() {
let entry = ObjectIndexEntry {
id: 123,
fragment_name: "Document.iwa".to_string(),
data_offset: 100,
data_length: 200,
object_type: 42,
};
assert_eq!(entry.id, 123);
assert_eq!(entry.fragment_name, "Document.iwa");
assert_eq!(entry.object_type, 42);
}
#[test]
fn test_reference_graph() {
let mut graph = ReferenceGraph::new();
graph.add_reference(1, 2);
graph.add_reference(1, 3);
graph.add_reference(2, 3);
assert_eq!(graph.get_outgoing_refs(1), Some(&vec![2, 3]));
assert_eq!(graph.get_incoming_refs(3), Some(&vec![1, 2]));
assert_eq!(graph.get_incoming_refs(1), None);
}
}