use amethyst::advanced_parser::parse_program;
use amethyst::config::parse_config;
use amethyst::turing::TuringMachine;
use colored::*;
use std::collections::HashSet;
use std::env;
use std::fs;

const VERSION: &str = "1.2.1";

fn display_help() {
    println!("geode {}", VERSION);
    println!(
        "{} {} <COMMAND>",
        "Usage:".bright_blue(),
        "geode".bright_green()
    );
    println!();
    println!("{}", "Commands:".bright_blue());
    println!(
        "   {}, {}    Run the turing machine",
        "run".bright_green(),
        "r".bright_green()
    );
    println!(
        "   {}, {}  Analyze the file for syntax errors",
        "check".bright_green(),
        "c".bright_green()
    );
    println!(
        "   {}, {}   Information about the geode command",
        "help".bright_green(),
        "h".bright_green()
    );
}

fn display_run_help() {
    println!("No input file.");
    println!(
        "{} {}",
        "Usage:".bright_blue(),
        "geode run INPUT [OPTIONS]".bright_green()
    );
    println!();
    println!("{}", "Options:".bright_blue());
    println!("      Flag                          Argument         Description           Default");
    println!(
        "   {}                     {}   Initial tape content       @",
        "-input|-i".bright_green(),
        "tape_symbols".bright_blue()
    );
    println!(
        "   {}                                   Show output                false",
        "-output|-o".bright_green(),
    );
    println!(
        "   {}                                     Show tape                  false",
        "-tape|-t".bright_green(),
    );
    println!(
        "   {}                     {}   Maximum tape memory        none",
        "-bound|-b".bright_green(),
        "cells_number".bright_blue()
    );
    println!(
        "   {}   {}   Max turing machine steps   1200",
        "-iterations|-iter|-limit|-l".bright_green(),
        "steps_number".bright_blue()
    );
    println!(
        "   {}                                    Show current state         false",
        "-debug|-d".bright_green(),
    );
    println!(
        "   {}                    {}     External flags file    ",
        "-config|-c".bright_green(),
        "config.txt".bright_blue()
    );
}

fn turing_command(args: Vec<String>, run: bool) {
    let code_file = args[1].to_owned();
    let code = match fs::read_to_string(code_file.clone()) {
        Ok(code) => code,
        Err(_) => {
            println!("{} {}", "Cannot find".red(), code_file);
            return;
        }
    };

    let config = parse_config(args[2..].to_vec());

    if config.debug {
        println!("Compiling {} with the configuration {}", code_file, config);
    }

    let syntax_result = parse_program(code.as_str());
    match syntax_result {
        Err((leftover, msg)) => {
            let spaces = " ".repeat((leftover.row + 1).to_string().len() + 1);
            println!("{}", msg.red().bold());
            println!(
                "{}{} {}:{}:{}",
                spaces,
                "-->".bright_blue(),
                code_file,
                leftover.row + 1,
                leftover.col
            );
            println!("{}{}", spaces, "|".bright_blue());
            println!(
                "{} {} {}",
                (leftover.row + 1).to_string().bright_blue(),
                "|".bright_blue(),
                code.lines().nth(leftover.row as usize).unwrap()
            );
            println!(
                "{}{} {}{}",
                spaces,
                "|".bright_blue(),
                " ".repeat(leftover.col as usize),
                "^".red().bold()
            );
            println!("{}{}", spaces, "|".bright_blue());
        }
        Ok(syntax) => {
            // println!("{:?}", syntax);
            let mut turing_machine = TuringMachine::default();
            match turing_machine.make(&syntax, &config.start, &mut HashSet::new()) {
                Err(err) => println!("{}", err.red().bold()),
                Ok(()) => {
                    if run {
                        if config.debug {
                            config.display_run();
                        }
                        let result = turing_machine.start(&config);
                        println!("{}", result)
                    } else {
                        println!("{}", "Ok!".bright_blue().bold())
                    }
                }
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() <= 1 {
        display_help();
        return;
    }
    let command = args[1].to_owned();
    match command.as_str() {
        "--version" | "--v" | "version" | "v" => {
            println!("geode {}", VERSION);
        }
        "run" | "r" => {
            if args.len() <= 2 {
                display_run_help();
                return;
            }
            turing_command(args[1..].to_vec(), true);
        }
        "check" | "c" => {
            if args.len() <= 2 {
                println!("No input file.");
                println!(
                    "{} {}",
                    "Usage:".bright_blue(),
                    "geode check INPUT [-state|-s  machine_name]".bright_green()
                );
            }
            turing_command(args[1..].to_vec(), false);
        }
        "help" | "h" => display_help(),
        _ => {
            println!("Unrecognized subcommand {}", command.red());
            println!(
                "{} {} <COMMAND>",
                "Usage:".bright_blue(),
                "geode".bright_green()
            );
            println!();
            println!("For more information, try {}", "geode help".bright_green());
        }
    }
}
