use std::{
collections::HashMap,
fs::File,
io::{Read, Write},
sync::{Arc, OnceLock},
};
use eframe::egui::{self, mutex::RwLock, Color32, Label, RichText, Sense, Stroke, Vec2};
use serde::{Deserialize, Serialize};
use crate::{
tabs::{
activities::{activities, get_lec_short_name, get_teachers_short_name, Activity},
classes::{get_classes, selected_class, set_selected_class},
generating::Schedule,
},
DAYS,
};
use super::save_classes_limitations;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClassLimitation {
pub class_id: i32,
pub limitations: Vec<Vec<bool>>,
}
impl ClassLimitation {
pub fn new(id: i32) -> Self {
Self {
class_id: id,
limitations: vec![vec![true; 10]; 7],
}
}
}
pub fn classes_limitations() -> &'static Arc<RwLock<HashMap<i32, ClassLimitation>>> {
static PAGE: OnceLock<Arc<RwLock<HashMap<i32, ClassLimitation>>>> = OnceLock::new();
PAGE.get_or_init(|| Arc::new(RwLock::new(get_class_limitations())))
}
pub fn schedules() -> &'static Arc<RwLock<Vec<Schedule>>> {
static PAGE: OnceLock<Arc<RwLock<Vec<Schedule>>>> = OnceLock::new();
PAGE.get_or_init(|| Arc::new(RwLock::new(vec![])))
}
pub fn get_class_limitations() -> HashMap<i32, ClassLimitation> {
let lims = File::open("./db/classes_limitations.json");
if let Ok(mut lims) = lims {
let mut content: String = String::new();
lims.read_to_string(&mut content).unwrap();
let cls: Vec<ClassLimitation> = serde_json::from_str(content.as_str()).unwrap();
let mut lims: HashMap<i32, ClassLimitation> = HashMap::new();
for c in cls {
lims.insert(c.class_id, c);
}
return lims;
}
HashMap::new()
}
type ClassSched = Arc<RwLock<HashMap<i32, Vec<Vec<Option<Activity>>>>>>;
pub fn class_sched() -> &'static ClassSched {
static ACT: OnceLock<ClassSched> = OnceLock::new();
ACT.get_or_init(|| Arc::new(RwLock::new(HashMap::new())))
}
pub fn class_limitation() -> &'static Arc<RwLock<ClassLimitation>> {
static LIM: OnceLock<Arc<RwLock<ClassLimitation>>> = OnceLock::new();
LIM.get_or_init(|| Arc::new(RwLock::new(ClassLimitation::default())))
}
pub fn create_sched(id: i32) {
let class_acts = activities()
.read()
.iter()
.filter(|a| a.1.classes.iter().any(|c| c == &id))
.map(|a| a.1.clone())
.collect::<Vec<Activity>>();
let scs = (*schedules().read()).clone();
let sched = scs
.iter()
.filter(|s| class_acts.iter().any(|a| a.id == s.activity))
.map(|s| s.clone())
.collect::<Vec<Schedule>>();
let mut scheduls: Vec<Vec<Option<Activity>>> = vec![vec![None; 10]; 7];
sched.iter().for_each(|s| {
scheduls[s.day_id as usize - 1][s.hour as usize] = class_acts
.clone()
.into_iter()
.find(|ca| ca.id == s.activity);
});
std::thread::spawn(move || {
let c_sched = Arc::clone(class_sched());
c_sched.write().insert(id, scheduls);
});
}
pub fn save_class_limitations() {
let lims = &*classes_limitations().read();
let lims = lims.values().collect::<Vec<&ClassLimitation>>();
let mut f = File::create("./db/classes_limitations.json").unwrap();
f.write_all(serde_json::to_string(&lims).unwrap().as_bytes())
.expect("Kısıtlamalar Yazılamadı");
}
pub fn update_class_limitation() {
let class_lims = &*class_limitation().read();
let mut lims = classes_limitations().write();
let id = selected_class().read().unwrap();
lims.insert(id, class_lims.clone());
}
pub fn class_lim_ui(ui: &mut egui::Ui) {
for c in &*get_classes().read() {
create_sched(c.1.id);
}
egui::SidePanel::left("right_panel")
.resizable(true)
.default_width(250.0)
.width_range(150.0..=300.0)
.show_inside(ui, |ui| {
ui.vertical_centered(|ui| {
let class_id = selected_class().read();
let mut classes = get_classes().write();
for c in &mut *classes {
let fill_color = if class_id.is_some_and(|c2| c2 == c.1.id) {
Color32::LIGHT_BLUE
} else {
Color32::default()
};
let stroke_color = if class_id.is_some_and(|c2| c2 == c.1.id) {
Color32::DARK_BLUE
} else {
Color32::default()
};
let button = egui::Frame::none()
.fill(fill_color)
.stroke(Stroke::new(1., stroke_color))
.show(ui, |ui| {
ui.label(&c.1.name);
})
.response;
let button = button
.interact(egui::Sense::click())
.on_hover_cursor(egui::CursorIcon::PointingHand);
if button.clicked() {
set_selected_class(c.1.id);
}
}
});
});
egui::CentralPanel::default()
.show_inside(ui, |ui| {
class_limitation_ui(ui);
});
}
fn class_limitation_ui(ui: &mut egui::Ui) {
if let None = *selected_class().read() {
return ();
}
let class_id = selected_class().read().unwrap();
let c_sched = class_sched().read();
let c_sched = c_sched.get(&class_id);
if let Some(c_sched) = c_sched {
ui.add_space(50.);
ui.horizontal(|ui| {
ui.add_space(100.);
egui::Grid::new("some_unique_id")
.min_col_width(100.)
.max_col_width(100.)
.min_row_height(100.)
.spacing((1.0, 1.0))
.show(ui, |ui| {
hours_row(ui);
ui.end_row();
days_row(ui, c_sched);
});
ui.end_row();
});
ui.vertical_centered(|ui| {
ui.add_space(20.);
if ui.button("Kaydet").clicked() {
update_class_limitation();
};
if ui.button("Tümünü dosyaya Kaydet").clicked() {
save_classes_limitations();
};
});
}
}
fn days_row(ui: &mut egui::Ui, c_sched: &Vec<Vec<Option<Activity>>>) {
for day in DAYS.iter().enumerate() {
ui.painter().rect(
ui.available_rect_before_wrap(),
0.0,
Color32::default(),
Stroke::new(1.0, Color32::LIGHT_GRAY),
);
if ui
.add_sized(Vec2::new(100., 50.), Label::new(*day.1))
.interact(Sense::click())
.clicked()
{
change_all_day(day.0);
};
for i in 1..=10 {
row_paint(ui, day.0, i - 1);
let r = ui.add_sized(
Vec2::new(100., 50.),
Label::new(sched_act(&c_sched[day.0][i - 1])),
);
let r = r
.interact(Sense::click())
.on_hover_cursor(egui::CursorIcon::PointingHand);
if r.clicked() {
change_row(day.0, i - 1);
}
}
ui.end_row();
}
}
fn hours_row(ui: &mut egui::Ui) {
ui.painter().rect(
ui.available_rect_before_wrap(),
0.0,
Color32::default(),
Stroke::new(1.0, Color32::LIGHT_GRAY),
);
ui.label("Günler ve Saatler");
for i in 1..=10 {
ui.painter().rect(
ui.available_rect_before_wrap(),
0.0,
Color32::default(),
Stroke::new(1.0, Color32::LIGHT_GRAY),
);
if ui
.add_sized(
Vec2::new(100., 50.),
Label::new(RichText::new(format!("{i}. Saat")).color(Color32::WHITE)),
)
.interact(Sense::click())
.clicked()
{
change_all_hours(i - 1);
};
}
}
fn row_paint(ui: &mut egui::Ui, day: usize, hour: usize) {
let lims = &*class_limitation().read().limitations;
if lims[day][hour] {
ui.painter()
.rect_filled(ui.available_rect_before_wrap(), 0.0, Color32::BROWN);
}
ui.painter().rect(
ui.available_rect_before_wrap(),
0.0,
Color32::default(),
Stroke::new(1., Color32::WHITE),
);
}
fn change_row(day: usize, hour: usize) {
let lims = &*class_limitation();
std::thread::spawn(move || {
let lim = Arc::clone(lims);
let row = lim.read().limitations[day][hour];
lim.write().limitations[day][hour] = !row;
});
}
fn sched_act(a: &Option<Activity>) -> String {
let Some(a) = a else {
return "".to_string();
};
let mut activity = a.id.to_string();
activity.push_str("-");
activity.push_str(&get_lec_short_name(a.lecture));
activity.push_str("\n");
activity.push_str(&get_teachers_short_name(&a.teachers));
activity
}
fn change_all_day(day: usize) {
let lims = &*class_limitation();
std::thread::spawn(move || {
let lim = Arc::clone(lims);
let day = &mut lim.write().limitations[day];
if day.iter().any(|h| !*h) {
*day = vec![true; 10];
} else {
*day = vec![false; 10];
}
});
}
fn change_all_hours(hour: usize) {
let lims = &*class_limitation();
std::thread::spawn(move || {
let lim = Arc::clone(lims);
let day = &mut lim.write().limitations;
if day.iter().any(|h| !h[hour]) {
day.iter_mut().for_each(|d| d[hour] = true);
} else {
day.iter_mut().for_each(|d| d[hour] = false);
}
});
}