use chrono::prelude::*;
use colored::*;
use std::{cmp::PartialEq, fmt};

#[derive(Debug, PartialEq)]
pub enum Result {
    Wrong,
    Correct,
    Partial,
}

impl fmt::Display for Result {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let block = match self {
            Result::Wrong => '⬛'.to_string().truecolor(105, 105, 105),
            Result::Correct => '🟩'.to_string().truecolor(51, 255, 51),
            Result::Partial => '🟨'.to_string().truecolor(255, 255, 51),
        };
        write!(f, "{}", block)?;
        Ok(())
    }
}

#[derive(Debug, PartialEq)]
pub struct Attempt {
    pub slots: Vec<Result>,
}

impl fmt::Display for Attempt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for slot in &self.slots {
            write!(f, "{}", slot)?;
        }
        write!(f, "\n")?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct Stat {
    pub attempts: Vec<Attempt>,
}

impl Stat {
    pub fn start() -> Stat {
        Stat {
            attempts: Vec::new(),
        }
    }
}

#[derive(Debug)]
pub struct DayState {
    pub stat: Stat,
    riddle: String,
    pub date: String,
    pub total_guess: i64,
    pub remaining_guess: i64,
}

impl DayState {
    pub fn get_today() -> String {
        format!("{}", Local::now().format("%b %d, %Y"))
    }

    pub fn new(riddle: String, total_guess: i64) -> DayState {
        DayState {
            riddle: riddle,
            date: DayState::get_today(),
            total_guess: total_guess,
            remaining_guess: total_guess,
            stat: Stat::start(),
        }
    }

    pub fn input_hygiene(&self, guess: &String) -> bool {
        return guess.len() == self.riddle.len() && guess.chars().all(|c| c.is_ascii_lowercase());
    }

    pub fn input_allowed(&self, guess: &String, allowed: &Vec<String>) -> bool {
        allowed.binary_search_by(|p| p.cmp(&guess)).is_ok()
    }

    fn char_compare(_guess_char: &char, _actual_char: &char) -> bool {
        _guess_char == _actual_char
    }

    fn validate(_guess: &String, _actual: &String) -> Attempt {
        let mut res = Vec::new();
        let guess = _guess.chars().collect::<Vec<_>>();
        let actual = _actual.chars().collect::<Vec<_>>();
        let n = _actual.len();
        for i in 0..n {
            let mut result_holder = Result::Wrong;
            for j in 0..n {
                if DayState::char_compare(&guess[i], &actual[j]) {
                    if i == j {
                        result_holder = Result::Correct;
                        break;
                    } else {
                        result_holder = Result::Partial;
                    }
                }
            }
            res.push(result_holder);
        }
        Attempt { slots: res }
    }

    pub fn guess(&mut self, word: &String) -> String {
        self.remaining_guess -= 1;
        self.stat
            .attempts
            .push(DayState::validate(&word, &self.riddle));
        format!("{}", self.stat.attempts.last().unwrap())
    }

    pub fn finished(&self) -> bool {
        return self.remaining_guess == 0
            || (!self.stat.attempts.is_empty()
                && self
                    .stat
                    .attempts
                    .last()
                    .unwrap()
                    .slots
                    .iter()
                    .all(|res| res == &Result::Correct));
    }
}

impl DayState {
    pub fn display_box(&self) -> String {
        let mut result = String::new();
        result.push_str("\n╭────────────────────────────╮\n");
        result.push_str(&format!(
            "│ Poodle {}   {}/{}  │\n",
            self.date,
            self.total_guess - self.remaining_guess,
            self.total_guess
        ));
        result.push_str("├────────────────────────────┤\n");
        for attempt in &self.stat.attempts {
            result.push_str(&format!(
                "│         {}         │\n",
                attempt.to_string().trim()
            ));
        }
        result.push_str("╰────────────────────────────╯\n");
        result
    }
}

impl fmt::Display for DayState {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "\n<>==========<>\n")?;
        write!(
            f,
            "Poodle {} {}/{}\n",
            self.date,
            self.total_guess - self.remaining_guess,
            self.total_guess
        )?;
        for attempt in &self.stat.attempts {
            write!(f, "{}", attempt)?;
        }
        write!(f, "<>==========<>\n")?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validate_correctness() {
        assert_eq!(
            DayState::validate(&String::from("aaaaa"), &String::from("aaaaa")),
            Attempt {
                slots: vec![
                    Result::Correct,
                    Result::Correct,
                    Result::Correct,
                    Result::Correct,
                    Result::Correct
                ]
            }
        );

        assert_eq!(
            DayState::validate(&String::from("bbbbb"), &String::from("ccccc")),
            Attempt {
                slots: vec![
                    Result::Wrong,
                    Result::Wrong,
                    Result::Wrong,
                    Result::Wrong,
                    Result::Wrong
                ]
            }
        );
    }

    #[test]
    fn validate_appearance() {
        assert_eq!(
            DayState::validate(&String::from("abyde"), &String::from("edxba")),
            Attempt {
                slots: vec![
                    Result::Partial,
                    Result::Partial,
                    Result::Wrong,
                    Result::Partial,
                    Result::Partial
                ]
            }
        );

        assert_eq!(
            DayState::validate(&String::from("aaxbb"), &String::from("bbxaa")),
            Attempt {
                slots: vec![
                    Result::Partial,
                    Result::Partial,
                    Result::Correct,
                    Result::Partial,
                    Result::Partial
                ]
            }
        );

        assert_eq!(
            DayState::validate(&String::from("arias"), &String::from("abyss")),
            Attempt {
                slots: vec![
                    Result::Correct,
                    Result::Wrong,
                    Result::Wrong,
                    Result::Partial,
                    Result::Correct
                ]
            }
        );

        assert_eq!(
            DayState::validate(&String::from("again"), &String::from("abyss")),
            Attempt {
                slots: vec![
                    Result::Correct,
                    Result::Wrong,
                    Result::Partial,
                    Result::Wrong,
                    Result::Wrong
                ]
            }
        );
    }

    #[test]
    fn validate_char_compare() {
        assert_eq!(DayState::char_compare(&'a', &'a'), true);
        assert_eq!(DayState::char_compare(&'\0', &'\0'), true);
        assert_eq!(DayState::char_compare(&'a', &'b'), false);
        assert_eq!(DayState::char_compare(&'z', &'x'), false);
    }
}
