use std::fs::File;
use std::io::{self, BufRead, Write};
use std::path::Path;

fn main() {
    let mut run: bool = true;
    let mut file_name: String = String::new();
    let mut col_amount: i8 = 50;

    while run {
        print!("\x1b[2J\x1b[H");
        println!("========== Justify some text ==========");
        println!("  A) Set file name (Name: {})", file_name);
        println!(
            "  B) Set column amount between 50 and 120 (Amount: {})",
            col_amount
        );
        println!("  C) Justify and print text");
        println!("  D) Justify and save text");
        println!("  Quit (any other key)");

        let mut choice: String = String::new();
        print!("Your choice: ");
        let _ = io::stdout().flush();
        io::stdin()
            .read_line(&mut choice)
            .expect("Failed to read from stdin");
        choice = choice.trim().to_ascii_lowercase().to_string();

        match choice.as_str() {
            "a" => {
                file_name = set_file_name();
            }
            "b" => {
                col_amount = set_col_amount();
            }
            "c" => {
                print_justified_text(&file_name, col_amount);
            }
            "d" => {
                save_justified_text(&file_name, col_amount);
            }
            _ => {
                run = false;
            }
        }
    }
}

fn set_file_name() -> String {
    let mut path_iscorrect: bool = false;
    let mut path: String = String::new();

    while !path_iscorrect {
        path = String::new();
        print!("Enter the desired file's path: ");
        let _ = io::stdout().flush();
        io::stdin()
            .read_line(&mut path)
            .expect("Failed to read from stdin");
        path = path.trim().to_string();

        path_iscorrect = Path::new(&path).exists();
        if !path_iscorrect {
            println!("The specified file does not exist. Please try again.");
        }
    }

    return path;
}

fn set_col_amount() -> i8 {
    let mut input_text: String;
    let mut col_amount: i8 = 0;
    let mut amount_iscorrect: bool = false;

    while !amount_iscorrect {
        input_text = String::new();
        print!("Enter the desired column amount: ");
        let _ = io::stdout().flush();
        io::stdin()
            .read_line(&mut input_text)
            .expect("failed to read from stdin");

        match input_text.trim().parse::<i8>() {
            Ok(i) => {
                col_amount = i;
                amount_iscorrect = col_amount >= 50 && col_amount <= 120;
                if !amount_iscorrect {
                    println!("The specified amount was not between 50 and 120. Please try again.");
                }
            }
            Err(..) => println!(
                "input is not an integer: {}. Please try again.",
                input_text.trim()
            ),
        };
    }

    return col_amount;
}

fn print_justified_text(filename: &str, col_amount: i8) {
    if let Ok(lines) = read_lines(filename) {
        for line in lines.flatten() {
            let justified_text = justify_text(&line, col_amount);
            println!("{}", justified_text);
        }
    } else {
        println!("Failed to read the file.");
    }
}

fn save_justified_text(filename: &str, col_amount: i8) {
    let output_file = "output.txt";
    let mut file = File::create(output_file).expect("Failed to create output file.");

    if let Ok(lines) = read_lines(filename) {
        for line in lines.flatten() {
            let justified_text = justify_text(&line, col_amount);
            writeln!(file, "{}", justified_text).expect("Failed to write to output file.");
        }
        println!("Written to {}", output_file);
    }
}

fn justify_text(text: &str, col_amount: i8) -> String {
    if text.len() >= col_amount as usize {
        return text.to_string();
    }
    if text == "" {
        return text.to_string();
    }

    let words: Vec<&str> = text.split_whitespace().collect();

    if words.len() == 1 {
        return format!("{:<width$}", words[0], width = col_amount as usize);
    }

    let mut justified = String::new();
    let words_len: usize = words.iter().map(|word| word.len()).sum();
    let total_spaces = (col_amount as usize) - words_len;
    let gaps = words.len() - 1;
    let spaces_per_gap = total_spaces / gaps;
    let extra_spaces = total_spaces % gaps;

    for (i, word) in words.iter().enumerate() {
        justified.push_str(word);
        if i < gaps {
            justified.push_str(&" ".repeat(spaces_per_gap));
            if i < extra_spaces {
                justified.push(' ');
            }
        }
    }

    return justified;
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}
