use std::{
    fs::File,
    io::{BufReader, Read},
};

use rand::{prelude::*, rngs::OsRng};

pub struct Words {
    words: Vec<String>,
}

impl Words {
    pub fn new() -> Self {
        Self { words: Vec::new() }
    }

    pub fn from_text_file(file: File) -> std::io::Result<Self> {
        let mut reader = BufReader::new(file);
        let mut contents = String::new();
        reader.read_to_string(&mut contents)?;

        Ok(Self::from_str(&contents))
    }

    pub fn from_str(words: &str) -> Self {
        Self {
            words: words.split_whitespace().map(|s| s.to_owned()).collect(),
        }
    }

    pub fn random_words_with_min_length(
        &self,
        word_count: usize,
        min_word_length: usize,
    ) -> Vec<&str> {
        self.words
            .iter()
            .filter(|word| word.chars().count() >= min_word_length)
            .map(|word| word.as_str())
            .choose_multiple(&mut OsRng, word_count)
    }

    pub fn random_word(&self) -> &str {
        let i = OsRng.gen_range(0..self.words.len());

        self.words[i].as_str()
    }
}

pub struct PassPhraseGenerator {
    capitalized: bool,
    length: i16,
    min_word_length: Option<i16>,
}

impl PassPhraseGenerator {
    pub fn new() -> Self {
        Self {
            capitalized: false,
            length: 6,
            min_word_length: None,
        }
    }

    pub fn with_capitalized(mut self, capitalized: bool) -> Self {
        self.capitalized = capitalized;

        self
    }

    pub fn with_length(mut self, length: i16) -> Self {
        self.length = length;

        self
    }

    pub fn with_min_word_length(mut self, length: Option<i16>) -> Self {
        self.min_word_length = length;

        self
    }

    pub fn generate_n(&self, words: &Words, count: usize) -> Option<Vec<String>> {
        (0..count).map(|_| self.generate(words)).collect()
    }

    pub fn generate(&self, words: &Words) -> Option<String> {
        let random_words = words.random_words_with_min_length(
            self.length as usize,
            self.min_word_length.unwrap_or(0) as usize,
        );

        if random_words.len() == self.length as usize {
            Some(
                random_words
                    .into_iter()
                    .map(|word| {
                        if self.capitalized {
                            let mut chars = word.chars();

                            match chars.next() {
                                None => String::new(),
                                Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
                            }
                        } else {
                            word.to_string()
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(" "),
            )
        } else {
            None
        }
    }
}

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

    #[test]
    fn it_works() {
        //let words = Words::fr
        let words = Words::from_str(include_str!("../words_alpha.txt"));
        let gen = PassPhraseGenerator::new().with_length(4);

        let passphrase = gen.generate(&words).unwrap();
        let mut pass_words = passphrase.split_whitespace();
        assert!(pass_words.next().unwrap().chars().all(|c| c.is_lowercase()));
        assert!(pass_words.next().is_some());
        assert!(pass_words.next().is_some());
        assert!(pass_words.next().is_some());
        assert!(pass_words.next().is_none());
    }

    #[test]
    fn test_capitalize() {
        //let words = Words::fr
        let words = Words::from_str(include_str!("../words_alpha.txt"));
        let gen = PassPhraseGenerator::new()
            .with_length(1)
            .with_capitalized(true);

        let passphrase = gen.generate(&words).unwrap();
        let mut pass_words = passphrase.split_whitespace();
        let mut first_word = pass_words.next().unwrap().chars();
        assert!(first_word.next().unwrap().is_uppercase());
        assert!(first_word.all(|c| c.is_lowercase()));
    }
}
