extern crate clap;
extern crate serde;
extern crate serde_json;

use clap::*;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{Read, Write};

#[derive(Serialize, Deserialize, Debug)]
struct BenchData {
    benches: Vec<Bench>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Bench {
    language: String,
    compile: bool,
    compile_command: String,
    run_command: String,
}

#[derive(Parser, Debug)]
#[command(author = "Astro Orbis <astroorbis@gmail.com>")]
#[command(version = env!("CARGO_PKG_VERSION"))]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    Print,
    Run { warmup_runs: u32, runs: u32 },
    Remove { language: String },
    Add,
}

// implement something so I can turn a serde_json into a BenchData
// implement something so I can turn a BenchData into a serde_json
impl BenchData {
    fn new() -> BenchData {
        BenchData {
            benches: Vec::new(),
        }
    }
    fn write(&self) {
        let json_data = serde_json::to_string(self).expect("Failed to serialize data.");
        let mut file = File::create("./benchfile").expect("Failed to create file.");
        file.write_all(json_data.as_bytes())
            .expect("Failed to write to file.");
    }
    fn read() -> BenchData {
        let mut file = File::open("./benchfile").expect("Failed to open file.");
        let mut data = String::new();
        file.read_to_string(&mut data)
            .expect("Failed to read file.");
        let d: BenchData = serde_json::from_str(&data).expect("Failed to deserialize data.");
        d
    }
    fn remove(mut self, lang: String) {
        let mut index = 0;
        for bench in &self.benches {
            if bench.language == lang {
                self.benches.remove(index);
                self.write();
                std::process::exit(0);
            }
            index += 1;
        }
        println!(
            "Could not find bench for language `{}`. Available languages are: {}",
            lang,
            self.benches
                .iter()
                .map(|x| x.language.clone())
                .collect::<Vec<String>>()
                .join(", ")
        );
    }
}

fn main() {
    let mut data: BenchData = match std::path::Path::new("./benchfile").exists() {
        false => {

        match astrolib::easyux::easyconfirm("A benchfile could not be found in the current directory. Do you want to create a new one?", true) {
				false => {
					std::process::exit(0);
				}
				true => {
					let d = BenchData::new();
					d.write();
					d
				}
            }
        }
        true => {
			BenchData::read()
		}
    };

    let args = Cli::parse();

    match args.command {
        Commands::Print => {
            println!("{:#?}", data);
        }
        Commands::Run { warmup_runs, runs } => {
            run(data, warmup_runs, runs);
        }
        Commands::Remove { language } => data.remove(language),
        Commands::Add => {
            println!("Remember - all commands will be run from current directory.");
            let lang = astrolib::easyux::easytext("Language: ");
            let comp = astrolib::easyux::easyconfirm("Is this a compiled language?", false);
            let comp_cmd = if comp {
                astrolib::easyux::easytext("What's the command used to compile? ")
            } else {
                String::new()
            };
            let run_cmd = astrolib::easyux::easytext("Run command: ");
            data.benches.push(Bench {
                language: lang,
                compile: comp,
                compile_command: comp_cmd,
                run_command: run_cmd,
            });
            data.write();
        }
    }
}

fn run(config: BenchData, warmup_runs: u32, runs: u32) {
    // Compilation phase
    println!("Compiling...");
    for bench in &config.benches {
        if bench.compile {
            println!("Compiling {}...", bench.language);
            let mut command = std::process::Command::new(&bench.compile_command);
            command
                .spawn()
                .expect("Failed to spawn process for compilation.");
        }
    }

    // Args building

    let mut args: Vec<String> = vec![
        //String::from("--show-output"),
        String::from("--warmup"),
        warmup_runs.to_string(),
        String::from("--runs"),
        runs.to_string(),
    ];

    for bench in config.benches {
        args.push(format!("\"{}\"", bench.run_command));
    }

    let arg = args.clone().join(" ");
    println!("hyperfine {}", &arg);

    // Run!
    /*
    std::process::Command::new("hyperfine")
        .current_dir(std::env::current_dir().unwrap().as_path())
        .args(&*args)
        //.arg(arg)
        .spawn()
        .expect("Failed to spawn process for hyperfine.");*/

    std::process::Command::new("zsh")
        .arg("-c")
        .arg(format!("hyperfine {}", arg))
        .spawn()
        .expect("Failed to spawn process for Zsh & Hyperfine.");
}
