use std::collections::HashMap;
use bstr::{BStr, BString, ByteSlice};
use crate::{
File,
file::{self, SectionId, SectionLookup},
lookup,
parse::section,
};
impl File {
pub(crate) fn push_section_internal(&mut self, mut section: file::SectionData) -> SectionId {
let new_section_id = self.allocate_section_id();
section.id = new_section_id;
let lookup_name = section::Name(section.header.name.to_bstring_in(&self.backing));
let subsection_name = section
.header
.subsection_name
.as_ref()
.map(|name| name.value_in(&self.backing).to_owned());
self.sections.insert(new_section_id, section);
let lookup = self.section_lookup_tree.entry(lookup_name).or_default();
match subsection_name {
Some(name) => lookup.by_subsection.entry(name).or_default().push(new_section_id),
None => lookup.without_subsection.push(new_section_id),
}
self.section_order.push(new_section_id);
new_section_id
}
pub(crate) fn insert_section_after(&mut self, mut section: file::SectionData, before: SectionId) -> SectionId {
let before_order = self.section_order_position(before);
let new_section_id = self.allocate_section_id();
section.id = new_section_id;
let lookup_name = section::Name(section.header.name.to_bstring_in(&self.backing));
let subsection_name = section
.header
.subsection_name
.as_ref()
.map(|name| name.value_in(&self.backing).to_owned());
self.sections.insert(new_section_id, section);
self.section_order.insert(before_order + 1, new_section_id);
let order = self.section_order_position(new_section_id);
let earlier_sections = &self.section_order[..order];
let lookup = self.section_lookup_tree.entry(lookup_name).or_default();
let ids = match subsection_name {
Some(name) => lookup.by_subsection.entry(name).or_default(),
None => &mut lookup.without_subsection,
};
insert_id_in_order(ids, earlier_sections, new_section_id);
new_section_id
}
pub(crate) fn section_ids_by_name_and_subname<'a>(
&'a self,
section_name: &'a str,
subsection_name: Option<&BStr>,
) -> Result<impl ExactSizeIterator<Item = SectionId> + DoubleEndedIterator + 'a, lookup::existing::Error> {
let section_name = section::Name::from_str_unchecked(section_name);
let lookup = self
.section_lookup_tree
.get(§ion_name)
.ok_or(lookup::existing::Error::SectionMissing)?;
match subsection_name {
Some(name) => lookup.by_subsection.get(name),
None => (!lookup.without_subsection.is_empty()).then_some(&lookup.without_subsection),
}
.ok_or(lookup::existing::Error::SubSectionMissing)
.map(|ids| ids.iter().copied())
}
pub(crate) fn section_ids_by_name<'a>(
&'a self,
section_name: &str,
) -> Result<impl Iterator<Item = SectionId> + 'a + use<'a>, lookup::existing::Error> {
let lookup_name = section::Name::from_str_unchecked(section_name);
let lookup = self
.section_lookup_tree
.get(&lookup_name)
.ok_or(lookup::existing::Error::SectionMissing)?;
let mut ids = Vec::with_capacity(self.section_order.len());
ids.extend_from_slice(&lookup.without_subsection);
ids.extend(lookup.by_subsection.values().flatten().copied());
Ok(self.section_order.iter().filter(move |id| ids.contains(id)).copied())
}
fn allocate_section_id(&mut self) -> SectionId {
let id = SectionId(self.next_section_id);
self.next_section_id += 1;
id
}
pub(crate) fn section_order_position(&self, id: SectionId) -> usize {
self.section_order
.iter()
.position(|candidate| *candidate == id)
.expect("each section-id is present in section order")
}
}
pub(crate) fn remove_section_id_from_lookup(
lookup_tree: &mut HashMap<section::Name, SectionLookup>,
section_name: §ion::Name,
subsection_name: Option<&BStr>,
section_id: SectionId,
) {
let lookup = lookup_tree
.get_mut(section_name)
.expect("lookup cache contains the section to be changed");
match subsection_name {
Some(name) => {
let ids = lookup
.by_subsection
.get_mut(name)
.expect("lookup cache contains the subsection to be changed");
remove_id(ids, section_id);
if ids.is_empty() {
lookup.by_subsection.remove(name);
}
}
None => remove_id(&mut lookup.without_subsection, section_id),
}
if lookup.without_subsection.is_empty() && lookup.by_subsection.is_empty() {
lookup_tree.remove(section_name);
}
}
pub(crate) fn set_section_header(
section_data: &mut file::SectionData,
backing: &[u8],
lookup_tree: &mut HashMap<section::Name, SectionLookup>,
section_order: &[SectionId],
header: section::HeaderData,
) {
let old_name = section::Name(section_data.header.name.to_bstring_in(backing));
let old_subsection_name = section_data
.header
.subsection_name
.as_ref()
.map(|name| name.value_in(backing).to_owned());
let new_name = section::Name(header.name.to_bstring_in(backing));
let new_subsection_name = header
.subsection_name
.as_ref()
.map(|name| name.value_in(backing).to_owned());
if old_name != new_name || old_subsection_name != new_subsection_name {
remove_section_id_from_lookup(
lookup_tree,
&old_name,
old_subsection_name.as_ref().map(|name| name.as_bstr()),
section_data.id,
);
insert_section_id_into_lookup(
lookup_tree,
section_order,
new_name,
new_subsection_name,
section_data.id,
);
}
section_data.header = header;
}
pub(crate) fn insert_section_id_into_lookup(
lookup_tree: &mut HashMap<section::Name, SectionLookup>,
section_order: &[SectionId],
section_name: section::Name,
subsection_name: Option<BString>,
section_id: SectionId,
) {
let order = section_order
.iter()
.position(|id| *id == section_id)
.expect("section-id is present in section order");
let lookup = lookup_tree.entry(section_name).or_default();
let earlier_sections = §ion_order[..order];
let ids = match subsection_name {
Some(name) => lookup.by_subsection.entry(name).or_default(),
None => &mut lookup.without_subsection,
};
insert_id_in_order(ids, earlier_sections, section_id);
}
fn insert_id_in_order(ids: &mut Vec<SectionId>, earlier_sections: &[SectionId], section_id: SectionId) {
let position = ids.partition_point(|id| earlier_sections.contains(id));
ids.insert(position, section_id);
}
fn remove_id(ids: &mut Vec<SectionId>, section_id: SectionId) {
let position = ids
.iter()
.position(|candidate| *candidate == section_id)
.expect("lookup cache contains the section-id to be changed");
ids.remove(position);
}