use chrono::{Datelike, NaiveDate};
use colored::Colorize;
use terminal_size::{terminal_size, Width};
use crate::calendar::events::{Event, EventList};
use crate::args::ViewMode;
pub struct CalendarOptions {
pub date: NaiveDate,
pub mode: ViewMode,
pub number: u32,
}
pub fn show_calendar(events: &EventList, options: &CalendarOptions) {
match options.mode {
ViewMode::Day => show_day_view(events, options),
ViewMode::Week => show_week_view(events, options),
ViewMode::Month => show_month_view(events, options),
}
}
fn show_day_view(events: &EventList, options: &CalendarOptions) {
for i in 0..options.number {
let target_date = options.date + chrono::Duration::days(i.into());
let day_events = events.on_date(target_date);
println!("{}", target_date.format("%A, %d %B %Y").to_string().bold());
if day_events.is_empty() {
println!("No events");
} else {
for event in day_events {
print_event_time_line(event);
}
}
if i < options.number - 1 {
println!();
}
}
}
fn show_week_view(events: &EventList, options: &CalendarOptions) {
for week in 0..options.number {
let start_of_week = get_start_of_week(options.date + chrono::Duration::weeks(week.into()));
for day in 0..7 {
let current_date = start_of_week + chrono::Duration::days(day);
let day_events = events.on_date(current_date);
println!("{}", current_date.format("%A, %d %B").to_string().bold());
if day_events.is_empty() {
println!("No events");
} else {
for event in day_events {
print_event_time_line(event);
}
}
println!();
}
if week < options.number - 1 {
println!();
}
}
}
fn show_month_view(events: &EventList, options: &CalendarOptions) {
let term_width = get_terminal_width();
for month_num in 0..options.number {
let target_date = add_months_to_date(options.date, month_num);
let (first_of_month, last_of_month) = get_month_bounds(target_date);
let upcoming_events = get_upcoming_events_for_month(events, first_of_month, last_of_month);
let mut upcoming_iter = upcoming_events.iter();
print_month_header(&first_of_month, &mut upcoming_iter, term_width);
print_weekday_header(&mut upcoming_iter, term_width, month_num == 0);
print_month_grid(events, first_of_month, last_of_month, &mut upcoming_iter, term_width);
if month_num < options.number - 1 {
println!();
}
}
}
fn print_event_time_line(event: &Event) {
let start_time = event.start.format("%H:%M");
let end_time = event.end.format("%H:%M");
let location_part = event
.location
.as_ref()
.map_or(String::new(), |loc| format!(" in {}", loc));
println!("{}-{} - {}{}", start_time, end_time, event.name, location_part);
}
fn get_start_of_week(date: NaiveDate) -> NaiveDate {
let days_from_monday = date.weekday().num_days_from_monday();
date - chrono::Duration::days(days_from_monday.into())
}
fn get_month_bounds(date: NaiveDate) -> (NaiveDate, NaiveDate) {
let first = NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap();
let last = if date.month() == 12 {
NaiveDate::from_ymd_opt(date.year() + 1, 1, 1).unwrap() - chrono::Duration::days(1)
} else {
NaiveDate::from_ymd_opt(date.year(), date.month() + 1, 1).unwrap() - chrono::Duration::days(1)
};
(first, last)
}
fn add_months_to_date(date: NaiveDate, months: u32) -> NaiveDate {
let mut year = date.year();
let mut month = date.month() + months;
while month > 12 {
month -= 12;
year += 1;
}
NaiveDate::from_ymd_opt(year, month, 1).unwrap()
}
fn get_upcoming_events_for_month(events: &EventList, first: NaiveDate, last: NaiveDate) -> Vec<&Event> {
let mut upcoming: Vec<&Event> = events.all().iter()
.filter(|e| e.start.date() >= first && e.start.date() <= last)
.collect();
upcoming.sort_by(|a, b| a.start.cmp(&b.start));
upcoming.truncate(20); upcoming
}
fn print_month_header(first_of_month: &NaiveDate, upcoming_iter: &mut std::slice::Iter<&Event>, term_width: u16) {
let month_year = first_of_month.format("%B %Y").to_string().bold();
print!("{:^20} ", month_year);
if let Some(event) = upcoming_iter.next() {
print_upcoming_event(event, term_width);
} else {
println!();
}
}
fn print_weekday_header(upcoming_iter: &mut std::slice::Iter<&Event>, term_width: u16, show_coming_up: bool) {
if show_coming_up {
print!("Mo Tu We Th Fr Sa Su Coming up:");
} else {
print!("Mo Tu We Th Fr Sa Su ");
}
if let Some(event) = upcoming_iter.next() {
print_upcoming_event(event, term_width);
} else {
println!();
}
}
fn print_month_grid(events: &EventList, first: NaiveDate, last: NaiveDate, upcoming_iter: &mut std::slice::Iter<&Event>, term_width: u16) {
let mut current_date = get_start_of_week(first);
let today = chrono::Local::now().date_naive();
while current_date <= last || current_date.month() == first.month() {
let week_start = current_date;
for _day in 0..7 {
print_day_cell(current_date, first.month(), today, events);
current_date = current_date + chrono::Duration::days(1);
}
if let Some(event) = upcoming_iter.next() {
print_upcoming_event(event, term_width);
} else {
println!();
}
if current_date > last && week_start.month() != first.month() {
break;
}
}
}
fn print_day_cell(date: NaiveDate, target_month: u32, today: NaiveDate, events: &EventList) {
let day_str = format!("{:2}", date.day());
if date.month() != target_month {
print!(" ");
} else if date == today {
print!("{} ", day_str.on_white().black());
} else if !events.on_date(date).is_empty() {
print!("{} ", day_str.bold());
} else {
print!("{} ", day_str);
}
}
fn print_upcoming_event(event: &Event, term_width: u16) {
print!(" ");
let date = event.start.format("%d %b").to_string();
let start_time = event.start.format("%H:%M").to_string();
let end_time = event.end.format("%H:%M").to_string();
let location_part = event
.location
.as_ref()
.map_or(String::new(), |loc| format!(" in {}", loc));
let full_event = format!(
"{} {}-{} - {}{}",
date, start_time, end_time, event.name, location_part
);
let available_width = (term_width as usize).saturating_sub(22);
if full_event.len() > available_width {
let truncated = format!("{}...", &full_event[..available_width.saturating_sub(3)]);
println!("{}", truncated);
} else {
println!("{}", full_event);
}
}
fn get_terminal_width() -> u16 {
terminal_size()
.map(|(Width(w), _)| w)
.unwrap_or(80)
}