//! Integration test for the 6502 CPU using a NES ROM and instruction trace comparison.
//!
//! This test runs the CPU with a test ROM, logs the executed instructions, and compares
//! the output against a reference log to verify correctness.

#[cfg(feature = "logging")]
mod test_computer;

#[cfg(feature = "logging")]
mod inner {
    use crate::test_computer::TestComputer;
    use std::io::BufRead;
    use std::path::Path;
    use std::{fs, io};

    extern crate disasm6502;
    extern crate simplelog;

    /// Path to the reference instruction trace log.
    const REFERENCE_FILE: &'static str = "testdata/reference.6502log";
    /// Path to the output log generated by this test.
    const OUTPUT_FILE: &'static str = "testdata/output.6502log";

    /// Runs the CPU for a specified number of cycles, logging each instruction.
    ///
    /// # Arguments
    ///
    /// * `computer` - The test computer containing the CPU and memory.
    /// * `num_cycles` - The number of cycles to execute.
    fn run(computer: &mut TestComputer, num_cycles: u64) {
        // Reset the CPU before starting execution.
        computer.cpu.reset();

        // Run the CPU until the desired number of cycles is reached.
        while computer.cpu.cycle_count_since_reset() < num_cycles {
            // Execute one instruction cycle.
            computer.cpu.run(&mut computer.memory);
        }
    }

    /// Compares the generated output log with the reference log line by line.
    ///
    /// Panics if the logs differ in length or content.
    fn check_results() {
        // Check that both files have the same length.
        let reference_file_len = fs::metadata(REFERENCE_FILE)
            .expect("could not read metadata of {REFERENCE_FILE}")
            .len();
        let output_file_len = fs::metadata(OUTPUT_FILE)
            .expect("could not read metadata of {OUTPUT_FILE}")
            .len();

        assert_eq!(output_file_len, reference_file_len);

        // Open both files for line-by-line comparison.
        let reference_file =
            fs::File::open(REFERENCE_FILE).expect("could not open {REFERENCE_FILE}");
        let output_file = fs::File::open(OUTPUT_FILE).expect("could not open {OUTPUT_FILE}");

        let reference_lines = io::BufReader::new(reference_file).lines();
        let output_lines = io::BufReader::new(output_file).lines();

        // Compare each line from both files.
        for (reference, output) in reference_lines.zip(output_lines) {
            assert_eq!(
                reference.expect("cannot unwrap reference trace line"),
                output.expect("cannot unwrap output trace line")
            );
        }
    }

    /// Runs the NES ROM integration test, logging instructions and comparing with the reference.
    #[test]
    fn nes_rom_test() {
        {
            // Initialize the test computer and load the NES ROM.
            let mut computer = TestComputer::new();
            computer
                .cpu
                .init_logging_debug(Path::new("testdata/output.6502log"));
            computer.load_rom();

            // Run the CPU for 14575 cycles and log instructions.
            run(&mut computer, 14575);
            // Dropping the computer here ensures the log file is flushed and closed.
        }

        // Compare the output log with the reference log.
        check_results();
    }
}
