use std::{collections::{HashMap, HashSet}, fs, path::PathBuf};
use crate::pcontent::{
PAbstractContent, PAbstractLectureBackend,
PReferenceUrl,
PLabelId
};
use crate::pinvitation::{PEvent, PInvitation};
#[derive(Debug, Clone, PartialEq)]
pub struct PContentTimeBlock{
p_title: String,
p_invitation: String,
p_style: String,
p_nb_time_row: usize,
p_is_empty_block: bool,
p_date: String,
p_begin_time: String,
p_end_time: String,
p_duration: String,
p_location: String,
}
impl PContentTimeBlock{
pub fn new(title: &String, invitation: &String) -> Self{
PContentTimeBlock {
p_title: title.clone(),
p_invitation: invitation.clone(),
p_style: String::from(""),
p_nb_time_row: 0,
p_is_empty_block: true,
p_date: String::from(""),
p_begin_time: String::from(""),
p_end_time: String::from(""),
p_duration: String::from(""),
p_location: String::from(""),
}
}
pub fn set_style(&mut self, style: &String){
self.p_style = style.clone();
}
pub fn set_time(&mut self, date: &String, begin_time: &String, end_time: &String, duration: &String){
self.p_date = date.clone();
self.p_begin_time = begin_time.clone();
self.p_end_time = end_time.clone();
self.p_duration = duration.clone();
}
pub fn set_location(&mut self, location: &String){
self.p_location = location.clone();
}
pub fn get_title(&self) -> &String{
&self.p_title
}
pub fn get_invitation(&self) -> &String{
&self.p_invitation
}
pub fn get_date(&self) -> &String{
&self.p_date
}
pub fn get_begin_time(&self) -> &String{
&self.p_begin_time
}
pub fn get_end_time(&self) -> &String{
&self.p_end_time
}
pub fn get_location(&self) -> &String{
&self.p_location
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PContentTimeRow{
p_map_day: HashMap<String, PContentTimeBlock>,
}
impl PContentTimeRow {
fn add_block(&mut self, block: &PContentTimeBlock){
self.p_map_day.insert(block.p_date.clone(), block.clone());
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PContentWeek{
p_week_title: String,
p_invitation: String,
p_map_row: HashMap<String, PContentTimeRow>,
p_map_time: HashMap<String, usize>,
p_map_date: HashSet<String>,
}
impl PContentWeek {
fn new(title: &String, invitation: &String) -> Self{
PContentWeek {
p_week_title: title.clone(),
p_invitation: invitation.clone(),
p_map_row: Default::default(),
p_map_time: Default::default(),
p_map_date: Default::default()
}
}
fn add_block(&mut self, block: &PContentTimeBlock){
self.p_map_time.insert(block.p_begin_time.clone(), 0);
self.p_map_date.insert(block.p_date.clone());
match self.p_map_row.get_mut(&block.p_begin_time) {
Some(row) => row.add_block(block),
None => {
let mut row: PContentTimeRow = Default::default();
row.add_block(block);
self.p_map_row.insert(block.p_begin_time.clone(), row);
}
}
if !self.p_map_time.contains_key(&block.p_end_time) {
self.p_map_time.insert(block.p_end_time.clone(), 0);
}
}
fn update_block_row(&mut self){
let mut vec_time: Vec<String> = self.p_map_time.iter().map(|(key, _)| key.clone()).collect::<Vec<String>>();
vec_time.sort();
for (i, key) in vec_time.iter().enumerate() {
*self.p_map_time.get_mut(key).unwrap() = i;
}
for (start_time, row) in self.p_map_row.iter_mut() {
let index_start_time: usize = *self.p_map_time.get(start_time).unwrap();
for (_, block) in row.p_map_day.iter_mut() {
let index_end_time: usize = *self.p_map_time.get(&block.p_end_time).unwrap();
block.p_nb_time_row = index_end_time - index_start_time;
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PContentTimeTable{
p_main_url: String,
p_invitation: String,
p_vec_week_name: Vec<String>,
p_map_week: HashMap<String, PContentWeek>,
p_current_date: String,
p_block_counter: usize,
p_main_invitation: PInvitation,
p_map_invitation_week: HashMap<String, PInvitation>,
p_map_invitation_date: HashMap<String, PInvitation>,
p_map_invitation_session: HashMap<String, PInvitation>,
}
impl PContentTimeTable {
pub fn new(main_url: &String, invitation: &String) -> Self{
PContentTimeTable {
p_main_url: main_url.clone(),
p_invitation: invitation.clone(),
p_vec_week_name: Default::default(),
p_map_week: Default::default(),
p_current_date: String::from("20260607T150300"),
p_block_counter: 0,
p_main_invitation: PInvitation::new(invitation),
p_map_invitation_week: Default::default(),
p_map_invitation_date: Default::default(),
p_map_invitation_session: Default::default(),
}
}
pub fn add_week(&mut self, title: &String, invitation: &String){
let week = PContentWeek::new(title, invitation);
self.p_map_week.insert(week.p_invitation.clone(), week.clone());
self.p_vec_week_name.push(week.p_invitation.clone());
}
pub fn add_block(&mut self, week_invitation: &String, block: &PContentTimeBlock){
match self.p_map_week.get_mut(week_invitation) {
Some(week) => week.add_block(block),
None => panic!("PContentTimeTable::add_block : no week with invitation '{}' in the time table", week_invitation)
}
if block.p_invitation.is_empty() { return;
}
self.p_block_counter += 1;
let event = PEvent::new(block.get_title(), &block.p_invitation, &block.p_date, &block.p_begin_time, &block.p_end_time, &block.p_location, self.p_block_counter, &self.p_current_date);
self.p_main_invitation.add_event(&event);
add_invitation_in_map(&mut self.p_map_invitation_week, week_invitation, &event);
add_invitation_in_map(&mut self.p_map_invitation_date, &block.p_date, &event);
add_invitation_in_map(&mut self.p_map_invitation_session, &block.p_invitation, &event);
}
pub fn udpate_block_row(&mut self){
for (_, week) in self.p_map_week.iter_mut(){
week.update_block_row();
}
}
pub fn save_invitation(&self, output_path: &PathBuf){
let output_invitation_path = output_path.join(PathBuf::from("invitation"));
fs::create_dir_all(&output_invitation_path).expect(&format!("PContentTimeTable::save_invitation : cannot create output directory of invitation {:?}", output_invitation_path));
self.p_main_invitation.write(&output_invitation_path, &self.p_main_url);
for (_, invitation) in self.p_map_invitation_week.iter() {
invitation.write(&output_invitation_path, &self.p_main_url);
}
for (_, invitation) in self.p_map_invitation_date.iter() {
invitation.write(&output_invitation_path, &self.p_main_url);
}
for (_, invitation) in self.p_map_invitation_session.iter() {
invitation.write(&output_invitation_path, &self.p_main_url);
}
}
}
fn add_invitation_in_map(map_invitation: &mut HashMap<String, PInvitation>, key_name: &String, event: &PEvent){
match map_invitation.get_mut(key_name) {
Some(week) => week.add_event(&event),
None => {
let mut week = PInvitation::new(key_name);
week.add_event(&event);
map_invitation.insert(key_name.clone(), week.clone());
}
}
}
impl PAbstractContent for PContentTimeTable{
fn has_embeded_label(&self) -> bool{
false
}
fn get_reference_url(&self, current_file: &String, id: usize) -> PReferenceUrl{
PReferenceUrl::from_text(current_file, id, &String::from("PContentTimeTable"))
}
fn to_html<TLectureBackend>(&self, backend: &mut TLectureBackend, id: &PLabelId)
where TLectureBackend: PAbstractLectureBackend
{
backend.write(&String::from(format!("<table id=\"{}\" class=\"timetableStyle\">\n", id.get_id())));
for week_name in self.p_vec_week_name.iter() {
let week = self.p_map_week.get(week_name).unwrap();
backend.write(&String::from("<tr><td class=\"timetableWeekRowStyle\">"));
if !week.p_invitation.is_empty() {
backend.write(&String::from(format!("<a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>", week.p_invitation)));
}
if !week.p_week_title.is_empty() {
backend.write(&String::from(format!("<span class=\"timetableWeekTitle\">{}</span>", week.p_week_title)));
}
backend.write(&String::from("</td></tr>\n"));
backend.write(&String::from("\t<tr class=\"timetableMainWeekRowStyle\"><td class=\"timetableMainWeekRowStyle\">\n"));
backend.write(&String::from("\t\t<table class=\"timetableWeekStyle\">\n"));
backend.write(&String::from("\t\t\t<tr class=\"timetableTimeRow\">\n"));
backend.write(&String::from("\t\t\t\t<td class=\"timetableDayStyle\">Period/Day</td>\n"));
let mut vec_date: Vec<String> = week.p_map_date.iter().map(|key| key.clone()).collect::<Vec<String>>();
vec_date.sort();
for date_name in vec_date.iter() {
backend.write(&String::from(format!("\t\t\t\t<td class=\"timetableDayStyle\"><a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>{}</td>\n", date_name, date_name)));
}
backend.write(&String::from("\t\t\t</tr>\n"));
let mut vec_time: Vec<String> = week.p_map_row.iter().map(|(key, _)| key.clone()).collect::<Vec<String>>();
vec_time.sort();
let mut map_already_used_time: HashMap<String, String> = Default::default();
for row_time in vec_time.iter(){
let row = week.p_map_row.get(row_time).unwrap();
backend.write(&String::from("\t\t\t<tr class=\"timetableTimeRow\">\n"));
backend.write(&String::from(format!("\t\t\t\t<td class=\"timetableTimeStyle\"><span class=\"timetableTimeTextStyle\">{}</span></td>\n", row_time)));
for date_name in vec_date.iter() {
match row.p_map_day.get(date_name) {
Some(date) => {
backend.write(&String::from(format!("\t\t\t\t<td rowspan=\"{}\" class=\"{}\">", date.p_nb_time_row, date.p_style)));
if !date.p_invitation.is_empty(){
backend.write(&String::from(format!("<a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>", date.p_invitation)));
}
backend.write(&String::from(format!("{}", date.p_title)));
backend.write(&String::from("</td>\n"));
map_already_used_time.insert(date_name.clone(), date.p_end_time.clone());
}, None => {
match map_already_used_time.get(date_name) {
Some(end_time) => {
if row_time >= end_time { backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"));
map_already_used_time.remove(row_time);
}
},
None => backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"))
}
}
}
}
backend.write(&String::from("\t\t\t</tr>\n"));
}
backend.write(&String::from("\t\t</table>\n"));
backend.write(&String::from("\t</td></tr>\n"));
}
backend.write(&String::from("</table>\n"));
}
}