typeman 0.1.0

Typing speed test with practice mode in GUI, TUI and CLI
use core::time;
use std::collections::VecDeque;
use macroquad::prelude::*;
use std::time::{Instant, Duration};

use crate::ui::gui::main;
use crate::{practice, utils};


pub fn draw_rounded_rect(x: f32, y: f32, w: f32, h: f32, radius: f32, color: Color) {
    draw_rectangle(x + radius, y, w - 2.0 * radius, h, color);
    draw_rectangle(x, y + radius, w, h - 2.0 * radius, color);

    draw_circle(x + radius, y + radius, radius, color);
    draw_circle(x + w - radius, y + radius, radius, color);
    draw_circle(x + radius, y + h - radius, radius, color);
    draw_circle(x + w - radius, y + h - radius, radius, color);
}

fn draw_toggle_button(
    x: f32,
    y: f32,
    btn_padding: f32,
    label: &str,
    font: &Option<Font>,
    is_active: bool,
    inactive_color: Color,
    visible: bool,
    font_size: u16,
    selected: bool,
) -> (bool, bool, f32) {
    if !visible {
        return (false, false, 0.0);
    }
    let padding = font_size as f32 * 0.5;
        
    let text_dims = measure_text(&label, Some(font.as_ref().unwrap()), font_size, 1.0);
    let btn_width = text_dims.width + btn_padding * 2.0;
    let btn_height = measure_text("t", font.as_ref(), font_size, 1.0).height + padding as f32 * 2.0;

    let rect = Rect::new(x, y, btn_width, btn_height);
    let (mx, my) = mouse_position();
    let hovered = rect.contains(vec2(mx, my));
    let clicked = hovered && is_mouse_button_pressed(MouseButton::Left);

    let mut text_color = if is_active { main::MAIN_COLOR } else { inactive_color };
    let mut bg_color = Color::from_rgba(255, 0, 0, 0);
    if selected && is_active {
        text_color = macroquad::color::BLACK;
        bg_color = Color::from_rgba(150, 90, 0, 255);
    } else if selected {
        text_color = macroquad::color::BLACK;
        bg_color = Color::from_rgba(100, 60, 0, 255);
    }

    let font_size: u16 = if label == "|" {
        (font_size as f32 * 1.5) as u16
    } else {
        font_size
    };
    
    let corner_radius: f32 = font_size as f32 / 3.0;
    let btn_x = x;
    draw_rounded_rect(btn_x, y, btn_width, btn_height, corner_radius, bg_color);
    draw_text_ex(
        &label,
        x + btn_padding,
        y + btn_height - padding as f32,
        TextParams {
            font: font.as_ref(),
            font_size,
            font_scale: 1.0,
            color: text_color,
            ..Default::default()
        },
    );

    (clicked, hovered, btn_width + btn_padding * 2.0)
}

pub fn update_game_state(
    reference: &str,
    pressed_vec: &mut Vec<char>,
    is_correct: &mut VecDeque<i32>,
    pos1: &mut usize,
    timer: &mut Duration,
    start_time: &mut Instant,
    game_started: &mut bool,
    game_over: &mut bool,
    test_time: f32,
    time_mode: bool,
    words_done: &mut usize,
    errors_this_second: &mut f64,
    practice_mode: &mut bool,
    practice_menu: bool,
) {
    if !*game_started && main::handle_input(reference, pressed_vec, is_correct, pos1, words_done, errors_this_second, &mut false, &mut vec![false; reference.chars().count()], *practice_mode, practice_menu) {
        *game_started = true;
        *start_time = Instant::now();
    }
    
    if *game_started && !*game_over {
        *timer = start_time.elapsed();
        if (timer.as_secs_f32() >= test_time && time_mode) || *pos1 >= reference.chars().count() {
            *game_over = true;
        }
    }
}

