Skip to main content

lectio_wasm/
generate_liturgy.rs

1use chrono::{Datelike, Duration, NaiveDate, Weekday};
2use serde::{Deserialize, Serialize};
3use inflector::Inflector;
4use number_names::{ordinal};
5use std::collections::HashMap;
6use std::env;
7use std::fs;
8use std::path::Path;
9use once_cell::sync::Lazy;
10use regex::Regex;
11
12use crate::config::get_storage_path;
13
14#[derive(Deserialize, Debug, Clone, Serialize)]
15pub struct RangeEnd {
16    chapter: u32,
17    verse: u32,
18}
19
20#[derive(Deserialize, Debug, Clone, Serialize)]
21pub struct Verse {
22    pub chapter: u32,
23    pub verse: u32,
24    pub translation: Option<String>,
25    range_end: Option<RangeEnd>,
26}
27
28#[derive(Deserialize, Debug, Clone, Serialize)]
29pub struct Verses {
30    pub book: String,
31    pub verses: Vec<Verse>,
32}
33
34#[derive(Deserialize, Debug, Clone, Serialize)]
35pub struct Reading {
36    #[serde(rename = "rawReading")]
37    pub raw_reading: String,
38    pub reading: Vec<Verses>,
39}
40
41#[derive(Deserialize, Debug, Clone, Serialize)]
42pub struct Readings {
43    pub title: String,
44    pub first: Option<Reading>,
45    pub responsal: Option<Reading>,
46    pub second: Option<Reading>,
47    pub gospel: Option<Reading>,
48    pub rank: Option<String>,
49}
50
51#[derive(Debug, Clone)]
52struct Calendar {
53    weekday_cycle: u8,
54    sunday_cycle: u8,
55    holy_family: NaiveDate,
56    epiphany: NaiveDate,
57    christmas_end: NaiveDate,
58    ash_wednesday: NaiveDate,
59    easter: NaiveDate,
60    pentecost: NaiveDate,
61    advent_start: NaiveDate,
62    weeks_before_lent: u8,
63    pentecost_start_ot: u8,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum LiturgicalSeason {
68    Christmas,
69    Ordinary,
70    Lent,
71    Easter,
72    Advent,
73}
74
75impl LiturgicalSeason {
76    fn as_str(&self) -> &'static str {
77        match self {
78            Self::Christmas => "CHRISTMAS",
79            Self::Ordinary => "ORDINARY", 
80            Self::Lent => "LENT",
81            Self::Easter => "EASTER",
82            Self::Advent => "ADVENT",
83        }
84    }
85}
86
87type LectionaryTemplate = HashMap<String, HashMap<String, Readings>>;
88
89static LECTIONARYTEMPLATE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/lectionaryTemplate.json"));
90
91pub struct LiturgyGenerator {
92    lectionary: LectionaryTemplate,
93    calendar: Calendar,
94    year: i32,
95}
96
97impl LiturgyGenerator {
98    pub fn new(year: i32) -> Result<Self, Box<dyn std::error::Error>> {
99        // let template_path = get_storage_path().join("lectionaryTemplate.json");
100        // let lectionary_data = fs::read_to_string(template_path)?;
101        let lectionary_data = LECTIONARYTEMPLATE;
102        let lectionary: LectionaryTemplate = serde_json::from_str(&lectionary_data)?;
103        
104        // This would normally come from readCalendar.rs equivalent
105        let calendar = Self::get_calendar(year);
106        
107        Ok(Self {
108            lectionary,
109            calendar,
110            year,
111        })
112    }
113
114    fn get_calendar(year: i32) -> Calendar {
115        let easter = calendar_utils::calculate_easter(year);
116        let calendar = Calendar {
117            weekday_cycle: calendar_utils::calculate_weekday_cycle(year),
118            sunday_cycle: calendar_utils::calculate_sunday_cycle(year),
119            holy_family: calendar_utils::calculate_holy_family(year),
120            epiphany: calendar_utils::calculate_epiphany(year),
121            christmas_end: calendar_utils::calculate_christmas_end(year),
122            ash_wednesday: calendar_utils::calculate_ash_wednesday(easter),
123            easter: easter, 
124            pentecost: calendar_utils::calculate_pentecost(easter), 
125            advent_start: calendar_utils::calculate_advent_start(year), 
126            weeks_before_lent: calendar_utils::calculate_weeks_before_lent(year),
127            pentecost_start_ot: calendar_utils::calculate_pentecost_start_ot(year),
128        };
129        // println!("Weekday Cycle: {}", calendar.weekday_cycle);
130        // println!("Sunday Cycle: {}", calendar.sunday_cycle);
131        // println!("Holy Family: {}", calendar.holy_family);
132        // println!("Epiphany: {}", calendar.epiphany);
133        // println!("End of christmas: {}", calendar.christmas_end);
134        // println!("Ash Wednesday: {}", calendar.ash_wednesday);
135        // println!("Easter: {}", calendar.easter);
136        // println!("Pentecost: {}", calendar.pentecost);
137        // println!("Advent start: {}", calendar.advent_start);
138        // println!("Weeks before lent: {}", calendar.weeks_before_lent);
139        // println!("Pentecost start OT: {}", calendar.pentecost_start_ot);
140
141        calendar
142    }
143
144    fn sunday_cycle_to_char(cycle: u8) -> char {
145        match cycle {
146            1 => 'A',
147            2 => 'B', 
148            3 => 'C',
149            _ => 'A',
150        }
151    }
152
153    fn get_prev_saturday(mut date: NaiveDate, delta: u32) -> NaiveDate {
154        for _ in 0..delta {
155            date = date.pred_opt().unwrap();
156            while date.weekday() != Weekday::Sat {
157                date = date.pred_opt().unwrap();
158            }
159        }
160        date
161    }
162
163    fn get_next_thursday(mut date: NaiveDate, delta: u32) -> NaiveDate {
164        for _ in 0..delta {
165            date = date.succ_opt().unwrap();
166            while date.weekday() != Weekday::Thu {
167                date = date.succ_opt().unwrap();
168            }
169        }
170        date
171    }
172
173    fn get_next_sunday(mut date: NaiveDate) -> NaiveDate {
174        date = date.succ_opt().unwrap();
175        while date.weekday() != Weekday::Sun {
176            date = date.succ_opt().unwrap();
177        }
178        date
179    }
180
181    fn date_to_lectionary_date(date: NaiveDate) -> String {
182        let month = match date.month() {
183            1 => "Jan",
184            2 => "Feb", 
185            3 => "Mar",
186            4 => "Apr",
187            5 => "May",
188            6 => "Jun",
189            7 => "Jul",
190            8 => "Aug",
191            9 => "Sep",
192            10 => "Oct",
193            11 => "Nov",
194            12 => "Dec",
195            _ => unreachable!(),
196        };
197        format!("{}{}", month, date.day())
198    }
199
200    fn get_ordinal_number(n: u32) -> String {
201        // let suffix = match n % 10 {
202        //     1 if n % 100 != 11 => "st",
203        //     2 if n % 100 != 12 => "nd", 
204        //     3 if n % 100 != 13 => "rd",
205        //     _ => "th",
206        // };
207        // format!("{}{}", n, suffix)
208        // let o = Osize::from1(n as usize).to_string();
209        let o = ordinal(n as u64).to_title_case();
210        o
211    }
212
213    fn get_day_of_week_string(day: u32) -> &'static str {
214        match day {
215            0 => "Monday",
216            1 => "Tuesday",
217            2 => "Wednesday", 
218            3 => "Thursday",
219            4 => "Friday",
220            5 => "Saturday",
221            6 => "Sunday",
222            _ => "Monday",
223        }
224    }
225
226    fn determine_seasons(&self) -> HashMap<NaiveDate, LiturgicalSeason> {
227        let mut seasons = HashMap::new();
228        let start_date = NaiveDate::from_ymd_opt(self.year, 1, 1).unwrap();
229        let end_date = NaiveDate::from_ymd_opt(self.year + 1, 1, 1).unwrap();
230        
231        let mut current_date = start_date;
232        while current_date < end_date {
233            let season = if current_date <= self.calendar.christmas_end {
234                LiturgicalSeason::Christmas
235            } else if current_date < self.calendar.ash_wednesday {
236                LiturgicalSeason::Ordinary
237            } else if current_date < self.calendar.easter - Duration::days(2) {
238                LiturgicalSeason::Lent
239            } else if current_date <= self.calendar.pentecost {
240                LiturgicalSeason::Easter
241            } else if current_date < self.calendar.advent_start {
242                LiturgicalSeason::Ordinary
243            } else if current_date < NaiveDate::from_ymd_opt(self.year, 12, 25).unwrap() {
244                LiturgicalSeason::Advent
245            } else {
246                LiturgicalSeason::Christmas
247            };
248            
249            seasons.insert(current_date, season);
250            current_date = current_date.succ_opt().unwrap();
251        }
252        
253        seasons
254    }
255
256    fn process_christmas_readings(
257        &self,
258        date: NaiveDate,
259        liturgy: &mut HashMap<NaiveDate, Vec<Readings>>,
260    ) {
261        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
262        let christmas_lectionary = &self.lectionary["CHRISTMAS"];
263        let date_search = Self::date_to_lectionary_date(date);
264
265        liturgy.entry(date).or_insert_with(Vec::new);
266
267        if date == NaiveDate::from_ymd_opt(self.year, 12, 25).unwrap() {
268            // Christmas Day - multiple masses
269            let masses = ["CHRISTMAS-VIGIL", "CHRISTMAS-NIGHT", "CHRISTMAS-DAWN", "CHRISTMAS-DAY"];
270            for mass in &masses {
271                if let Some(reading) = christmas_lectionary.get(*mass) {
272                    liturgy.get_mut(&date).unwrap().push(reading.clone());
273                }
274            }
275        } else if date == self.calendar.holy_family {
276            // Holy Family
277            let next_sunday_cycle = self.calendar.sunday_cycle % 3 + 1;
278            let key_search = format!("HOLYFAMILY-{}", Self::sunday_cycle_to_char(next_sunday_cycle));
279            
280            if next_sunday_cycle == 1 {
281                if let Some(reading) = christmas_lectionary.get(&key_search) {
282                    liturgy.get_mut(&date).unwrap().push(reading.clone());
283                }
284            } else {
285                if let Some(reading) = christmas_lectionary.get("HOLYFAMILY-A") {
286                    liturgy.get_mut(&date).unwrap().push(reading.clone());
287                }
288                if let Some(reading) = christmas_lectionary.get(&key_search) {
289                    liturgy.get_mut(&date).unwrap().push(reading.clone());
290                }
291            }
292        } else if christmas_lectionary.contains_key(&date_search) &&
293                  (date < self.calendar.epiphany || 
294                   date > NaiveDate::from_ymd_opt(self.year, 12, 25).unwrap()) {
295            // Christmas season days
296            if let Some(reading) = christmas_lectionary.get(&date_search) {
297                liturgy.get_mut(&date).unwrap().push(reading.clone());
298            }
299        } else if date == self.calendar.christmas_end {
300            // Baptism of Our Lord
301            let key_search = format!("BAPTISM-{}", sunday_cycle_char);
302            if self.calendar.sunday_cycle == 1 {
303                if let Some(reading) = christmas_lectionary.get("BAPTISM-A") {
304                    liturgy.get_mut(&date).unwrap().push(reading.clone());
305                }
306            } else {
307                if let Some(reading) = christmas_lectionary.get("BAPTISM-A") {
308                    liturgy.get_mut(&date).unwrap().push(reading.clone());
309                }
310                if let Some(reading) = christmas_lectionary.get(&key_search) {
311                    liturgy.get_mut(&date).unwrap().push(reading.clone());
312                }
313            }
314        } else if date == self.calendar.epiphany {
315            // Epiphany
316            if let Some(reading) = christmas_lectionary.get("EPIPHANY") {
317                liturgy.get_mut(&date).unwrap().push(reading.clone());
318            }
319        } else {
320            // Days after Epiphany
321            let days_from_epiphany = (date - self.calendar.epiphany).num_days();
322            let key = format!("EPIPHANY-{}", days_from_epiphany);
323            if let Some(reading) = christmas_lectionary.get(&key) {
324                liturgy.get_mut(&date).unwrap().push(reading.clone());
325            }
326        }
327    }
328
329    fn process_advent_readings(
330        &self,
331        date: NaiveDate,
332        liturgy: &mut HashMap<NaiveDate, Vec<Readings>>,
333    ) {
334        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
335        let advent_lectionary = &self.lectionary["ADVENT"];
336        let day_of_week = date.weekday().num_days_from_monday() + 1;
337        let weeks_since_start = ((date - self.calendar.advent_start).num_days() / 7 + 1) as u32;
338
339        liturgy.entry(date).or_insert_with(Vec::new);
340
341        if date.day() >= 17 && date.day() <= 24 {
342            // Specific December dates
343            let key_search = format!("Dec{}", date.day());
344            if let Some(mut reading) = advent_lectionary.get(&key_search).cloned() {
345                let title = format!(
346                    "{} {} of Advent",
347                    Self::get_ordinal_number(weeks_since_start),
348                    Self::get_day_of_week_string((day_of_week - 1) as u32)
349                );
350                reading.title = title;
351                liturgy.get_mut(&date).unwrap().push(reading);
352            }
353        } else if date.weekday() == Weekday::Sun {
354            // Sundays
355            let key_search = format!("{}-7-{}", weeks_since_start, sunday_cycle_char);
356            if let Some(reading) = advent_lectionary.get(&key_search) {
357                liturgy.get_mut(&date).unwrap().push(reading.clone());
358            }
359        } else {
360            // Weekdays
361            let key_search = format!("{}-{}", weeks_since_start, day_of_week);
362            if let Some(reading) = advent_lectionary.get(&key_search) {
363                liturgy.get_mut(&date).unwrap().push(reading.clone());
364            }
365        }
366    }
367
368    fn process_ordinary_readings(
369        &self,
370        date: NaiveDate,
371        liturgy: &mut HashMap<NaiveDate, Vec<Readings>>,
372    ) {
373        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
374        let ordinary_lectionary = &self.lectionary["ORDINARY"];
375        let day_of_week = date.weekday().num_days_from_monday() + 1;
376
377        let weeks_since_start = if date < self.calendar.ash_wednesday {
378            ((date - self.calendar.christmas_end).num_days() / 7 + 1) as u32
379        } else {
380            ((date - self.calendar.pentecost).num_days() / 7 + self.calendar.pentecost_start_ot as i64) as u32
381        };
382
383        let week_str = if weeks_since_start < 10 {
384            format!("0{}", weeks_since_start)
385        } else {
386            weeks_since_start.to_string()
387        };
388
389        liturgy.entry(date).or_insert_with(Vec::new);
390
391        if date.weekday() == Weekday::Sun {
392            let key_search = format!("{}-7-{}", week_str, sunday_cycle_char);
393            if let Some(mut reading) = ordinary_lectionary.get(&key_search).cloned() {
394                reading.title = format!("{} Sunday of Ordinary Time", Self::get_ordinal_number(weeks_since_start));
395                liturgy.get_mut(&date).unwrap().push(reading);
396            }
397        } else {
398            let key_search = format!("{}-{}-{}", week_str, day_of_week, self.calendar.weekday_cycle);
399            if let Some(reading) = ordinary_lectionary.get(&key_search) {
400                liturgy.get_mut(&date).unwrap().push(reading.clone());
401            }
402        }
403    }
404
405    fn process_lent_readings(
406        &self,
407        date: NaiveDate,
408        liturgy: &mut HashMap<NaiveDate, Vec<Readings>>,
409    ) {
410        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
411        let lent_lectionary = &self.lectionary["LENT"];
412        let easter_lectionary = &self.lectionary["EASTER"];
413        let day_of_week = date.weekday().num_days_from_monday() + 1;
414        let weeks_since_ash = ((date - self.calendar.ash_wednesday + Duration::days(2)).num_days() / 7) as u32;
415
416        liturgy.entry(date).or_insert_with(Vec::new);
417
418        if date + Duration::days(3) == self.calendar.easter {
419            // Holy Thursday
420            if let Some(reading) = lent_lectionary.get("CHRISM") {
421                liturgy.get_mut(&date).unwrap().push(reading.clone());
422            }
423            if let Some(reading) = easter_lectionary.get("0-4") {
424                liturgy.get_mut(&date).unwrap().push(reading.clone());
425            }
426        } else if date.weekday() == Weekday::Sun {
427            if weeks_since_ash + 1 == 6 {
428                // Palm Sunday
429                let key_search = format!("6-7-{}", sunday_cycle_char);
430                if let Some(reading) = lent_lectionary.get(&format!("{}-MASS", key_search)) {
431                    liturgy.get_mut(&date).unwrap().push(reading.clone());
432                }
433                if let Some(reading) = lent_lectionary.get(&format!("{}-PROC", key_search)) {
434                    liturgy.get_mut(&date).unwrap().push(reading.clone());
435                }
436            } else {
437                let key_search = format!("{}-7-{}", weeks_since_ash + 1, sunday_cycle_char);
438                if let Some(reading) = lent_lectionary.get(&key_search) {
439                    liturgy.get_mut(&date).unwrap().push(reading.clone());
440                }
441            }
442        } else {
443            let key_search = format!("{}-{}", weeks_since_ash, day_of_week);
444            if let Some(reading) = lent_lectionary.get(&key_search) {
445                liturgy.get_mut(&date).unwrap().push(reading.clone());
446            }
447        }
448    }
449
450    fn process_easter_readings(
451        &self,
452        date: NaiveDate,
453        liturgy: &mut HashMap<NaiveDate, Vec<Readings>>,
454    ) {
455        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
456        let easter_lectionary = &self.lectionary["EASTER"];
457        let day_of_week = date.weekday().num_days_from_monday() + 1;
458        let weeks_since_easter = ((date - self.calendar.easter).num_days() / 7 + 1) as u32;
459
460        liturgy.entry(date).or_insert_with(Vec::new);
461
462        if date == self.calendar.easter - Duration::days(1) {
463            // Holy Saturday
464            liturgy.get_mut(&date).unwrap().push(Readings {
465                title: "No Mass".to_string(),
466                first: None,
467                responsal: None, 
468                second: None, 
469                gospel: None, 
470                rank: None, 
471            });
472        } else if date == self.calendar.easter - Duration::days(2) {
473            // Good Friday
474            if let Some(reading) = easter_lectionary.get("0-5") {
475                liturgy.get_mut(&date).unwrap().push(reading.clone());
476            }
477        } else if date == self.calendar.easter {
478            // Easter Sunday
479            if let Some(reading) = easter_lectionary.get("0-7-VIGIL") {
480                liturgy.get_mut(&date).unwrap().push(reading.clone());
481            }
482            if let Some(reading) = easter_lectionary.get("0-7") {
483                liturgy.get_mut(&date).unwrap().push(reading.clone());
484            }
485        } else if date == self.calendar.pentecost {
486            // Pentecost
487            if let Some(reading) = easter_lectionary.get("PENTECOST-VIGIL") {
488                liturgy.get_mut(&date).unwrap().push(reading.clone());
489            }
490            if let Some(reading) = easter_lectionary.get("PENTECOST-A") {
491                liturgy.get_mut(&date).unwrap().push(reading.clone());
492            }
493            if self.calendar.sunday_cycle != 1 {
494                let key = format!("PENTECOST-{}", sunday_cycle_char);
495                if let Some(reading) = easter_lectionary.get(&key) {
496                    liturgy.get_mut(&date).unwrap().push(reading.clone());
497                }
498            }
499        } else if date.weekday() == Weekday::Sun {
500            // Easter Sundays
501            let key_search = format!("{}-7-{}", weeks_since_easter - 1, sunday_cycle_char);
502            if let Some(reading) = easter_lectionary.get(&key_search) {
503                liturgy.get_mut(&date).unwrap().push(reading.clone());
504            }
505            if weeks_since_easter == 6 {
506                // Ascension
507                let ascension_key = format!("ASCENSION-{}", sunday_cycle_char);
508                if let Some(reading) = easter_lectionary.get(&ascension_key) {
509                    liturgy.get_mut(&date).unwrap().insert(0, reading.clone());
510                }
511            }
512        } else {
513            // Easter Weekdays
514            let key_search = format!("{}-{}", weeks_since_easter, day_of_week);
515            if let Some(reading) = easter_lectionary.get(&key_search) {
516                liturgy.get_mut(&date).unwrap().push(reading.clone());
517            }
518        }
519    }
520
521    fn process_solemnities(&self, liturgy: &mut HashMap<NaiveDate, Vec<Readings>>) {
522        let sunday_cycle_char = Self::sunday_cycle_to_char(self.calendar.sunday_cycle);
523        let solemnity_lectionary = &self.lectionary["SOLEMNITY"];
524        
525        let trinity_sunday = Self::get_next_sunday(self.calendar.pentecost);
526        let body_blood = Self::get_next_sunday(trinity_sunday);
527        let sacred_heart = body_blood + Duration::days(5);
528        let start_holy_week = self.calendar.easter - Duration::days(6);
529
530        // Major moveable solemnities
531        if let Some(reading) = solemnity_lectionary.get(&format!("TrinSun-{}", sunday_cycle_char)) {
532            liturgy.entry(trinity_sunday).or_insert_with(Vec::new).insert(0, reading.clone());
533        }
534        if let Some(reading) = solemnity_lectionary.get(&format!("BodyBlood-{}", sunday_cycle_char)) {
535            liturgy.entry(body_blood).or_insert_with(Vec::new).insert(0, reading.clone());
536        }
537        if let Some(reading) = solemnity_lectionary.get(&format!("SacHeart-{}", sunday_cycle_char)) {
538            liturgy.entry(sacred_heart).or_insert_with(Vec::new).insert(0, reading.clone());
539        }
540
541        // Fixed solemnities with special handling
542        let dates_to_check = [
543            (3, 19, "Mar19"), // St. Joseph
544            (3, 25, "Mar25"), // Annunciation
545            (6, 24, "Jun24"), // St. John the Baptist
546            (12, 8, "Dec8"),  // Immaculate Conception
547        ];
548
549        for (month, day, key) in &dates_to_check {
550            if let Some(solemnity_date) = NaiveDate::from_ymd_opt(self.year, *month, *day) {
551                let mut actual_date = solemnity_date;
552                
553                match key {
554                    &"Mar19" => {
555                        // St. Joseph - move if conflicts with Holy Week
556                        if start_holy_week < solemnity_date && solemnity_date < self.calendar.easter {
557                            actual_date = Self::get_prev_saturday(solemnity_date, 1);
558                        } else if solemnity_date.weekday() == Weekday::Sun && 
559                                 matches!(liturgy.get(&solemnity_date).map(|v| v.is_empty()).unwrap_or(true), false) {
560                            actual_date = solemnity_date + Duration::days(1);
561                        }
562                    },
563                    &"Mar25" => {
564                        // Annunciation - complex moving rules
565                        if solemnity_date.weekday() == Weekday::Sun {
566                            actual_date = NaiveDate::from_ymd_opt(self.year, 3, 26).unwrap();
567                        }
568                        if start_holy_week <= actual_date && actual_date < Self::get_next_sunday(self.calendar.easter) {
569                            actual_date = self.calendar.easter + Duration::days(8);
570                        }
571                    },
572                    &"Jun24" => {
573                        // St. John the Baptist - move if conflicts with Sacred Heart
574                        if solemnity_date == sacred_heart {
575                            actual_date = NaiveDate::from_ymd_opt(self.year, 6, 23).unwrap();
576                        }
577                        // This one has both vigil and day masses
578                        if let Some(vigil_reading) = solemnity_lectionary.get("Jun24-VIGIL") {
579                            liturgy.entry(actual_date).or_insert_with(Vec::new).insert(0, vigil_reading.clone());
580                        }
581                    },
582                    &"Dec8" => {
583                        // Immaculate Conception - move if Sunday in Advent
584                        if solemnity_date.weekday() == Weekday::Sun {
585                            actual_date = NaiveDate::from_ymd_opt(self.year, 12, 9).unwrap();
586                        }
587                    },
588                    _ => {}
589                }
590
591                if let Some(reading) = solemnity_lectionary.get(*key) {
592                    liturgy.entry(actual_date).or_insert_with(Vec::new).insert(0, reading.clone());
593                }
594            }
595        }
596
597        // Other fixed solemnities
598        for (date_str, reading) in solemnity_lectionary {
599            if let Ok(parsed_date) = Self::parse_lectionary_date_to_date(date_str, self.year) {
600                // Skip already handled dates
601                if ["Mar19", "Mar25", "Jun24", "Dec8"].contains(&date_str.as_str()) ||
602                   date_str.contains("TrinSun") || date_str.contains("BodyBlood") || date_str.contains("SacHeart") {
603                    continue;
604                }
605
606                if date_str.ends_with("-VIGIL") {
607                    if let Some(base_reading) = solemnity_lectionary.get(&date_str.replace("-VIGIL", "")) {
608                        let readings = liturgy.entry(parsed_date).or_insert_with(Vec::new);
609                        readings.insert(0, base_reading.clone());
610                        readings.insert(0, reading.clone());
611                    }
612                } else if !solemnity_lectionary.contains_key(&format!("{}-VIGIL", date_str)) {
613                    liturgy.entry(parsed_date).or_insert_with(Vec::new).insert(0, reading.clone());
614                }
615            }
616        }
617    }
618
619    fn process_saints(&self, liturgy: &mut HashMap<NaiveDate, Vec<Readings>>) {
620        let saint_lectionary = &self.lectionary["SAINTPROPER"];
621        
622        // Add Thanksgiving
623        let thanksgiving = Self::get_next_thursday(
624            NaiveDate::from_ymd_opt(self.year, 10, 31).unwrap(), 
625            4
626        );
627        if let Some(reading) = saint_lectionary.get("THANKSGIVING") {
628            liturgy.entry(thanksgiving).or_insert_with(Vec::new).push(reading.clone());
629        }
630
631        // Process all saint days
632        for (date_str, reading) in saint_lectionary {
633            if date_str == "THANKSGIVING" {
634                continue; // Already handled
635            }
636
637            if let Ok(saint_date) = Self::parse_lectionary_date_to_date(date_str, self.year) {
638                let day_of_week = saint_date.weekday();
639                let rank = reading.rank.clone().unwrap_or(String::new());
640                
641                if day_of_week == Weekday::Sun || rank.contains("Opt") || rank.contains("Mem") {
642                    liturgy.entry(saint_date).or_insert_with(Vec::new).push(reading.clone());
643                } else {
644                    liturgy.entry(saint_date).or_insert_with(Vec::new).insert(0, reading.clone());
645                }
646            }
647        }
648    }
649
650    fn parse_lectionary_date_to_date(date_str: &str, year: i32) -> Result<NaiveDate, Box<dyn std::error::Error>> {
651        if date_str.len() < 4 {
652            return Err("Invalid date string".into());
653        }
654
655        let month_str = &date_str[..3];
656        let day_str = &date_str[3..].chars().take_while(|c| c.is_ascii_digit()).collect::<String>();
657        
658        let month = match month_str {
659            "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,
660            "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8,
661            "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12,
662            _ => return Err("Invalid month".into()),
663        };
664
665        let day: u32 = day_str.parse()?;
666        NaiveDate::from_ymd_opt(year, month, day)
667            .ok_or_else(|| "Invalid date".into())
668    }
669
670    pub fn generate(&self) -> Result<(HashMap<String, Vec<Readings>>, HashMap<String, LiturgicalSeason>), Box<dyn std::error::Error>> {
671        let seasons = self.determine_seasons();
672        let mut liturgy: HashMap<NaiveDate, Vec<Readings>> = HashMap::new();
673
674        // Process each day according to its season
675        for (&date, &season) in &seasons {
676            match season {
677                LiturgicalSeason::Christmas => self.process_christmas_readings(date, &mut liturgy),
678                LiturgicalSeason::Advent => self.process_advent_readings(date, &mut liturgy),
679                LiturgicalSeason::Ordinary => self.process_ordinary_readings(date, &mut liturgy),
680                LiturgicalSeason::Lent => self.process_lent_readings(date, &mut liturgy),
681                LiturgicalSeason::Easter => self.process_easter_readings(date, &mut liturgy),
682            }
683        }
684
685        // Process solemnities and saints
686        self.process_solemnities(&mut liturgy);
687        self.process_saints(&mut liturgy);
688
689        // Convert to string keys for JSON serialization
690        let parsed_liturgy: HashMap<String, Vec<Readings>> = liturgy
691            .into_iter()
692            .map(|(date, readings)| (date.format("%Y-%m-%d").to_string(), readings))
693            .collect();
694
695        let parsed_seasons: HashMap<String, LiturgicalSeason> = seasons
696            .into_iter()
697            .map(|(date, season)| (date.format("%Y-%m-%d").to_string(), season))
698            .collect();
699
700        Ok((parsed_liturgy, parsed_seasons))
701    }
702
703    pub fn save_to_files(&self) -> Result<(), Box<dyn std::error::Error>> {
704        let (liturgy, seasons) = self.generate()?;
705        
706        // Create output directory
707        let liturgy_folder = get_storage_path().join("liturgies");
708        std::fs::create_dir_all(&liturgy_folder)?;
709        
710        // Save liturgy
711        let liturgy_json = serde_json::to_string_pretty(&liturgy)?;
712        std::fs::write(&liturgy_folder.join(format!("liturgy{}.json", self.year)), &liturgy_json)?;
713        println!("{}", liturgy_json);
714        
715        // Save seasons
716        let seasons_json = serde_json::to_string_pretty(&seasons)?;
717        std::fs::write(format!("./liturgies/liturgy{}season.json", self.year), &seasons_json)?;
718        
719        Ok(())
720    }
721}
722
723// fn main() -> Result<(), Box<dyn std::error::Error>> {
724//     let args: Vec<String> = env::args().collect();
725//     let year = if args.len() < 2 {
726//         chrono::Utc::now().year()
727//     } else {
728//         args[1].parse::<i32>()?
729//     };
730//
731//     let generator = LiturgyGenerator::new(year)?;
732//     generator.save_to_files()?;
733//     
734//     Ok(())
735// }
736
737#[cfg(test)]
738mod tests {
739    use super::*;
740
741    #[test]
742    fn test_sunday_cycle_conversion() {
743        assert_eq!(LiturgyGenerator::sunday_cycle_to_char(1), 'A');
744        assert_eq!(LiturgyGenerator::sunday_cycle_to_char(2), 'B');
745        assert_eq!(LiturgyGenerator::sunday_cycle_to_char(3), 'C');
746    }
747
748    #[test]
749    fn test_date_to_lectionary_date() {
750        let date = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
751        assert_eq!(LiturgyGenerator::date_to_lectionary_date(date), "Mar15");
752    }
753
754    #[test]
755    fn test_ordinal_numbers() {
756        assert_eq!(LiturgyGenerator::get_ordinal_number(1), "First");
757        assert_eq!(LiturgyGenerator::get_ordinal_number(2), "Second");
758        assert_eq!(LiturgyGenerator::get_ordinal_number(21), "Twenty First");
759    }
760
761    #[test]
762    fn test_parse_lectionary_date() {
763        let result = LiturgyGenerator::parse_lectionary_date_to_date("Mar15", 2024);
764        assert!(result.is_ok());
765        assert_eq!(result.unwrap(), NaiveDate::from_ymd_opt(2024, 3, 15).unwrap());
766    }
767}
768
769// Additional helper functions that might be needed for the calendar module
770pub mod calendar_utils {
771    use chrono::{NaiveDate, Datelike};
772
773    /// Calculate Easter date using the algorithm for Gregorian calendar
774    pub fn calculate_easter(year: i32) -> NaiveDate {
775        let a = year % 19;
776        let b = year / 100;
777        let c = year % 100;
778        let d = b / 4;
779        let e = b % 4;
780        let f = (b + 8) / 25;
781        let g = (b - f + 1) / 3;
782        let h = (19 * a + b - d - g + 15) % 30;
783        let i = c / 4;
784        let k = c % 4;
785        let l = (32 + 2 * e + 2 * i - h - k) % 7;
786        let m = (a + 11 * h + 22 * l) / 451;
787        let n = (h + l - 7 * m + 114) / 31;
788        let p = (h + l - 7 * m + 114) % 31;
789        
790        NaiveDate::from_ymd_opt(year, n as u32, (p + 1) as u32).unwrap()
791    }
792
793    /// Calculate Ash Wednesday (46 days before Easter)
794    pub fn calculate_ash_wednesday(easter: NaiveDate) -> NaiveDate {
795        easter - chrono::Duration::days(46)
796    }
797
798    /// Calculate Pentecost (49 days after Easter)
799    pub fn calculate_pentecost(easter: NaiveDate) -> NaiveDate {
800        easter + chrono::Duration::days(49)
801    }
802
803    /// Calculate first Sunday of Advent (4 Sundays before Christmas)
804    pub fn calculate_advent_start(year: i32) -> NaiveDate {
805        let christmas = NaiveDate::from_ymd_opt(year, 12, 25).unwrap();
806        let mut advent_start = christmas;
807        
808        // Go back to find the 4th Sunday before
809        for _ in 0..4 {
810            advent_start = advent_start - chrono::Duration::days(1);
811            while advent_start.weekday() != chrono::Weekday::Sun {
812                advent_start = advent_start - chrono::Duration::days(1);
813            }
814        }
815        
816        advent_start
817    }
818
819    /// Determine the Sunday cycle (A, B, C) based on the year
820    pub fn calculate_sunday_cycle(year: i32) -> u8 {
821        // Year A begins with Advent in the year before a year divisible by 3
822        // Year B begins with Advent in the year before a year that leaves remainder 1 when divided by 3
823        // Year C begins with Advent in the year before a year that leaves remainder 2 when divided by 3
824        ((year - 1) % 3 + 1) as u8
825    }
826
827    pub fn calculate_weekday_cycle(year: i32) -> u8 {
828        ((year + 1) % 2 + 1) as u8
829    }
830
831    pub fn calculate_epiphany(year: i32) -> NaiveDate {
832        let jan1 = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
833        let mut epiphany = jan1;
834
835        loop {
836            epiphany = epiphany + chrono::Duration::days(1);
837            if epiphany.weekday() == chrono::Weekday::Sun {
838                break;
839            }
840        }
841
842        epiphany
843    }
844
845    pub fn calculate_holy_family(year: i32) -> NaiveDate {
846        let epiphany = calculate_epiphany(year + 1);
847        let mut holy_family = epiphany;
848        if NaiveDate::from_ymd_opt(year, 12, 25).unwrap().weekday() == chrono::Weekday::Sun {
849            return NaiveDate::from_ymd_opt(year, 12, 30).unwrap();
850        } else {
851            loop {
852                holy_family = holy_family - chrono::Duration::days(1);
853                if holy_family.weekday() == chrono::Weekday::Sun {
854                    return holy_family; 
855                }
856            }
857        }
858    }
859
860    pub fn calculate_christmas_end(year: i32) -> NaiveDate {
861        let epiphany = calculate_epiphany(year);
862        let mut christmas_end = epiphany;
863        if christmas_end.day() == 6 || christmas_end.day() == 7 {
864            return christmas_end + chrono::Duration::days(1);
865        } else {
866            loop {
867                christmas_end = christmas_end + chrono::Duration::days(1);
868                if christmas_end.weekday() == chrono::Weekday::Sun {
869                    return christmas_end;
870                }
871            }
872        }
873
874    }
875    
876    pub fn calculate_weeks_before_lent(year: i32) -> u8 {
877        let mut weeks: u8 = 0;
878        let easter = calculate_easter(year);
879        let mut sunday_index = calculate_ash_wednesday(easter);
880        let christmas_end = calculate_christmas_end(year);
881
882        while sunday_index > christmas_end {
883            loop {
884                sunday_index = sunday_index - chrono::Duration::days(1);
885                if sunday_index.weekday() == chrono::Weekday::Sun {
886                    break;
887                }
888            } 
889            weeks = weeks + 1;
890        }
891        
892        return weeks;
893    }
894
895    pub fn calculate_pentecost_start_ot(year: i32) -> u8 {
896        let mut weeks: u8 = 0;
897        let mut sunday_index = calculate_advent_start(year);
898        let easter = calculate_easter(year);
899        let pentecost = calculate_pentecost(easter);
900
901        while sunday_index > pentecost {
902            loop {
903                sunday_index = sunday_index - chrono::Duration::days(1);
904                if sunday_index.weekday() == chrono::Weekday::Sun {
905                    break;
906                }
907            } 
908            weeks = weeks + 1;
909        }
910
911        let weeks_before_lent = calculate_weeks_before_lent(year);
912        if weeks + calculate_weeks_before_lent(year) == 34 {
913            return weeks_before_lent + 1;
914        } else {
915            return weeks_before_lent + 2;
916        }
917    }
918}
919
920
921// Searches bible for verses to complete Liturgy 
922pub fn search_bible(bible:&str, liturgy:&mut Vec<Readings>) {
923    for readings in liturgy {
924        if let Some(reading) = &mut readings.first {
925            // println!("READINGS: {:#?}", reading);
926            search_reading(bible, reading);
927        }
928        if let Some(reading) = &mut readings.responsal {
929            search_reading(bible, reading);
930        }
931        if let Some(reading) = &mut readings.second {
932            search_reading(bible, reading);
933        }
934        if let Some(reading) = &mut readings.gospel {
935            search_reading(bible, reading);
936        }
937    }
938}
939
940// Searches bible for verses to complete Reading
941fn search_reading(bible:&str, reading:&mut Reading) {
942    for verses in &mut reading.reading {
943        // println!("{:?}", verses);
944        search_verses(bible, verses);
945    }
946}
947
948fn search_verses<'a>(bible:&'a str, verses:&mut Verses) -> Vec<String> {
949    let book = &verses.book;
950    let bible_lines: Vec<&str> = bible.lines().collect();
951    let book_start = match bible_lines.clone().into_iter().position(|line| line == book) {
952        Some(content) => content,
953        None => {return Vec::new();},
954    };
955    let mut result: Vec<String> = Vec::new();
956    let mut new_verses_vector: Vec<Verse> = Vec::new();
957
958    for verse in &mut verses.verses {
959        let chapter = verse.chapter;
960        let verse_number = verse.verse;
961        let chapter_number = verse.chapter;
962        let mut book_index = bible_lines.iter().skip(book_start);
963        let end_asterisk_regex = Regex::new(r"\s*\*[^*]*\*$").unwrap();
964        // println!("Searching: {book}, {chapter}:{verse_number}");
965
966        if let Some(range_end) = &verse.range_end {
967            // println!("RANGE END: {:#?}", range_end);
968            let end_chapter = range_end.chapter;
969            let end_verse = range_end.verse;
970
971            let mut start_reading = false;
972            loop {
973                let line = book_index.clone().collect::<Vec<&&str>>()[1];
974                // println!("{line}");
975                let line_split: Vec<&str> = line.split(':').collect();
976                let current_chapter = line_split[0].parse::<u32>().unwrap_or(0); 
977                if current_chapter == 0 {break;}
978                let (unparsed_current_verse, result) = line_split[1].split_once(' ').unwrap();
979
980                let result = end_asterisk_regex.replace_all(&result.to_string(), "").trim().to_string();
981
982                // let current_verse = space_split[0].parse::<u32>().unwrap();
983                let current_verse = unparsed_current_verse.parse::<u32>().unwrap();
984
985                if current_chapter == chapter_number && current_verse == verse_number {
986                    start_reading = true;
987                }
988                if start_reading {
989                    // println!("{:#?}", result);
990                    // println!("Current Chapter: {}", current_chapter);
991                    // println!("Current Verse: {}", current_verse);
992                    let new_verse = Verse {
993                        chapter: current_chapter,
994                        verse: current_verse,
995                        translation: Some(result.to_string()),
996                        range_end: None,
997                    };
998                    new_verses_vector.push(new_verse);
999                }
1000                // let current_chapter = book_index;
1001                if current_chapter >= end_chapter && current_verse >= end_verse {
1002                    break;
1003                }
1004                book_index.next();
1005            }
1006
1007            // println!("NEW VERSES: {:#?}", new_verses);
1008        } else {
1009            for line in book_index {
1010                if line.starts_with(&(chapter.to_string())) {
1011                    let split_text: Vec<&str> = line.split(':').collect();
1012                    if let Some(verse_text) = split_text.get(1) {
1013                        if verse_text.starts_with(&(verse_number.to_string())) {
1014                            let space_split = line.split_once(' ');
1015                            let (_, verse_text) = space_split.unwrap_or(("", ""));
1016                            let formatted_text = end_asterisk_regex.replace_all(verse_text, "").trim().to_string();
1017                            result.push(formatted_text.clone());
1018
1019                            verse.translation = Some(formatted_text.to_string());
1020                            break;
1021                        }
1022                    }
1023                }
1024            }
1025        }
1026
1027    }
1028
1029    if !new_verses_vector.is_empty() {
1030        verses.verses = new_verses_vector;
1031    }
1032    // println!("{:#?}", result);
1033    
1034    return result;
1035}