use std::{
    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
    sync::{Arc, OnceLock},
    thread::sleep,
    time::Duration,
};

use activities::activities;
use eframe::{egui::mutex::RwLock, glow::MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS};
use rand::{seq::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};

use super::{
    activities::{self, partnered_act, selected_act, Activity},
    classes::{get_classes, validates_c_lims, validates_t_lims, Class},
    generate::{
        continued, create_total_hours, generating, multi_thread, recalc_sched, total_hours,
    },
    get_school,
    limitations::limitations_ui::{
        classes_sched, create_classes_sched, create_teachers_sched, get_class_limitations,
        get_teacher_limitations, schedules, teachers_limitations, teachers_scheds, update_tt,
        ClassLimitation, TeacherLimitation,
    },
    school_hour,
    teachers::{get_teachers, Teacher},
};
pub fn act_total_teachers() -> &'static Arc<RwLock<HashMap<i32, Vec<i32>>>> {
    static STOP: OnceLock<Arc<RwLock<HashMap<i32, Vec<i32>>>>> = OnceLock::new();
    STOP.get_or_init(|| Arc::new(RwLock::new(create_act_total_teachers())))
}
fn create_act_total_teachers() -> HashMap<i32, Vec<i32>> {
    let acts = activities().read().clone();
    let mut map = HashMap::new();
    for act in &acts {
        let mut v = act.1.teachers.clone();
        for a in &act.1.partners {
            if let Some(a) = acts.get(&a) {
                v.append(&mut a.teachers.clone())
            };
        }
        map.insert(act.1.id, v);
    }
    map
}
pub fn act_total_classes() -> &'static Arc<RwLock<HashMap<i32, Vec<i32>>>> {
    static STOP: OnceLock<Arc<RwLock<HashMap<i32, Vec<i32>>>>> = OnceLock::new();
    STOP.get_or_init(|| Arc::new(RwLock::new(create_act_total_classes())))
}
pub fn available_days() -> &'static Arc<RwLock<HashMap<i32, Vec<(usize, Vec<usize>)>>>> {
    static STOP: OnceLock<Arc<RwLock<HashMap<i32, Vec<(usize, Vec<usize>)>>>>> = OnceLock::new();
    STOP.get_or_init(|| Arc::new(RwLock::new(create_available_days())))
}
pub fn conflict_acts() -> &'static Arc<RwLock<HashSet<i32>>> {
    static STOP: OnceLock<Arc<RwLock<HashSet<i32>>>> = OnceLock::new();
    STOP.get_or_init(|| Arc::new(RwLock::new(HashSet::new())))
}

pub fn create_available_days() -> HashMap<i32, Vec<(usize, Vec<usize>)>> {
    let acts = activities().read();
    let mut map = HashMap::new();
    let hour = get_school().read().hour;
    for a in &*acts {
        let mut days = vec![];
        for i in 0..7 {
            let mut hours = vec![];
            for h in (0..hour).rev() {
                if a.1.teachers_available(h, i) && a.1.classes_available(h, i) {
                    hours.push(h);
                }
            }
            if !hours.is_empty() {
                days.push((i, hours));
            }
        }
        map.insert(*a.0, days);
    }
    map
}
// fn started() -> &'static Arc<RwLock<bool>> {
//     static STOP: OnceLock<Arc<RwLock<bool>>> = OnceLock::new();
//     STOP.get_or_init(|| Arc::new(RwLock::new(false)))
// }
fn create_act_total_classes() -> HashMap<i32, Vec<i32>> {
    let acts = activities().read().clone();
    let mut map = HashMap::new();
    for act in &acts {
        let mut v = act.1.classes.clone();
        for a in &act.1.partners {
            if let Some(a) = acts.get(&a) {
                v.append(&mut a.classes.clone())
            };
        }
        map.insert(act.1.id, v);
    }
    map
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(crate = "serde")]
pub struct Schedule {
    pub day_id: usize,
    pub hour: usize,
    pub activity: i32,
    pub locked: bool,
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(crate = "serde")]
pub struct Params {
    pub hour: usize,
    pub depth: usize,
    pub depth2: usize,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(crate = "serde")]
