// src/lib.rs

use serde::{Serialize, Deserialize};
use serde_json;
use std::fmt;

#[derive(Default, Serialize, Deserialize, PartialEq)]
enum Semester {
    #[default]
    Zimny,
    Letny
}

impl fmt::Display for Semester {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Semester::Zimny => write!(f, "Zimny"),
            Semester::Letny => write!(f, "Letny"),
        }
    }
}

#[derive(Default, Serialize, Deserialize, PartialEq, Eq, Hash, Copy, Clone)]
enum TypPovinnosti {
    Povinny,
    PovinneVoliteny,
    #[default]
    Vyberovy,
}

impl fmt::Display for TypPovinnosti {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TypPovinnosti::Povinny => write!(f, "Povinny"),
            TypPovinnosti::PovinneVoliteny => write!(f, "PovinneVoliteny"),
            TypPovinnosti::Vyberovy => write!(f, "Vyberovy"),
        }
    }
}

#[derive(Default, Serialize, Deserialize)]
struct Predmet {
    kod: String,
    nazov: String,
    vyucujuci: Vec<String>,
    popis: String,
    semester: Semester,
    rocnik: u8,
    pocet_kreditov: u8,
    znamka: char,
    typ_povinnosti: TypPovinnosti
}

impl fmt::Display for Predmet {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {}, {}, {}, {}, {}, {}, {}, {})",
               self.kod,
               self.nazov,
               self.vyucujuci.join(", "),
               self.popis,
               self.semester,
               self.rocnik,
               self.pocet_kreditov,
               self.znamka,
               self.typ_povinnosti
        )
    }
}

#[derive(Default, Serialize, Deserialize)]
struct Index {
    predmety: Vec<Predmet>
}

impl Index{
    pub fn nacitaj_zo_suboru(&self, cesta: &std::path::PathBuf) -> Option<Self> {
        let data = match std::fs::read_to_string(cesta) {
            Ok(x) => x,
            Err(_) => return None,
        };
        let vysledok = match serde_json::from_str(data.as_str()) {
            Ok(k) => k,
            Err(_) => return None,
        };
        Some(vysledok)
    }

    pub fn uloz_do_suboru(&self, cesta_uloz: &std::path::PathBuf) -> bool {
        let str_data = match serde_json::to_string(&self) {
            Ok(x) => x,
            Err(_) => return false,
        };
        match std::fs::write(cesta_uloz, str_data) {
            Ok(_) => true,
            Err(_) => false,
        }
    }

    pub fn pridaj_predmet2(&mut self, predmet: Predmet) -> Result<(), ()> {
        let mut je: bool;
        let var_name = self.predmety.iter().any(|x| x.kod == predmet.kod);

        if var_name {
            return Err(());
        }
        self.predmety.push(predmet);
        Ok(())
    }

    pub fn pridaj_predmet(&mut self, predmet: Predmet) -> Result<(), ()> {
        let mut je: bool;
        let var_name = self.predmety.iter().filter(|x| x.kod == predmet.kod).count();

        if var_name > 0 {
            return Err(());
        } else {
            self.predmety.push(predmet);
        }
        Ok(())
    }

    pub fn odstran_predmet(&mut self, kod: &str) -> Result<Predmet, ()> {
        if let Some(index) = self.predmety.iter().position(|x| x.kod == kod) {
            let odstranenie = self.predmety.remove(index);
            return Ok(odstranenie);
        } else {
            Err(())
        }
    }

    pub fn daj_predmet_podla_kodu(&self, kod: &str) -> Result<Option<&Predmet>, ()> {
        if let Some(vrat) = self.predmety.iter().find(|x| x.kod == kod) {
            return Ok(Some(vrat));
        } else {
            Err(())
        }
    }

    pub fn daj_predmety_vyucujuceho(&self, menov: &str) -> Vec<&Predmet> {
        let vec: Vec<_> = self.predmety.iter().filter(|x| x.vyucujuci.contains(&menov.to_string())).collect();
        vec
    }

    pub fn daj_predmety_podla_povinnosti(&self, tp: TypPovinnosti) -> Vec<&Predmet> {
        let vec: Vec<_> = self.predmety.iter().filter(|x| x.typ_povinnosti == tp).collect();
        vec
    }

    pub fn formatuj_predmety_podla_znamky(&self, znamka: char) -> Vec<String> {
        self.predmety
            .iter()
            .filter(|p| p.znamka == znamka)
            .map(|p| format!("{}", p))
            .collect()
    }
}

impl Predmet {
    pub fn new(
        kod: String,
        nazov: String,
        vyucujuci: Vec<String>,
        popis: String,
        semester: Semester,
        rocnik: u8,
        pocet_kreditov: u8,
        znamka: char,
        typ_povinnosti: TypPovinnosti
    ) -> Self{
        return Self {
            kod: kod.to_string(),
            nazov: nazov.to_string(),
            vyucujuci,
            popis: popis.to_string(),
            semester,
            rocnik,
            pocet_kreditov,
            znamka,
            typ_povinnosti,
        }
    }
}
    // kod: &str,
    // nazov: &str,
    // vyucujuci: Vec<String>,
    // popis: &str,
    // semester: Semester,
    // rocnik: u8,
    // pocet_kreditov: u8,
    // znamka: char,
    // typ_povinnosti: TypPovinnosti