pub fn reset_game_state(
    pressed_vec: &mut Vec<char>,
    is_correct: &mut VecDeque<i32>,
    pos1: &mut usize,
    timer: &mut Duration,
    start_time: &mut Instant,
    game_started: &mut bool,
    game_over: &mut bool,
    speed_per_second: &mut Vec<f64>,
    last_recorded_time: &mut Instant,
    words_done: &mut usize,
    errors_per_second: &mut Vec<f64>,
    saved_results: &mut bool,
    error_positions: &mut Vec<bool>
) {
    *is_correct = VecDeque::from(vec![0; is_correct.len()]);
    pressed_vec.clear();
    *error_positions = vec![false; is_correct.len()];
    *pos1 = 0;
    *timer = Duration::new(0, 0);
    *start_time = Instant::now();
    *game_started = false;
    *game_over = false;
    *speed_per_second = vec![];
    *errors_per_second = vec![];
    *last_recorded_time = Instant::now();
    *words_done = 0;
    *saved_results = false;
}

pub fn handle_settings_buttons(
    font: &Option<Font>,
    word_list: &[String],
    punctuation: &mut bool,
    numbers: &mut bool,
    quote: &mut bool,
    time_mode: &mut bool,
    word_mode: &mut bool,
    pressed_vec: &mut Vec<char>,
    is_correct: &mut VecDeque<i32>,
    pos1: &mut usize,
    timer: &mut time::Duration,
    start_time: &mut Instant,
    game_started: &mut bool,
    game_over: &mut bool,
    reference: &mut String,
    test_time: &mut f32,
    batch_size: &mut usize,
    start_x: f32,
    speed_per_second: &mut Vec<f64>,
    last_recorded_time: &mut Instant,
    words_done: &mut usize,
    errors_per_second: &mut Vec<f64>,
    font_size: u16,
    config_opened: &mut bool,
    selected_config: &mut String,
    practice_menu: &mut bool,
    selected_practice_level: &mut Option<usize>,
    practice_mode: &mut bool,
    saved_results: &mut bool,
    error_positions: &mut Vec<bool>,
) -> bool {
    let inactive_color = Color::from_rgba(255, 255, 255, 80);
    let btn_y = screen_height() / 5.0;
    let btn_padding = if screen_width() > 800.0 {
        font_size as f32 * 0.5
    } else {
        font_size as f32 * 0.25
    };
    let divider = true;
    let mut total_width = 0.0;

    let mut button_states = vec![
        ("! punctuation", *punctuation, !*quote && !*practice_mode),
        ("# numbers", *numbers, !*quote && !*practice_mode),
        ("|", divider, true),
        ("time", *time_mode, true),
        ("words", *word_mode, true),
        ("quote", *quote, true),
        ("practice", *practice_mode, true),
        ("|", divider, true),
        ("15", test_time == &15.0, *time_mode),
        ("30", test_time == &30.0, *time_mode),
        ("60", test_time == &60.0, *time_mode),
        ("120", test_time == &120.0, *time_mode),
        ("25", *batch_size == 25, *word_mode),
        ("50", *batch_size == 50, *word_mode),
        ("100", *batch_size == 100, *word_mode),
    ];

    if is_key_down(KeyCode::Up) {
        *config_opened = true;
    } else if is_key_down(KeyCode::Down) {
        *config_opened = false;
    } else if is_key_pressed(KeyCode::Left) {
        if !*config_opened {
            return false;
        }
        for (i, (label, _state_val, visible)) in button_states.iter().enumerate() {
            if *visible && *selected_config == *label {
            let mut j = if i == 0 {
                button_states.len() - 1
            } else {
                i - 1
            };

            while j != i {
                if button_states[j].2 && button_states[j].0 != "|" {
                    *selected_config = button_states[j].0.to_string();
                    break;
                }
                j = if j == 0 {
                    button_states.len() - 1
                } else {
                    j - 1
                };
            }
            break;
            }
        }
        } else if is_key_pressed(KeyCode::Right) {
        if !*config_opened {
            return false;
        }
        for (i, (label, _state_val, visible)) in button_states.iter().enumerate() {
            if *visible && *selected_config == *label {
            let mut next = if i == button_states.len() - 1 {
                0
            } else {
                i + 1
            };

            while next != i {
                if button_states[next].2 && button_states[next].0 != "|" {
                *selected_config = button_states[next].0.to_string();
                break;
                }
                next = if next == button_states.len() - 1 {
                0
                } else {
                next + 1
                };
            }
            break;
            }
        }
    } else if is_key_pressed(KeyCode::Enter) && *config_opened {
        update_config(&selected_config, punctuation, numbers, time_mode, word_mode, quote, test_time, batch_size, practice_menu, selected_practice_level, practice_mode);
        if *quote {
            *reference = utils::get_random_quote();
        } else if *practice_mode {
            *reference = practice::create_words(
                &practice::TYPING_LEVELS[selected_practice_level.unwrap_or(0)].1,
                *batch_size,
            );
        } else {
            *reference = utils::get_reference(*punctuation, *numbers, word_list, *batch_size);
        }
        *is_correct = VecDeque::from(vec![0; reference.len()]);
        *error_positions = vec![false; is_correct.len()];
        reset_game_state(pressed_vec, is_correct, pos1, timer, start_time, game_started, game_over, speed_per_second, last_recorded_time, words_done, errors_per_second, saved_results, &mut vec![false; reference.chars().count()]);
    }

    let mut any_button_hovered = false;

    for (label, state_val, visible) in button_states.iter_mut() {
        let x = start_x + total_width;
        let is_active = *state_val;
        
        let (clicked, hovered, btni_width) = draw_toggle_button(
            x, 
            btn_y,
            btn_padding,
            label,
            font, 
            is_active, 
            inactive_color,
            *visible,
            font_size,
            selected_config == label && *config_opened,
        );
        total_width += btni_width;
        
        if hovered && *label != "|" {
            any_button_hovered = true;
        }
        
        if clicked && *label != "|" {
            
            update_config(label, punctuation, numbers, time_mode, word_mode, quote, test_time, batch_size, practice_menu, selected_practice_level, practice_mode);
            if *quote {
                *reference = utils::get_random_quote();
                *is_correct = VecDeque::from(vec![0; reference.chars().count()]);
                *error_positions = vec![false; is_correct.len()];
                *punctuation = false;
                *numbers = false;
                reset_game_state(pressed_vec, is_correct, pos1, timer, start_time, game_started, game_over, speed_per_second, last_recorded_time, words_done, errors_per_second, saved_results, error_positions);
            } else if *practice_menu {
                *practice_menu = true;
            } else {
                *reference = utils::get_reference(*punctuation, *numbers, word_list, *batch_size);
                *is_correct = VecDeque::from(vec![0; reference.chars().count()]);
                *error_positions = vec![false; is_correct.len()];
                reset_game_state(pressed_vec, is_correct, pos1, timer, start_time, game_started, game_over, speed_per_second, last_recorded_time, words_done, errors_per_second, saved_results, error_positions);
            }
        }
    }

    any_button_hovered
}

