use alloc::{borrow::Cow, string::String, vec::Vec};
use crate::{
prop::{VcardProp, VcardPropKind},
tree::{cst::VcardCst, line::VcardLine, merge::VcardPropPath},
};
pub(super) struct Instance<'a> {
pub(super) cst: &'a VcardCst<'a>,
pub(super) line: usize,
pub(super) nth: usize,
pub(super) key: String,
pub(super) identity: Option<String>,
pub(super) prop: VcardProp<'a>,
}
impl<'a> Instance<'a> {
pub(super) fn all(cst: &'a VcardCst<'a>) -> Vec<Self> {
let version = cst.version();
let mut instances: Vec<Self> = Vec::new();
for (line, node) in cst.props.iter().enumerate() {
let name = node.name.get();
if is_envelope(name) {
continue;
}
let key = name.to_ascii_uppercase();
let nth = instances
.iter()
.filter(|instance| instance.key == key)
.count();
let identity = Self::identity_of(&key, node);
instances.push(Self {
cst,
line,
nth,
key,
identity,
prop: node.decode(version),
});
}
let repeated: Vec<usize> = instances
.iter()
.enumerate()
.filter(|(at, instance)| {
instance.identity.is_some()
&& instances.iter().enumerate().any(|(index, other)| {
index != *at
&& other.key == instance.key
&& other.identity == instance.identity
})
})
.map(|(at, _)| at)
.collect();
for at in repeated {
instances[at].identity = None;
}
instances
}
pub(super) fn node(&self) -> &'a VcardLine<'a> {
&self.cst.props[self.line]
}
pub(super) fn path(&self) -> VcardPropPath<'a> {
VcardPropPath {
name: Cow::Borrowed(self.node().name.get()),
index: self.nth,
identity: self.identity.clone().map(Cow::Owned),
}
}
pub(super) fn line_eq(&self, other: &Self) -> bool {
let (mut ours, mut theirs) = (Vec::new(), Vec::new());
self.node().write_bytes(&mut ours);
other.node().write_bytes(&mut theirs);
ours == theirs
}
pub(super) fn prop_eq(&self, other: &Self) -> bool {
let ours = self.node();
let theirs = other.node();
self.prop.name == other.prop.name
&& ours.params.len() == theirs.params.len()
&& ours
.params
.iter()
.zip(&theirs.params)
.all(|(ours, theirs)| {
ours.name.get().eq_ignore_ascii_case(theirs.name.get())
&& ours.same_param_as(theirs)
})
&& ours.value.same_value_as(&theirs.value)
}
fn identity_of(key: &str, line: &VcardLine<'_>) -> Option<String> {
let identified = matches!(
key.parse::<VcardPropKind>(),
Ok(VcardPropKind::CalAdrUri
| VcardPropKind::CalUri
| VcardPropKind::Email
| VcardPropKind::FbUrl
| VcardPropKind::Impp
| VcardPropKind::Key
| VcardPropKind::Logo
| VcardPropKind::Member
| VcardPropKind::Photo
| VcardPropKind::Related
| VcardPropKind::SocialProfile
| VcardPropKind::Sound
| VcardPropKind::Source
| VcardPropKind::Tel
| VcardPropKind::Url)
);
identified.then(|| String::from_utf8_lossy(&line.value.raw_bytes()).to_lowercase())
}
}
fn is_envelope(name: &str) -> bool {
name.eq_ignore_ascii_case("VERSION")
|| name.eq_ignore_ascii_case("BEGIN")
|| name.eq_ignore_ascii_case("END")
}