/*
64tass -a -o instructions.bin -b instructions.asm &&
cargo run --bin de6502 -- -v instructions.bin instructions_produced.asm &&
64tass -a -b instructions_produced.asm -o instructions_produced.bin
*/
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::path::PathBuf;
use std::thread::current;
use core::constants::constants_system::*;
use core::constants::constants_isa_op::*;
use clap::{Parser, Subcommand};
use core::instruction_table::INSTRUCTION_TABLE;
use std::io::Write;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct CliOptions {
    /// File to Decompile
    input: PathBuf,
    /// File to write
    output: PathBuf,
    /// Verbose output
    #[arg(short, action = clap::ArgAction::Count)]
    verbose: u8
}

#[derive(Debug)]
struct DecompiledLine {
    offset: u16,
    text: String,
    label: Option<String>,
    bytes: Vec<u8>
}

impl DecompiledLine {
    /// is_jump
    ///
    /// Check if the current line is a type of a jump
    pub fn is_jump(&self) -> bool {
        self.bytes[0] == ISA_OP_JMP_ABS || self.bytes[0] == ISA_OP_JMP_IND
    }

    /// is_branch
    ///
    /// Check if the current line is a branch
    pub fn is_branch(&self) -> bool {
        self.bytes[0] == ISA_OP_BCC || self.bytes[0] == ISA_OP_BCS ||
            self.bytes[0] == ISA_OP_BEQ || self.bytes[0] == ISA_OP_BMI ||
            self.bytes[0] == ISA_OP_BNE || self.bytes[0] == ISA_OP_BPL ||
            self.bytes[0] == ISA_OP_BVS || self.bytes[0] == ISA_OP_BVC
    }
}

/// WorkingProgram
///
/// Where we keep the program being decompiled
struct WorkingProgram {
    data: [u8; SIZE_64KB],
    lines: Vec<DecompiledLine>
}

impl WorkingProgram {

}

fn parse_to_decompiled_lines(data: &[u8]) -> HashMap<u16, DecompiledLine> {
    println!("PARSE GOT {}b", data.len());
    let mut current_data_position = 0;
    let mut lines: HashMap<u16, DecompiledLine> = HashMap::new();

    while current_data_position < data.len() as u16 {
        // read the next byte.
        let next_byte = data[current_data_position as usize];
        let mut bytes = vec![next_byte];
        println!("Bytes = {bytes:?}");
        let target_op = INSTRUCTION_TABLE[next_byte as usize].clone();

        if let Some(top) = target_op {
            // println!("Instruction {:?}/{:?} needs {:?} bytes.", top.operation, top.mode, top.length);

            let num_bytes_to_load = top.length - 1;
            // println!("Need {num_bytes_to_load} more bytes.");
            let mut formatted_asm =  String::from(top.format_prefix);
            let mut param = None;
            if num_bytes_to_load == 1 {
                bytes.push(data[current_data_position as usize + 1]);
                formatted_asm = format!("{}{:02x}{}", formatted_asm, bytes[1], top.format_postfix);
            };

            if num_bytes_to_load == 2 {
                bytes.push(data[current_data_position as usize + 1]);
                bytes.push(data[current_data_position as usize + 2]);
                // 16 bit parameter.
                param = Some(((bytes[2] as u16) << 8) | bytes[1] as u16);
                formatted_asm = format!("{}{:04x}{}", formatted_asm, param.unwrap(), top.format_postfix);
            };

            // figure out how long the instruction is and read that much more data
            lines.insert(current_data_position, DecompiledLine {
                offset: current_data_position,
                text: String::from(formatted_asm),
                label: None,
                bytes,
            });

            current_data_position = current_data_position + top.length as u16;
        } else {
            // invalid instruction
            println!("Byte sequence 0x{} invalid.", next_byte);
            current_data_position += 1;
        }
    }
    lines
}

fn decompile(data: &Vec<u8>) -> Vec<(u16, DecompiledLine)> {
    println!("Preparing to decompile {}b", data.len());
    let mut current_data_position: u16 = 0;
    let mut lines: HashMap<u16, DecompiledLine> = parse_to_decompiled_lines(data);

    println!("Found {} instructions in pass 1.  Adding labels for jumps.", lines.len());

   //let targets = vec![];

    let mut items: Vec<_> = lines.into_iter().collect();
    items.sort_by(|a, b| a.0.cmp(&b.0));

    // loop through the lines for JSR and label the 'target' as SUB<index>
    // let mut jump_targets = vec![];
    for (line_index, working_line) in &mut items {
        if working_line.is_jump() {
            println!("There are {}b in bytes", working_line.bytes.len());
            let target = read_word(&working_line.bytes[1..=2]);
            println!("Found a jump at index {line_index} / 0x{line_index} with target of 0x{:04x}", target);;
            // Derp -> this doesnt work.
            // items[target as usize].1.label = Some("Label1".parse().unwrap());
        }
    }

    // // check the vectors to label those entrypoints.
    // let (mut main, mut nmi, mut irq) = (0, 0, 0);
    // match data.len() {
    //     SIZE_32KB | SIZE_64KB => {
    //         main = read_word(&data[OFFSET_RESET_VECTORS..=OFFSET_RESET_VECTORS + 1]);
    //         nmi =  read_word(&data[OFFSET_NMI_VECTORS..=OFFSET_NMI_VECTORS + 1]);
    //         irq = read_word(&data[OFFSET_INT_VECTORS..=OFFSET_INT_VECTORS]);
    //     }
    //     _ => {
    //         // dont know where to look for our offsets.  :(
    //     }
    // }
    // -> main
    // -> NMI
    // -> IRQ

    items
}

/// read_word
///
/// Read a word from the provided data with first byte being
/// LSB
fn read_word(from: &[u8]) -> u16 {
    (from[1] as u16) << 8 | from[0] as u16
}

/// read_word_swap
///
/// Read a word from the provided data with first byte being
/// MSB
fn read_word_swap(from: &[u8]) -> u16 {
    (from[0] as u16) << 8 | from[1] as u16
}

/// format_bytes
///
/// formats data for stuff
fn format_bytes(from: &Vec<u8>) -> String {
    let mut to_return = String::from("[");

    for index in 0..from.len() {
        to_return = format!("{} 0x{:02x}", to_return, from[index]);
    }
    to_return = format!("{} ]", to_return);

    to_return
}


fn main() {
    let opts = CliOptions::parse();
    println!("Taxation is theft.");

    // Load program to an array...
    let data = vec![
        0xa9, 0xab, 0x4c, 0x00, 0x40, 0xa9, 0x54, 0x4c, 0x00, 0x40,
        ISA_OP_TYA, ISA_OP_TXA, ISA_OP_TSX
    ];

    let loaded_data = fs::read(opts.input).unwrap();
    println!("Loaded {}b", loaded_data.len());
    // ...load the array to the WorkingProgram structure...
    let result = decompile(&loaded_data);
    let mut output_file = File::create(opts.output).unwrap();

    // ...reap the decompiled code.
    for (_, line) in &result {
        let formatted_bytes = format_bytes(&line.bytes);
        let output_line =format!("{:width$}; {}", line.text, formatted_bytes, width=30 );

        // do we need a label
        if let Some(label) = &line.label {
            println!("There is a label - {label}");
        }
        if opts.verbose > 0 {
             println!("{}", output_line);
        }
        writeln!(output_file, "{}", output_line).expect("cant write output file.  you picked the red snapper.");
    }
    println!("OPTS = {}", opts.verbose);
}