pub struct TimetableData {
    pub tat: HashMap<i32, TeacherLimitation>,
    pub cat: HashMap<i32, ClassLimitation>,
    pub clean_tat: HashMap<i32, TeacherLimitation>,
    pub clean_cat: HashMap<i32, ClassLimitation>,
    pub acts: Vec<Activity>,
    pub teachers_acts: HashMap<i32, Vec<Activity>>,
    pub neighbour_acts: HashMap<i32, HashSet<i32>>,
    pub classes: BTreeMap<i32, Class>,
    pub teachers: BTreeMap<i32, Teacher>,
    pub timetables: HashMap<i32, Vec<Schedule>>,
    pub school_hour: usize,
    pub not_placed_acts: HashSet<i32>,
}

// fn cpu_nums() -> &'static Arc<RwLock<u8>> {
//     static STOP: OnceLock<Arc<RwLock<u8>>> = OnceLock::new();
//     STOP.get_or_init(|| Arc::new(RwLock::new(0)))
// }

impl TimetableData {
    // fn generate_multi(&mut self, params: &Params, act: &Activity) -> bool {
    //     let cpus = 3;
    //     *started().write() = false;
    //     drop(started().write());
    //     let ac_dt = Arc::new(RwLock::new(self.clone()));
    //     // drop(start);
    //     for _ in 0..cpus {
    //         let a = act.clone();
    //         let p = params.clone();
    //         let ac_clone = Arc::clone(&ac_dt);
    //         // if *cond_rec.read() {
    //         //     break;
    //         // }
    //         std::thread::spawn(move || {
    //             let s = ac_clone
    //                 .write()
    //                 .recursive_put(&a.clone(), 0, &a.clone(), &p.clone());
    //             if s {
    //                 // *started().write() = true;
    //                 // drop(started().write());
    //                 // let l = data().read().timetables.len();
    //                 // if ac_clone.read().timetables.len() > l {
    //                 *data().write() = ac_clone.read().clone();
    //                 // }
    //             }
    //         });
    //     }
    //     if self.timetables.len() < data().read().timetables.len() {
    //         *self = data().read().clone();
    //         return true;
    //     }
    //     return false;
    // }
    pub fn generate(&mut self, params: &mut Params) -> bool {
        // println!("{}", self.not_placed_acts.len());
        let mut act = self.not_placed_acts.iter();
        let Some(act) = act.next() else {
            return true;
        };
        let acts = activities().read();
        let Some(act) = acts.get(&act) else {
            return false;
        };
        *selected_act().write() = act.id;
        let available = self.find_timeslot(act, params);
        match available {
            Some(slots) => {
                self.put_act(slots.0, slots.1, act, false);
                return true;
            }
            None => {
                let mut s_c = self.clone();
                // if *multi_thread().read() {
                //     if s_c.generate_multi(params, act) {
                //         *self = s_c;
                //         return true;
                //     };
                conflict_acts().write().clear();
                if s_c.recursive_put(act, 0, &act, params) {
                    *self = s_c;
                    return true;
                }
                // let mut days = create_available_days();
                // days.iter_mut().for_each(|d| d.1.shuffle(&mut thread_rng()));
                // *available_days().write() = days;

                self.not_placed_acts = HashSet::from_iter(
                    self.acts
                        .clone()
                        .into_iter()
                        .filter(|a| !a.teachers.is_empty() && self.timetables.get(&a.id).is_none())
                        .map(|a| a.id),
                );
                let conflict_acts = self.find_conflict_activity(act, &act, params);
                if conflict_acts.is_empty() {
                    return false;
                }
                for a in &conflict_acts[0] {
                    self.delete_activity(a);
                }
                if let Some(slots) = self.find_timeslot(act, params) {
                    self.put_act(slots.0, slots.1, act, false);
                }
                // for a in &conflict_acts[0] {
                //     if let Some(slots) = self.find_timeslot(a, params) {
                //         self.put_act(slots.0, slots.1, a);
                //     }
                // }
                return true;
            }
        }
    }
    pub fn replace_act(&mut self, day: usize, hour: usize, act_id: i32, act_hour: usize) {
        let tt = &mut self.timetables;
        let scheds = &*schedules().write().clone();
        let ng = self.neighbour_acts.get(&act_id);
        if let Some(ng) = ng {
            let acts = scheds
                .iter()
                .filter(|s| s.day_id == day && ng.iter().any(|a| a == &s.activity))
                .collect::<Vec<&Schedule>>();
            println!("{acts:?}");
            for h in hour..hour + act_hour {
                if let Some(s) = acts.iter().find(|a| a.hour == h) {
                    tt.remove(&s.activity);
                };
            }
            update_tt();
        }
    }
    pub fn find_timeslot(&self, act: &Activity, params: &Params) -> Option<(usize, usize)> {
        let days = available_days().read().clone();
        let days = days.get(&act.id).unwrap();
        if self.tat.len() == 0 {
            validates_c_lims();
            validates_t_lims();
            return None;
        }

        // days.shuffle(&mut thread_rng());
        // let hour_lim = self.school_hour - act.hour as usize + 1;
        for day in days {
            for hour in &day.1 {
                if self.teachers_available(act, *hour, day.0)
                    && self.classes_available(act, *hour, day.0)
                    && (
                        //act.teachers.is_empty()
                        act.no_limit || self.same_day_available(act, *hour, day.0, params)
                    )
                {
                    // println!("{:?} {}", day, hour);
                    return Some((day.0, *hour));
                }
            }
        }
        None
    }
    fn same_day_available(&self, act: &Activity, hour: usize, day: usize, params: &Params) -> bool {
        if let Some(teacher_acts) = self.teachers_acts.get(&act.id) {
            let mut same_day_acts = vec![];
            for a in teacher_acts {
                if !a.no_limit {
                    if let Some(s) = self.timetables.get(&a.id) {
                        same_day_acts.append(&mut s.clone());
                        // return false;
                    }
                }
            }
            let same_day_acts: Vec<&Schedule> =
                same_day_acts.iter().filter(|t| t.day_id == day).collect();
            if same_day_acts.len() == 0 {
                return true;
            } else if (act.hour + same_day_acts.len()) > params.hour {
                return false;
            } else {
                let hours = same_day_acts
                    .iter()
                    .cloned()
                    .find(|t| hour > 0 && t.hour == (hour - 1) || t.hour == (hour + act.hour));
                if hours.is_some() {
                    return true;
                }
                return false;
            }
        }
        true
    }
    fn classes_available(&self, act: &Activity, hour: usize, day: usize) -> bool {
        let mut classes_availables = vec![];
        let act_classes = act_total_classes().read();
        let Some(classes) = act_classes.get(&act.id) else {
            return false;
        };

        for class in classes {
            let class = self.cat.get(class);
            if let Some(c) = class {
                classes_availables.push(c.limitations[day].clone());
            }
        }
        (hour..hour + act.hour as usize).all(|h| classes_availables.iter().all(|ca| ca[h]))
    }
    fn teachers_available(&self, act: &Activity, hour: usize, day: usize) -> bool {
        let mut teachers_availables = vec![];
        let act_teachers = act_total_teachers().read();
        let Some(teachers) = act_teachers.get(&act.id) else {
            return false;
        };
        for teacher in teachers {
            let teacher = self.tat.get(teacher);
            if let Some(t) = teacher {
                teachers_availables.push(t.limitations[day].clone());
            }
        }
        (hour..hour + act.hour as usize).all(|h| teachers_availables.iter().all(|ta| ta[h]))
    }
    pub fn put_act(&mut self, day: usize, hour: usize, act: &Activity, lock: bool) {
        for timetable in hour..hour + act.hour as usize {
            let tt = Schedule {
                day_id: day,
                hour: timetable,
                activity: act.id,
                locked: lock,
            };
            let act_teachers = act_total_teachers().read();
            if let Some(teachers) = act_teachers.get(&act.id) {
                for teacher in teachers {
                    if let Some(tat) = self.tat.get_mut(teacher) {
                        if let Some(tat_index) = tat.limitations.get_mut(tt.day_id) {
                            tat_index[tt.hour as usize] = false;
                        }
                    }
                }
            }
            let act_classes = act_total_classes().read();
            if let Some(classes) = act_classes.get(&act.id) {
                for class in classes {
                    if let Some(cat) = self.cat.get_mut(class) {
                        if let Some(cat_index) = cat.limitations.get_mut(tt.day_id) {
                            cat_index[tt.hour] = false;
                        }
                    }
                }
            }
            if let Some(timetables) = self.timetables.get_mut(&act.id) {
                timetables.push(tt);
            } else {
                self.timetables.insert(act.id, vec![tt.clone()]);
            }
        }
        self.not_placed_acts.remove(&act.id);
    }
    pub fn delete_activity(&mut self, act: &Activity) {
        let tt = self.timetables.get(&act.id).unwrap();
        for t in tt {
            let act_teachers = act_total_teachers().read();
            if let Some(teachers) = act_teachers.get(&act.id) {
                for teacher in teachers {
                    if let Some(tat) = self.tat.get_mut(teacher) {
                        if let Some(tat_index) = tat.limitations.get_mut(t.day_id) {
                            tat_index[t.hour as usize] = true;
                        }
                    }
                }
            }
            let act_classes = act_total_classes().read();
            if let Some(classes) = act_classes.get(&act.id) {
                for class in classes {
                    if let Some(cat) = self.cat.get_mut(class) {
                        if let Some(cat_index) = cat.limitations.get_mut(t.day_id) {
                            cat_index[t.hour as usize] = true;
                        }
                    }
                }
            }
        }
        self.not_placed_acts.insert(act.id);
        self.timetables.remove(&act.id);
        //self.timetables.retain(|t| t.activity != act.id);
    }
    fn find_conflict_activity(
        &self,
        act: &Activity,
        ignores: &Activity,
        params: &Params,
    ) -> Vec<Vec<Activity>> {
        //let now = instant::Instant::now();
        let mut total_act: Vec<Vec<Activity>> = Vec::new();
        let acts = self.neighbour_acts.get(&act.id).unwrap();
        let days = &*available_days().read();
        let days = days.get(&act.id).unwrap();
        for day in days {
            for h in &day.1 {
                let mut less_conflict: HashSet<Activity> = HashSet::new();
                for i in *h..*h + act.hour as usize {
                    for a in acts {
                        if let Some(s) = self.timetables.get(&a) {
                            let sa = s.iter().find(|t| {
                                t.day_id == day.0 && t.hour as usize == i && !t.locked
                                // && ignores.id != t.activity
                            });
                            if let Some(s) = sa {
                                if let Some(a) = activities().read().get(&s.activity) {
                                    less_conflict.insert(a.clone());
                                };
                            };
                        }
                    }
                }
                if !less_conflict.is_empty() {
                    total_act.push(Vec::from_iter(less_conflict));
                }
            }
        }
        total_act.shuffle(&mut thread_rng());
        if total_act.len() >= params.depth {
            return total_act[..params.depth].to_vec();
        }
        total_act
    }
    pub(crate) fn recursive_put(
        &mut self,
        act: &Activity,
        depth: usize,
        ignores: &Activity,
        params: &Params,
    ) -> bool {
        let mut conflict_acts = self.find_conflict_activity(act, ignores, params);
        let mut okey2 = false;
        let tat2 = self.tat.clone();
        let not_placed_acts = self.not_placed_acts.clone();
        let cat2 = self.cat.clone();
        let timetables2 = self.timetables.clone();
        for c_act in &mut conflict_acts {
            for a in &*c_act {
                self.delete_activity(a);
            }
            c_act.sort_by(|a, b| a.hour.cmp(&b.hour));
            c_act.push(act.clone());
            //ignore_list.append(&mut c_act.clone());
            let mut okey = true;
            for a in c_act.iter().rev() {
                let available = self.find_timeslot(a, params);
                match available {
                    Some(slots) => {
                        self.put_act(slots.0, slots.1, a, false);
                    }
                    None => {
                        if depth < params.depth2 {
                            let rec_result = self.recursive_put(a, depth + 1, ignores, params);
                            if !rec_result {
                                okey = false;
                                break;
                            }
                        } else {
                            okey = false;
                            break;
                        }
                    }
                }
            }
            if okey {
                okey2 = true;
                //ignore_list.retain(|a3| a3.id != act.id);
                break;
            } else {
                self.tat = tat2.to_owned();
                self.cat = cat2.to_owned();
                self.timetables = timetables2.to_owned();
                self.not_placed_acts = not_placed_acts.to_owned();
            }
        }
        okey2
    }
    fn validate_tt(&mut self) {
        self.clean_cat = get_class_limitations();
        self.clean_tat = get_teacher_limitations();
        self.tat = self.clean_tat.clone();
        self.cat = self.clean_cat.clone();
        let tt_c = self.timetables.clone();
        self.timetables.clear();
        schedules().write().clear();
        teachers_scheds().write().clear();
        classes_sched().write().clear();
        self.not_placed_acts = self.acts.iter().map(|a| a.id.clone()).collect();
        for tt in &tt_c {
            let act = self
                .acts
                .clone()
                .into_iter()
                .find(|a| a.id == *tt.0)
                .unwrap();
            if self.teachers_available(&act, tt.1[0].hour as usize, tt.1[0].day_id)
                && self.classes_available(&act, tt.1[0].hour as usize, tt.1[0].day_id)
                && (act.no_limit
                    || self.same_day_available(
                        &act,
                        tt.1[0].hour as usize,
                        tt.1[0].day_id,
                        &Params {
                            hour: 2,
                            depth: 0,
                            depth2: 0,
                        },
                    ))
            {
                self.put_act(tt.1[0].day_id, tt.1[0].hour as usize, &act, tt.1[0].locked);
            } else {
                // self.delete_activity(&act);
            }
        }
        let mut sch = vec![];
        self.timetables
            .values()
            .for_each(|tt| sch.append(&mut tt.clone()));
        *schedules().write() = sch.clone();
        *teachers_scheds().write() = create_teachers_sched();
        *classes_sched().write() = create_classes_sched();

        *data().write() = self.clone();
    }
}
pub fn create_data() -> TimetableData {
    // create_acts_data();
    let clean_tat = get_teacher_limitations();
    let tat = get_teacher_limitations();
    let clean_cat = get_class_limitations();
    let cat = get_class_limitations();
    let mut acts = (*activities().read())
        .clone()
        .values()
        .filter(|a| partnered_act().read().get(&a.id).is_none())
        .map(|a| a.clone())
        .collect::<Vec<Activity>>();
    acts.shuffle(&mut thread_rng());
    acts.sort_by(|a, b| a.hour.cmp(&b.hour));
    let not_placed_acts = acts.iter().map(|a| a.id).collect();
    let hour = &*school_hour().read();
    let h = hour.parse::<usize>().unwrap_or(8);
    let timetables = HashMap::new();
    let dt = TimetableData {
        tat,
        cat,
        clean_cat,
        clean_tat,
        acts,
        teachers_acts: create_teachers_acts(),
        neighbour_acts: create_ng(),
        classes: get_classes().read().clone(),
        teachers: (*get_teachers().read()).clone(),
        timetables, //(*schedules().read()).clone(),
        school_hour: h,
        not_placed_acts,
    };
    dt
}

