use alloc::{vec, vec::Vec};
use crate::{
component::IcalComponent,
param::IcalParam,
prop::{IcalPropKind, IcalPropName},
recur::{IcalRecurDateTime, IcalRecurRule, expand::IcalRecurExpand},
tz::IcalTz,
value::IcalValue,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IcalRecurOccurrence {
pub id: IcalRecurDateTime,
pub start: IcalRecurDateTime,
pub over: Option<usize>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IcalRecurOverride {
pub id: IcalRecurDateTime,
pub start: IcalRecurDateTime,
pub this_and_future: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IcalRecurSet {
pub start: Option<IcalRecurDateTime>,
pub rules: Vec<IcalRecurRule>,
pub dates: Vec<IcalRecurDateTime>,
pub exrules: Vec<IcalRecurRule>,
pub exdates: Vec<IcalRecurDateTime>,
pub overrides: Vec<IcalRecurOverride>,
}
impl IcalRecurSet {
pub fn of_component(component: &IcalComponent<'_>) -> Self {
let mut set = Self::default();
for prop in &component.props {
let IcalPropName::Kind(kind) = prop.name else {
continue;
};
match kind {
IcalPropKind::DtStart => set.start = date_of(&prop.value),
IcalPropKind::RRule => set.rules.extend(rule_of(&prop.value)),
IcalPropKind::ExRule => set.exrules.extend(rule_of(&prop.value)),
IcalPropKind::RDate => set.dates.extend(dates_of(&prop.value)),
IcalPropKind::ExDate => set.exdates.extend(dates_of(&prop.value)),
_ => {}
}
}
set.dates.sort_unstable();
set.dates.dedup();
set.exdates.sort_unstable();
set.exdates.dedup();
set
}
pub fn with_override(&mut self, component: &IcalComponent<'_>) -> &mut Self {
let mut id = None;
let mut start = None;
let mut this_and_future = false;
for prop in &component.props {
let IcalPropName::Kind(kind) = prop.name else {
continue;
};
match kind {
IcalPropKind::RecurrenceId => {
id = date_of(&prop.value);
this_and_future = prop.params.iter().any(|param| {
matches!(param, IcalParam::Range(range) if range.eq_ignore_ascii_case("THISANDFUTURE"))
});
}
IcalPropKind::DtStart => start = date_of(&prop.value),
_ => {}
}
}
if let (Some(id), Some(start)) = (id, start) {
self.overrides.push(IcalRecurOverride {
id,
start,
this_and_future,
});
self.overrides.sort_unstable_by_key(|over| over.id);
}
self
}
pub fn of_uid(components: &[IcalComponent<'_>], uid: &str) -> Self {
let mut set = Self::default();
for component in components {
if uid_of(component) != Some(uid) {
continue;
}
if has(component, IcalPropKind::RecurrenceId) {
set.with_override(component);
} else {
let series = Self::of_component(component);
set.start = series.start;
set.rules = series.rules;
set.dates = series.dates;
set.exrules = series.exrules;
set.exdates = series.exdates;
}
}
set
}
pub fn expand(&self) -> IcalRecurSetExpand<'_> {
self.walk(None)
}
pub fn expand_in_zone(&self, zone: &IcalTz) -> IcalRecurSetExpand<'_> {
self.walk(Some(zone))
}
fn walk(&self, zone: Option<&IcalTz>) -> IcalRecurSetExpand<'_> {
let start = self.start;
let stream = |rule: &IcalRecurRule| {
let expand = IcalRecurExpand::new(rule.clone(), start?);
Some(match zone {
Some(zone) => expand.in_zone(zone.clone()),
None => expand,
})
};
IcalRecurSetExpand {
set: self,
streams: self.rules.iter().filter_map(stream).collect(),
heads: vec![None; self.rules.len()],
primed: false,
literals: {
let mut literals: Vec<IcalRecurDateTime> = start.into_iter().collect();
literals.extend(self.dates.iter().copied());
literals.extend(self.overrides.iter().map(|over| over.id));
literals.sort_unstable();
literals.dedup();
literals
},
literal: 0,
exrules: self.exrules.iter().filter_map(stream).collect(),
exheads: vec![None; self.exrules.len()],
last: None,
}
}
}
pub struct IcalRecurSetExpand<'a> {
set: &'a IcalRecurSet,
streams: Vec<IcalRecurExpand>,
heads: Vec<Option<IcalRecurDateTime>>,
primed: bool,
literals: Vec<IcalRecurDateTime>,
literal: usize,
exrules: Vec<IcalRecurExpand>,
exheads: Vec<Option<IcalRecurDateTime>>,
last: Option<IcalRecurDateTime>,
}
impl Iterator for IcalRecurSetExpand<'_> {
type Item = IcalRecurOccurrence;
fn next(&mut self) -> Option<Self::Item> {
loop {
let id = self.next_id()?;
if self.excluded(id) {
continue;
}
let over = self.set.overrides.iter().position(|over| over.id == id);
let start = match over {
Some(index) => self.set.overrides[index].start,
None => IcalRecurDateTime::from_seconds(id.seconds() + self.shift(id)),
};
return Some(IcalRecurOccurrence { id, start, over });
}
}
}
impl IcalRecurSetExpand<'_> {
fn next_id(&mut self) -> Option<IcalRecurDateTime> {
loop {
if !self.primed {
for (index, stream) in self.streams.iter_mut().enumerate() {
self.heads[index] = stream.next();
}
self.primed = true;
}
let from_rules = self.heads.iter().flatten().min().copied();
let from_literals = self.literals.get(self.literal).copied();
let next = match (from_rules, from_literals) {
(Some(rule), Some(literal)) => rule.min(literal),
(Some(rule), None) => rule,
(None, Some(literal)) => literal,
(None, None) => return None,
};
for (index, head) in self.heads.iter_mut().enumerate() {
if *head == Some(next) {
*head = self.streams[index].next();
}
}
if from_literals == Some(next) {
self.literal += 1;
}
if self.last == Some(next) {
continue;
}
self.last = Some(next);
return Some(next);
}
}
fn excluded(&mut self, id: IcalRecurDateTime) -> bool {
if self.set.exdates.binary_search(&id).is_ok() {
return true;
}
for (index, stream) in self.exrules.iter_mut().enumerate() {
while self.exheads[index].is_none_or(|head| head < id) {
match stream.next() {
Some(next) => self.exheads[index] = Some(next),
None => break,
}
}
if self.exheads[index] == Some(id) {
return true;
}
}
false
}
fn shift(&self, id: IcalRecurDateTime) -> i64 {
self.set
.overrides
.iter()
.rfind(|over| over.this_and_future && over.id <= id)
.map(|over| over.start.seconds() - over.id.seconds())
.unwrap_or(0)
}
}
fn date_of(value: &IcalValue<'_>) -> Option<IcalRecurDateTime> {
let text = match value {
IcalValue::Date(date) => &date.0,
IcalValue::DateTime(date) => &date.0,
IcalValue::DateTimeList(dates) => dates.0.first()?,
_ => return None,
};
IcalRecurDateTime::parse(text).ok()
}
fn dates_of(value: &IcalValue<'_>) -> Vec<IcalRecurDateTime> {
let items: &[_] = match value {
IcalValue::DateTimeList(dates) => &dates.0,
other => return date_of(other).into_iter().collect(),
};
items
.iter()
.filter_map(|item| {
let start = item.split('/').next().unwrap_or(item);
IcalRecurDateTime::parse(start).ok()
})
.collect()
}
fn rule_of(value: &IcalValue<'_>) -> Option<IcalRecurRule> {
let IcalValue::Recur(recur) = value else {
return None;
};
IcalRecurRule::parse(&recur.0).ok()
}
fn uid_of<'a>(component: &'a IcalComponent<'_>) -> Option<&'a str> {
component.props.iter().find_map(|prop| {
if !matches!(prop.name, IcalPropName::Kind(IcalPropKind::Uid)) {
return None;
}
match &prop.value {
IcalValue::Text(text) => Some(&*text.0),
_ => None,
}
})
}
fn has(component: &IcalComponent<'_>, kind: IcalPropKind) -> bool {
component
.props
.iter()
.any(|prop| matches!(prop.name, IcalPropName::Kind(k) if k == kind))
}