//! This binary generates a Rust source file containing a match switch for querying whether a given
//! instruction and step performs a memory write on the 6502 CPU.
//!
//! When built with the `gen_write_cycle_query` feature, it iterates over all instructions and their
//! execution steps, simulates execution, and records whether a memory write occurs at each step.
//! The generated match arms are written to `src/cpu/write_cycle_query.rs` and formatted with `rustfmt`.
//!
//! This tool is intended for use in the build process or for updating the memory write cycle logic.

#[cfg(feature = "gen_write_cycle_query")]
mod inner {
    use cpu6502::instr::ClockEndStatus;
    use cpu6502::instr::{Instruction, InstructionSequenceMode};
    use cpu6502::MemorySpace;
    use strum::IntoEnumIterator;

    const COMMENT_HEADER: &str = "// THIS FILE HAS BEEN GENERATED BY gen_write_cycle_query";

    /// Helper struct to detect if a memory write occurs during instruction execution.
    #[derive(Default)]
    struct MemoryWriteDetector {
        write_access: bool,
    }

    impl MemorySpace for MemoryWriteDetector {
        /// Always returns 0 for reads.
        fn read(&mut self, _addr: u16) -> u8 {
            0
        }
        /// Sets `write_access` to true if a write occurs.
        fn write(&mut self, _data: u8, _addr: u16) {
            self.write_access = true;
        }
    }

    /// Generates the write cycle query switch file for all instructions and steps.
    ///
    /// This function simulates each instruction at each step, records if a memory write occurs,
    /// and writes the result as match arms to `src/cpu/write_cycle_query.rs`.
    pub fn main() {
        use std::fs;
        use std::io::Write;

        let mut switch_options = String::new();

        // Iterate over all instructions
        for instr in Instruction::iter() {
            let mut step = 0;
            loop {
                let mut memory = MemoryWriteDetector::default();
                let mut regs = cpu6502::RegisterFile::default();

                // Simulate execution of the instruction at the current step
                let result = cpu6502::execute(instr, step, &mut regs, &mut memory);

                let instr: InstructionSequenceMode = instr.into();
                // Record if a memory write occurred at this step
                switch_options.push_str(&format!(
                    "({instr:?}, {step}) => {},\n",
                    memory.write_access
                ));

                step += 1;

                // Stop if the instruction has finished executing
                if result != ClockEndStatus::Continue {
                    break;
                }
            }
        }

        // Ensure the output directory exists
        fs::create_dir_all("src/cpu").expect("Could not create directory");

        // Write the generated match arms to the output file
        let mut code_file =
            fs::File::create("src/cpu/write_cycle_query.rs").expect("Could not create file");
        write!(
            code_file,
            include_str!("write_cycle_query.rs.txt"),
            COMMENT_HEADER, switch_options
        )
        .expect("Could not write file");

        // Format the generated file using rustfmt
        std::process::Command::new("rustfmt")
            .arg("src/cpu/write_cycle_query.rs")
            .output()
            .expect("Failed to execute rustfmt");
    }
}

#[cfg(feature = "gen_write_cycle_query")]
pub use inner::main;

#[cfg(not(feature = "gen_write_cycle_query"))]
fn main() {}
