1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use crate::ir::{
    ExactDate, ExactDateTime, ExactEvent, ExactRange, ExactRecord, ExactTime, TimeZoneChoice,
};
use anyhow::{anyhow, Result};
use chrono::LocalResult::{Single, self};
use chrono::{prelude as cr, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use chrono::{Local, TimeZone, Utc};
use icalendar as ical;
use icalendar::{Component, EventLike};
use uuid::Uuid;

impl ExactTime {
    pub fn to_chrono(self) -> Result<NaiveTime> {
        match NaiveTime::from_hms_opt(self.hour, self.minute, self.second) {
            None => Err(anyhow!(
                "Invalid time: {}:{}:{}",
                self.hour,
                self.minute,
                self.second
            )),
            Some(res) => Ok(res),
        }
    }
}

impl ExactDate {
    pub fn to_chrono(self) -> Result<NaiveDate> {
        match NaiveDate::from_ymd_opt(self.year, self.month, self.day) {
            None => Err(anyhow!(
                "Invalid date: {}-{}-{}",
                self.year,
                self.month,
                self.day
            )),
            Some(res) => Ok(res),
        }
    }
}

impl ExactDateTime {
    pub fn to_chrono(&self) -> Result<cr::DateTime<Utc>> {
        let baset = NaiveDateTime::new(self.date.to_chrono()?, self.time.to_chrono()?);
        match self.tz {
            TimeZoneChoice::Local => {
                let t = Local.from_local_datetime(&baset);
                match t {
                    Single(t) => Ok(t.with_timezone(&Utc)),
                    _ => Err(anyhow!("Error processing DateTime: {}", baset)),
                }
            }
            TimeZoneChoice::Utc => Ok(Utc.from_utc_datetime(&baset)),
        }
    }

    pub fn from_chrono(t: cr::DateTime<Utc>) -> Self {
        ExactDateTime {
            date: ExactDate {
                year: t.year(),
                month: t.month(),
                day: t.day(),
            },
            time: ExactTime {
                hour: t.hour(),
                minute: t.minute(),
                second: t.second(),
            },
            tz: TimeZoneChoice::Utc,
        }
    }

    pub fn from_timestamp(timestamp: i64) -> Option<Self> {
        match Utc.timestamp_millis_opt(timestamp) {
            LocalResult::None => None,
            LocalResult::Ambiguous(_, _) => None,
            LocalResult::Single(t) => Some(
                Self { time: ExactTime::from_hms(0, 0, 0), ..Self::from_chrono(t)}
            ),
        }
    }
}

impl ExactEvent {
    fn to_icalevent(&self, key: Option<String>) -> Result<ical::Event> {
        let mut calevent = ical::Event::new();
        calevent.summary(self.name.as_str());
        if let Some(notes) = self.notes.as_ref() {
            calevent.description(notes.as_str());
        }
        if let Some(s) = key {
            calevent.uid(Uuid::new_v3(&Uuid::NAMESPACE_URL, s.as_bytes()).to_string().as_str());
        }
        match &self.range {
            ExactRange::TimeRange(range) => {
                calevent = calevent
                    .starts(range.start.to_chrono()?)
                    .ends(range.end.to_chrono()?)
                    .done();
            }
            ExactRange::AllDay(date) => {
                calevent = calevent.all_day(date.to_chrono()?).done();
            }
        }
        Ok(calevent)
    }
}

pub fn to_ical(records: Vec<ExactRecord>, deterministic: bool) -> String {
    let mut calendar = ical::Calendar::new();
    for (i,record) in records.iter().enumerate() {
        let key = if deterministic {
            Some(i.to_string())
        } else {
            None
        };
        if let ExactRecord::Event(event) = record {
            match event.to_icalevent(key) {
                Ok(calevent) => {
                    calendar.push(calevent);
                }
                Err(e) => {
                    eprintln!("Error processing event: {}", e);
                }
            }
        }
    }
    calendar = calendar.done();
    calendar.to_string()
}

// test
#[cfg(test)]
mod tests {
    use super::*;
    use chrono::NaiveDate;
    use anyhow::Result;

    #[test]
    fn test_timestamp() -> Result<()>{
        let timestamp = 1680533997811;
        if let Some(res) = ExactDateTime::from_timestamp(timestamp){
            assert_eq!(res.date.year, 2023);
            assert_eq!(res.date.month, 4);
            assert_eq!(res.date.day, 3);
            Ok(())
        } else {
            Err(anyhow::anyhow!("failed to parse timestamp"))
        }
    }
}