use std::{borrow::Cow, collections::HashMap, ops::DerefMut};
use bstr::{BStr, BString, ByteVec};
use crate::{
file::{
self,
mutable::{escape_value, Whitespace},
Section, SectionId,
},
lookup,
parse::{section, Event},
value::{normalize_bstr, normalize_bstring},
};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) struct EntryData {
pub(crate) section_id: SectionId,
pub(crate) offset_index: usize,
}
#[derive(PartialEq, Eq, Debug)]
pub struct MultiValueMut<'borrow, 'lookup, 'event> {
pub(crate) section: &'borrow mut HashMap<SectionId, Section<'event>>,
pub(crate) key: section::Key<'lookup>,
pub(crate) indices_and_sizes: Vec<EntryData>,
pub(crate) offsets: HashMap<SectionId, Vec<usize>>,
}
impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> {
pub fn get(&self) -> Result<Vec<Cow<'_, BStr>>, lookup::existing::Error> {
let mut expect_value = false;
let mut values = Vec::new();
let mut concatenated_value = BString::default();
for EntryData {
section_id,
offset_index,
} in &self.indices_and_sizes
{
let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index);
for event in &self.section.get(section_id).expect("known section id").as_ref()[offset..offset + size] {
match event {
Event::SectionKey(section_key) if *section_key == self.key => expect_value = true,
Event::Value(v) if expect_value => {
expect_value = false;
values.push(normalize_bstr(v.as_ref()));
}
Event::ValueNotDone(v) if expect_value => concatenated_value.push_str(v.as_ref()),
Event::ValueDone(v) if expect_value => {
expect_value = false;
concatenated_value.push_str(v.as_ref());
values.push(normalize_bstring(std::mem::take(&mut concatenated_value)));
}
_ => (),
}
}
}
if values.is_empty() {
return Err(lookup::existing::Error::KeyMissing);
}
Ok(values)
}
#[must_use]
pub fn len(&self) -> usize {
self.indices_and_sizes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.indices_and_sizes.is_empty()
}
pub fn set_string_at(&mut self, index: usize, value: impl AsRef<str>) {
self.set_at(index, value.as_ref());
}
pub fn set_at<'a>(&mut self, index: usize, value: impl Into<&'a BStr>) {
let EntryData {
section_id,
offset_index,
} = self.indices_and_sizes[index];
MultiValueMut::set_value_inner(
&self.key,
&mut self.offsets,
&mut self.section.get_mut(§ion_id).expect("known section id").body,
section_id,
offset_index,
value.into(),
);
}
pub fn set_values<'a, Iter, Item>(&mut self, values: Iter)
where
Iter: IntoIterator<Item = Item>,
Item: Into<&'a BStr>,
{
for (
EntryData {
section_id,
offset_index,
},
value,
) in self.indices_and_sizes.iter().zip(values)
{
Self::set_value_inner(
&self.key,
&mut self.offsets,
&mut self.section.get_mut(section_id).expect("known section id").body,
*section_id,
*offset_index,
value.into(),
);
}
}
pub fn set_all<'a>(&mut self, input: impl Into<&'a BStr>) {
let input = input.into();
for EntryData {
section_id,
offset_index,
} in &self.indices_and_sizes
{
Self::set_value_inner(
&self.key,
&mut self.offsets,
&mut self.section.get_mut(section_id).expect("known section id").body,
*section_id,
*offset_index,
input,
);
}
}
fn set_value_inner<'a: 'event>(
key: §ion::Key<'lookup>,
offsets: &mut HashMap<SectionId, Vec<usize>>,
section: &mut file::section::Body<'event>,
section_id: SectionId,
offset_index: usize,
value: &BStr,
) {
let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index);
let whitespace = Whitespace::from_body(section);
let section = section.as_mut();
section.drain(offset..offset + size);
let key_sep_events = whitespace.key_value_separators();
MultiValueMut::set_offset(offsets, section_id, offset_index, 2 + key_sep_events.len());
section.insert(offset, Event::Value(escape_value(value).into()));
section
.splice(offset..offset, key_sep_events.into_iter().rev())
.for_each(|_| {});
section.insert(offset, Event::SectionKey(key.to_owned()));
}
pub fn delete(&mut self, index: usize) {
let EntryData {
section_id,
offset_index,
} = &self.indices_and_sizes[index];
let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index);
if size == 0 {
return;
}
self.section
.get_mut(section_id)
.expect("known section id")
.body
.as_mut()
.drain(offset..offset + size);
Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0);
self.indices_and_sizes.remove(index);
}
pub fn delete_all(&mut self) {
for EntryData {
section_id,
offset_index,
} in &self.indices_and_sizes
{
let (offset, size) = MultiValueMut::index_and_size(&self.offsets, *section_id, *offset_index);
if size == 0 {
continue;
}
self.section
.get_mut(section_id)
.expect("known section id")
.body
.as_mut()
.drain(offset..offset + size);
Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0);
}
self.indices_and_sizes.clear();
}
fn index_and_size(
offsets: &'lookup HashMap<SectionId, Vec<usize>>,
section_id: SectionId,
offset_index: usize,
) -> (usize, usize) {
offsets
.get(§ion_id)
.expect("known section id")
.iter()
.take(offset_index + 1)
.fold((0, 0), |(total_ofs, ofs), size| (total_ofs + ofs, *size))
}
fn set_offset(
offsets: &mut HashMap<SectionId, Vec<usize>>,
section_id: SectionId,
offset_index: usize,
value: usize,
) {
*offsets
.get_mut(§ion_id)
.expect("known section id")
.get_mut(offset_index)
.unwrap()
.deref_mut() = value;
}
}