typeman 1.0.0

Typing speed test with practice mode in GUI, TUI and CLI
Documentation
use std::path::PathBuf;
use std::fs;
use std::time::Instant;
use std::collections::VecDeque;

use crate::ui::cli;
use crate::Cli;
use crate::utils;
use crate::practice;
use crate::language::Language;

fn get_language_from_args(args: &Cli) -> Language {
    args.language
        .as_ref()
        .and_then(|lang_str| Language::from_str(lang_str))
        .unwrap_or_default()
}

pub fn wiki_mode() {
    println!("Starting Wikipedia mode test");
    
    let reference = utils::get_wiki_summary();
    let mut start_time: Option<Instant> = None;
    let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);

    cli::main::type_loop(&reference, None, &mut start_time, None, &mut is_correct, "wiki");
}

pub fn word_mode(args: &Cli) {
    println!("Starting common words test with specified word number");

    let punctuation = args.punctuation;
    let digits = args.digits;
    
    let top_words = args.top_words.unwrap_or(500);
    if !(1..=1000).contains(&top_words) {
        eprintln!("Top words must be between 1 and 1000.");
        return;
    }
    let word_number = match args.word_number {
        Some(Some(n)) => n,
        Some(None) => 50,
        None => 50,
    };
    if !(1..=1000).contains(&word_number) {
        eprintln!("Word number must be between 1 and 1000.");
        return;
    }

    let language = get_language_from_args(args);
    let word_list = utils::read_first_n_words(top_words, language);

    let reference = utils::get_reference(punctuation, digits, &word_list, word_number);
    let mut start_time: Option<Instant> = None;
    let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);

    cli::main::type_loop(&reference, None, &mut start_time, None, &mut is_correct, "word");
}

pub fn time_mode(args: &Cli) {
    println!("Starting random words test with time limit");
    let time_limit = args.time_limit.unwrap_or(Some(30)).unwrap_or(30);

    if time_limit == 0 {
        eprintln!("Time limit must be at least 1 second.");
        return;
    }
    if time_limit > 300 {
        eprintln!("The maximum time limit is 300 seconds.");
        return;
    }

    let top_words = args.top_words.unwrap_or(500);
    println!("Starting common words test with {} second time limit", time_limit);

    let language = get_language_from_args(args);
    let word_list = utils::read_first_n_words(top_words, language);

    let batch_size = 20;
    let mut start_time: Option<Instant> = None;

    let punctuation = args.punctuation;
    let digits = args.digits;

    'outer: while start_time.is_none() || start_time.unwrap().elapsed().as_secs() < time_limit {
        let reference = utils::get_reference(punctuation, digits, &word_list, batch_size) + " ";
        let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);
        
        if let Some(start_time_val) = start_time {
            let elapsed = start_time_val.elapsed().as_secs();
            let remaining_time = if time_limit > elapsed {
                Some(time_limit - elapsed)
            } else {
                Some(0)
            };
    
            if remaining_time <= Some(0) {
                break;
            }
        }

        let res = cli::main::type_loop(&reference, Some(time_limit), &mut start_time, None, &mut is_correct, "time");
        if res != 0 {
            println!("Test interrupted by user.");
            break;
        }

        if start_time.unwrap().elapsed().as_secs() >= time_limit {
            break 'outer;
        }
    }
}

pub fn custom_text(path: &PathBuf) {
    println!("Starting custom text test with file: {:?}", path);
    utils::validate_custom_file(path).unwrap_or_else(|err| {
        eprintln!("{}", err);
        std::process::exit(1);
    });
    let reference = match fs::read_to_string(path) {
        Ok(content) => content.replace('\n', " "),
        Err(_) => {
            eprintln!("Error reading file");
            return;
        }
    };
    let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);
    let mut start_time: Option<Instant> = None;
    cli::main::type_loop(reference.as_str(), None, &mut start_time, None, &mut is_correct, "custom");
}

pub fn quotes() {
    println!("Starting random quote test");
    let reference = utils::get_random_quote();
    let mut start_time: Option<Instant> = None;
    let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);

    cli::main::type_loop(&reference, None, &mut start_time, None, &mut is_correct, "quote");
}

pub fn practice(args: &Cli) {
    let level = args.level.unwrap();
    if level.is_none() || level.unwrap() < 1 || level.unwrap() > practice::TYPING_LEVELS.len() {
        eprintln!("Please choose a level between 1 and {}.", practice::TYPING_LEVELS.len());
        for i in 0..practice::TYPING_LEVELS.len() {
            let results_path = format!("practice_results/level_{}.txt", i + 1);
            if practice::check_if_completed(results_path.as_str()) {
                println!("✔ Level {}: {}", i + 1, practice::TYPING_LEVELS[i].0);
            } else {
                println!("  Level {}: {}", i + 1, practice::TYPING_LEVELS[i].0);
            }
        }
        return;
    }
    
    let curr_level= level.unwrap() - 1;
    let chars = practice::TYPING_LEVELS[curr_level].1;
    
    let reference = practice::create_words(chars, args.word_number.unwrap_or(Some(50)).unwrap_or(50));
    let mut is_correct: VecDeque<i32> = VecDeque::from(vec![0; reference.len()]);
    let mut start_time: Option<Instant> = None;
    let res = cli::main::type_loop(&reference, None, &mut start_time, Some(curr_level), &mut is_correct, "practice");
    if res == 1 {
        println!("Exiting practice mode.");
    }
}