use std::io::{self, Write};
use std::time::Duration;

use tokio::time::sleep;

static CHARSET: [&str; 221] = [
    "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3",
    "4", "5", "6", "7", "8", "9", ",", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F",
    "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
    "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
    "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "-", "}", "~", "Ç",
    "ü", "é", "â", "ä", "à", "å", "ç", "ê", "ë", "è", "ï", "î", "ì", "Ä", "Å", "É", "æ", "Æ", "ô",
    "ö", "ò", "û", "ù", "ÿ", "Ö", "Ü", "¢", "£", "¥", "₧", "ƒ", "á", "í", "ó", "ú", "ñ", "Ñ", "ª",
    "º", "¿", "⌐", "¬", "½", "¼", "¡", "«", "»", "░", "▒", "▓", "│", "┤", "╡", "╢", "╖", "╕", "╣",
    "║", "╗", "╝", "╜", "╛", "┐", "└", "┴", "┬", "├", "─", "┼", "╞", "╟", "╚", "╔", "╩", "╦", "╠",
    "═", "╬", "╶", "╸", "╤", "╥", "╙", "╘", "╒", "╓", "╫", "╪", "┘", "┌", "█", "▄", "▌", "▐", "▀",
    "ʱ", "ß", "γ", "π", "Σ", "σ", "µ", "τ", "Φ", "Θ", "Ω", "δ", "∞", "φ", "ε", "∩", "≡", "±", "≥",
    "≤", "⌠", "⌠", "÷", "≈", "°", "∙", "·", "√", "ⁿ", "²", "■",
];

struct Cipher {
    text: String,
    line_count: usize,
    mask: Vec<bool>,
}

impl Cipher {
    async fn load(delay: u64) -> Self {
        let mut text = String::new();
        let stdin = io::stdin();
        let mut line_count = 0;

        for line in stdin.lines() {
            if let Ok(line) = line {
                text += &line;
                text += "\n";
                Self::print_line(line.as_str(), delay).await;
                line_count += 1;
            }
        }

        let mask = Self::get_mask(text.as_str());
        Cipher {
            text,
            line_count,
            mask,
        }
    }

    fn get_mask(lines: &str) -> Vec<bool> {
        let mut mask: Vec<bool> = Default::default();

        for c in lines.chars() {
            match c {
                c if c.is_whitespace() => mask.push(true),
                _ => mask.push(false),
            }
        }

        mask
    }

    async fn print_line(line: &str, delay: u64) {
        for c in line.chars() {
            match c {
                c if c.is_whitespace() => print!("{c}"),
                _ => {
                    let idx = rand::random_range(0..CHARSET.len());
                    print!("{}", CHARSET[idx]);
                }
            }
            let _ = std::io::stdout().flush();
            if delay > 0 {
                sleep(Duration::from_millis(delay)).await;
            }
        }
        println!();
    }

    fn reset(&self) {
        if self.line_count > 0 {
            print!("\x1b[{}F", self.line_count);
        }
    }

    fn display(&self, update_prob: u32) {
        for (c, m) in self.text.chars().zip(self.mask.iter()) {
            match m {
                true => print!("{c}"),
                false => {
                    if rand::random_range(0..100) < update_prob {
                        let idx = rand::random_range(0..CHARSET.len());
                        print!("{}", CHARSET[idx]);
                    } else {
                        print!("\x1b[1C");
                    }
                }
            }
        }
        let _ = std::io::stdout().flush();
    }

    fn random_unmask(&mut self, num: usize) -> bool {
        let mut idx: Vec<usize> = self
            .mask
            .iter()
            .enumerate()
            .filter_map(|(i, m)| if *m { None } else { Some(i) })
            .collect();

        for offset in 0..num {
            let selected: usize = rand::random_range(offset..idx.len());
            self.mask[idx[selected]] = true;
            idx[selected] = idx[offset];

            if offset == (idx.len() - 1) {
                return true;
            }
        }

        false
    }
}

#[tokio::main]
async fn main() {
    let mut cipher = Cipher::load(10).await;

    sleep(Duration::from_millis(1000)).await;

    for _ in 0..20 {
        cipher.reset();
        cipher.display(100);
        sleep(Duration::from_millis(50)).await;
    }

    loop {
        cipher.reset();
        cipher.display(20);
        sleep(Duration::from_millis(100)).await;

        if cipher.random_unmask(10) {
            break;
        }
    }

    cipher.reset();
    cipher.display(100);
}
