use anyhow::{bail, Result};
use clap::{Parser, Subcommand};
use std::fs;
use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::path::Path;

use hence::*;

#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
    #[clap(subcommand)]
    commands: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    #[clap(about = "Lexes source code and outputs tokens")]
    Lex {
        #[clap(value_parser)]
        src: String,
    },
    #[clap(about = "Parses source code and outputs AST")]
    Parse {
        #[clap(value_parser)]
        src: String,
    },
    #[clap(about = "Compiles binary from source code")]
    Assemble {
        #[clap(value_parser)]
        src: String,
        #[clap(value_parser)]
        bin: Option<String>,
        #[clap(long, action)]
        dump: bool,
    },
    #[clap(about = "Emulates binary")]
    Run {
        #[clap(value_parser)]
        bin: String,
    },
}

fn main() -> Result<()> {
    let args = Cli::parse();
    match args.commands {
        Commands::Lex { src } => {
            let assembly = fs::read_to_string(src)?;
            let tokens = lexer::lex(&assembly)?;
            println!("{:#?}", tokens);

            Ok(())
        }
        Commands::Parse { src } => {
            let assembly = fs::read_to_string(src)?;
            let tokens = lexer::lex(&assembly)?;
            let ast = parser::parse(tokens)?;
            println!("{:#?}", ast);

            Ok(())
        }
        Commands::Assemble { src, bin, dump } => {
            let assembly = fs::read_to_string(&src)?;
            let tokens = lexer::lex(&assembly)?;
            let ast = parser::parse(tokens)?;

            let mut data = assembler::Data::new(
                match match Path::new(&src).parent() {
                    Some(x) => x.to_str().map(|y| y.to_string()),
                    None => None,
                } {
                    Some(s) => s,
                    None => bail!("Could not get directory in which source code resides"),
                },
                ast,
            );
            data.assemble()?;

            if let Some(x) = bin {
                File::create(x)?.write_all(&data.program)?;
            }
            if dump {
                println!("{}", rhexdump::hexdump(&data.program));
            }

            Ok(())
        }
        Commands::Run { bin } => {
            let mut program_buf: Vec<u8> = vec![];
            let file = File::open(bin)?;
            BufReader::new(file).read_to_end(&mut program_buf)?;
            if program_buf.len() < (32 * 1024) {
                program_buf.append(&mut vec![0; 32 * 1024 - program_buf.len()]);
            }

            let program: [u8; 32 * 1024] = (&program_buf[0..(32 * 1024)]).try_into()?;
            emulator::Emulator::new(program).run()?;

            Ok(())
        }
    }
}

#[cfg(test)]
mod tests {}
