use std::fmt;

use crate::NES;

use super::rom_test;

enum HexDump<'a> {
    NES(&'a NES, u16, usize),
    Lit(&'a [u8]),
}

impl HexDump<'_> {
    fn get(&self, off: usize) -> Option<u8> {
        match self {
            Self::NES(_, _, len) if off >= *len => None,
            Self::NES(nes, addr, _) => nes.mem().peek_cpu(addr + off as u16),
            Self::Lit(items) => items.get(off).copied(),
        }
    }
}

impl fmt::Display for HexDump<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut i = 0;
        while let Some(val) = self.get(i) {
            if i % 16 == 0 {
                write!(f, "\n{i:04X}:")?;
            }
            write!(f, " {val:02X}")?;
            i += 1;
        }
        Ok(())
    }
}

fn mem_cmp(nes: &NES, addr: u16, vals: &[u8]) {
    for (i, v) in vals.iter().enumerate() {
        if nes.mem().peek_cpu(addr + i as u16) != Some(*v) {
            panic!(
                "memcmp assertion failed:\nNES:{}\nTest:{}",
                HexDump::NES(nes, addr, vals.len()),
                HexDump::Lit(vals)
            );
        }
    }
}

rom_test!(crc_check, "crc_check.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
    assert_eq!(nes.cpu.a, 0x00);
    mem_cmp(&nes, 0x20, &[0xFF, 0xFF, 0xFF, 0xFF]);
    mem_cmp(&nes, 0x24, &[0x69, 0xCF, 0xF8, 0x88]);
    mem_cmp(&nes, 0x28, &[0xD3, 0x9E, 0xF1, 0x11]);
    mem_cmp(&nes, 0x2C, &[0x52, 0x93, 0x45, 0x3F]);
    mem_cmp(&nes, 0x30, &[0xDF, 0x7C, 0x47, 0x12]);
    mem_cmp(&nes, 0x34, &[0x49, 0x4C, 0x40, 0x65]);
    mem_cmp(&nes, 0x38, &[0x72, 0x10, 0xFD, 0xD2]);
});

rom_test!(implied_instructions, "implied_instrs.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8026 HLT :2 []");
    assert_eq!(nes.cpu.a, 0x00);
});

rom_test!(bit, "alu_bit.nes", |nes| {
    assert_eq!(nes.last_instruction(), "0x8021 HLT :2 []");
    mem_cmp(&nes, 0x300, &[0xF6, 0xF6]);
});