fn update_config(label: &str, punctuation: &mut bool, numbers: &mut bool, time_mode: &mut bool, word_mode: &mut bool, quote: &mut bool, test_time: &mut f32, batch_size: &mut usize, practice_menu: &mut bool, selected_practice_level: &mut Option<usize>, practice_mode: &mut bool) {
    match label {
        "! punctuation" => {
            *punctuation = !*punctuation;
            *quote = false;
        },
        "# numbers" => {
            *numbers = !*numbers;
            *quote = false;
        },
        "time" => {
            *time_mode = true;
            *word_mode = false;
            *quote = false;
            *practice_mode = false;
        },
        "words" => {
            *word_mode = true;
            *time_mode = false;
            *quote = false;
            *practice_mode = false;
        },
        "quote" => {
            *quote = true;
            *punctuation = false;
            *numbers = false;
            *time_mode = false;
            *word_mode = false;
            *practice_mode = false;
        },
        "practice" => {
            *quote = false;
            *punctuation = false;
            *numbers = false;
            *time_mode = false;
            *word_mode = false;
            *practice_menu = true;
            *selected_practice_level = Some(practice::get_first_not_done());
        },
        "15" => {
            *test_time = 15.0;
        },
        "30" => {
            *test_time = 30.0;
        },
        "60" => {
            *test_time = 60.0;
        },
        "120" => {
            *test_time = 120.0;
        },
        "25" => {
            *batch_size = 25;
        },
        "50" => {
            *batch_size = 50;
        },
        "100" => {
            *batch_size = 100;
        },
        _ => {}
    }
}