fn create_teachers_acts() -> HashMap<i32, Vec<Activity>> {
    let activities = (*activities().read()).clone();
    let acts: Vec<Activity> = activities.clone().values().map(|a| a.clone()).collect();
    let mut ts_acts = HashMap::new();
    for act in &activities {
        let acts: Vec<Activity> = acts
            .iter()
            .cloned()
            .filter(
                |a| {
                    act.1
                        .teachers
                        .iter()
                        .all(|t| a.teachers.iter().any(|t2| t2 == t))
                        && act
                            .1
                            .classes
                            .iter()
                            .all(|c| a.classes.iter().any(|c2| c2 == c))
                }, //&& act.lecture == a.lecture
            )
            // .map(|a| a.1)
            .collect();
        ts_acts.insert(act.1.id, acts);
        // teachers_acts().set(ts_acts);
    }
    ts_acts
}

pub fn data() -> &'static Arc<RwLock<TimetableData>> {
    static DATA: OnceLock<Arc<RwLock<TimetableData>>> = OnceLock::new();
    DATA.get_or_init(|| Arc::new(RwLock::new(create_data())))
}
pub fn generate() {
    // create_data();
    let mut params = Params {
        hour: 2,
        depth: 6,
        depth2: 6,
    };
    std::thread::spawn(move || {
        let mut t_data = data().read().clone();
        t_data.validate_tt();
        t_data.timetables = recalc_sched();
        *total_hours().write() = create_total_hours();
        let mut sch = vec![];
        t_data
            .timetables
            .values()
            .for_each(|tt| sch.append(&mut tt.clone()));
        *schedules().write() = sch.clone();
        *teachers_scheds().write() = create_teachers_sched();
        *classes_sched().write() = create_classes_sched();
        // println!("not:{:?}", t_data.not_placed_acts);
        loop {
            if !*generating().read() {
                break;
            }
            // let len = t_data.timetables.len();
            if t_data.not_placed_acts.is_empty() {
                // // t_data.timetables = recalc_sched();
                // // is_generate().set(true);
                let mut sch = vec![];
                t_data
                    .timetables
                    .values()
                    .for_each(|tt| sch.append(&mut tt.clone()));
                *schedules().write() = sch.clone();
                *teachers_scheds().write() = create_teachers_sched();
                *classes_sched().write() = create_classes_sched();
                *generating().write() = false;
                *continued().write() = false;
                break;
            }
            if t_data.generate(&mut params) {
                if t_data.timetables.len() >= data().read().timetables.len() {
                    *data().write() = t_data.clone();
                } else {
                    t_data = data().read().clone();
                }
                let mut sch = vec![];
                t_data
                    .timetables
                    .values()
                    .for_each(|tt| sch.append(&mut tt.clone()));
                *schedules().write() = sch.clone();
                *teachers_scheds().write() = create_teachers_sched();
                *classes_sched().write() = create_classes_sched();
            } else {
                *generating().write() = false;
                *continued().write() = false;
                break;
            }
        }
    });
    // println!("{:?}", t_data.timetables);
}

fn create_ng() -> HashMap<i32, HashSet<i32>> {
    let acts = &*activities()
        .read()
        .values()
        .map(|a| a.clone())
        .collect::<Vec<Activity>>();
    let mut neighbours: HashMap<i32, HashSet<i32>> = HashMap::new();
    for a in &*acts {
        let ns = &*acts
            // .clone()
            .into_iter()
            .filter(|a2| {
                a2.id != a.id
                    && (a2
                        .classes
                        .iter()
                        .any(|c| a.classes.iter().any(|c2| c2 == c))
                        || a2
                            .teachers
                            .iter()
                            .any(|t| a.teachers.iter().any(|t2| t2 == t)))
            })
            .collect::<Vec<&Activity>>();
        let neigh_map: HashSet<i32> = HashSet::from_iter(ns.iter().map(|n| n.id));
        neighbours.insert(a.id, neigh_map);
    }
    neighbours
}