mod cpu_reset_ram;
mod instructions;
mod ppu;
mod interrupts;

macro_rules! rom_test {
    ($name:ident, $rom:literal, |$nes:ident| $eval:expr) => {
         rom_test!($name, $rom, timeout = 10000000, |$nes| $eval);
    };
    ($name:ident, $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
        #[test]
        fn $name() {
            let rom_file = include_bytes!(concat!(env!("ROM_DIR"), "/", $rom));
            println!("{}: {}", stringify!($name), concat!(env!("ROM_DIR"), "/", $rom));
            let mut $nes = crate::NES::load_nes_file_mem(rom_file).expect("Failed to create nes object");
            $nes.reset_and_run_with_timeout($timeout);
            println!("Final: {:?}", $nes);
            $eval
        }
    };
    // ($name:ident, . $rom:literal, |$nes:ident| $eval:expr) => {
    //     rom_test!($name, . $rom, timeout = 10000000, |$nes| $eval);
    // };
    // ($name:ident, . $rom:literal, timeout = $timeout:expr, |$nes:ident| $eval:expr) => {
    //     #[test]
    //     fn $name() {
    //         let rom_file = $rom;
    //         println!("{}: {}", stringify!($name), rom_file);
    //         let mut $nes = crate::NES::load_nes_file(rom_file).expect("Failed to create nes object");
    //         $nes.reset_and_run_with_timeout($timeout);
    //         println!("Final: {:?}", $nes);
    //         $eval
    //     }
    // };
}

pub(crate) use rom_test;

use crate::{Break, NES};

rom_test!(basic_cpu, "basic-cpu.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8001 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 10);
    assert_eq!(nes.cpu.pc, 0x8001);
    assert_eq!(nes.ppu.pixel, 34);

    assert_eq!(nes.cpu.sp, 0xFD);

    nes.repl_nop();
    nes.run_with_timeout(200);
    assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 12);
    assert_eq!(nes.cpu.pc, 0x8002);
    assert_eq!(nes.ppu.pixel, 40);

    assert_eq!(nes.cpu.sp, 0xFD);
});

rom_test!(basic_cpu_with_nop, "basic-cpu-nop.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8002 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 12);
    assert_eq!(nes.cpu.pc, 0x8002);
    assert_eq!(nes.ppu.pixel, 40);

    assert_eq!(nes.cpu.sp, 0xFD);
});

rom_test!(read_write, "read_write.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x800C HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 25);
    assert_eq!(nes.cpu.pc, 0x800C);
    assert_eq!(nes.cpu.sp, 0xFD);

    assert_eq!(nes.cpu.a, 0xAA);
    assert_eq!(nes.cpu.x, 0xAA);
    assert_eq!(nes.cpu.y, 0xAA);
    assert_eq!(nes.mem().peek_cpu(0x0000).unwrap(), 0xAA);
    assert_eq!(nes.mem().peek_cpu(0x0001).unwrap(), 0xAA);
    assert_eq!(nes.mem().peek_cpu(0x0002).unwrap(), 0xAA);
});

rom_test!(basic_init_0, "basic_init_0.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8017 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 40);
    assert_eq!(nes.cpu.pc, 0x8017);
    assert_eq!(nes.cpu.sp, 0xFF);

    assert_eq!(nes.cpu.a, 0x00);
    assert_eq!(nes.cpu.x, 0x00);
    assert_eq!(nes.cpu.y, 0x00);
});

rom_test!(basic_init_1, "basic_init_1.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x801C HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 27402);
    assert_eq!(nes.cpu.pc, 0x801C);
    assert_eq!(nes.ppu.pixel, 29);

    assert_eq!(nes.cpu.sp, 0xFF);
    assert_eq!(nes.cpu.a, 0x00);
    assert_eq!(nes.cpu.x, 0x00);
    assert_eq!(nes.cpu.y, 0x00);
});

rom_test!(basic_init_2, "basic_init_2.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 57179);
    assert_eq!(nes.cpu.pc, 0x8021);
    assert_eq!(nes.ppu.pixel, 18);

    assert_eq!(nes.cpu.sp, 0xFF);
    assert_eq!(nes.cpu.a, 0x00);
    assert_eq!(nes.cpu.x, 0x00);
    assert_eq!(nes.cpu.y, 0x00);
});

rom_test!(basic_init_3, "basic_init_3.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 86963);
    assert_eq!(nes.cpu.pc, 0x8026);
    assert_eq!(nes.ppu.pixel, 28);

    assert_eq!(nes.cpu.sp, 0xFF);
    assert_eq!(nes.cpu.a, 0x00);
    assert_eq!(nes.cpu.x, 0x00);
    assert_eq!(nes.cpu.y, 0x00);
});

rom_test!(even_odd, "even_odd.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []");
    assert_eq!(nes.cpu_cycle(), 57181);
    assert_eq!(nes.cpu.pc, 0x8023);
    assert_eq!(nes.ppu.pixel, 24);

    assert_eq!(nes.cpu.sp, 0xFF);
    assert_eq!(nes.cpu.a, 0x00);
    assert_eq!(nes.cpu.x, 0x08);
    assert_eq!(nes.cpu.y, 0x00);
});

fn run_frame(nes: &mut NES) {
    nes.run_one_clock_cycle(&Break::default());
    while nes.ppu().scanline != 241 || nes.ppu().pixel != 0 {
        nes.run_one_clock_cycle(&Break::default());
    }
}

rom_test!(input_test, "input_test.nes", timeout = 86964*3, |nes| {
    const A: u16 = 0x23DC; // 0 || 0b01000000
    const B: u16 = 0x23DD; // 0 || 0b01000000
    const SELECT: u16 = 0x23DA; // 0 || 0b01000000
    const START: u16 = 0x23DB; // 0 || 0b01000000
    const DOWN: u16 = 0x23E1; // 0 || 1
    const LEFT: u16 = 0x23D8; // 0 || 0b01000000
    const UP_RIGHT: u16 = 0x23D9; // 0 || 0b01000000 | 1
    run_frame(&mut nes);
    assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
    nes.controller_1().set_a(true);
    run_frame(&mut nes);
    assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
    nes.controller_1().set_b(true);
    run_frame(&mut nes);
    assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
    nes.controller_1().0 = 0xFF;
    run_frame(&mut nes);
    assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0x01);
    assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0x40);
    assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0x41);
    nes.controller_1().0 = 0;
    run_frame(&mut nes);
    assert_eq!(nes.mapped.peek_ppu(A).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(B).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(SELECT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(START).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(DOWN).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(LEFT).unwrap(), 0);
    assert_eq!(nes.mapped.peek_ppu(UP_RIGHT).unwrap(), 0);
});

// rom_test!(even_odd, "even_odd.nes", |nes| {
//     assert_eq!(nes.last_instruction(), "0x8023 HLT :2 []");
//     assert_eq!(nes.cpu_cycle(), 57182);
//     assert_eq!(nes.cpu.pc, 0x8024);
//     assert_eq!(nes.ppu.pixel, 25);

//     assert_eq!(nes.cpu.sp, 0xFF);
//     assert_eq!(nes.cpu.a, 0x00);
//     assert_eq!(nes.cpu.x, 0x08);
//     assert_eq!(nes.cpu.y, 0x00);
// });
