use std::fmt::Write;
use tracing::debug;

use crate::{Break, debug::DebugLog, mem::CpuMem};

bitfield::bitfield! {
    #[derive(Clone, Copy, PartialEq, Eq)]
    pub struct CpuStatus(u8);
    pub carry, set_carry: 0;
    pub zero, set_zero: 1;
    pub interrupt_disable, set_interrupt_disable: 2;
    pub decimal, set_decimal: 3;
    pub brk, set_brk: 4;
    pub php, set_php: 5;
    pub overflow, set_overflow: 6;
    pub negative, set_negative: 7;
}

impl std::fmt::Debug for CpuStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.negative() {
            write!(f, "N")?;
        }
        if self.overflow() {
            write!(f, "V")?;
        }
        if self.decimal() {
            write!(f, "D")?;
        }
        if self.interrupt_disable() {
            write!(f, "I")?;
        }
        if self.zero() {
            write!(f, "Z")?;
        }
        if self.carry() {
            write!(f, "C")?;
        }
        Ok(())
    }
}

#[derive(Debug, Clone)]
pub struct Cpu {
    pub a: u8,
    pub x: u8,
    pub y: u8,
    pub pc: u16,
    pub sp: u8,
    pub status: CpuStatus,
    pub clock_state: ClockState,

    pub nmi_line_state: bool,
    pub nmi_pending: bool,
    pub irq_pending: bool,

    pub(crate) last_instruction: String,
    pub(crate) debug_log: DebugLog,
    halted: bool,
    pub(crate) cycle: usize,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClockState {
    ReadInstruction,
    ReadOperands {
        instruction: u8,
        ops: [u8; 5],
        count: u8,
        addr: u16,
    },
    Hold {
        cycles: u8,
        instruction: u8,
        ops: [u8; 5],
        count: u8,
        addr: u16,
    },
    Oops {
        instruction: u8,
        ops: [u8; 5],
        count: u8,
        addr: u16,
    },
    HoldNmi {
        cycles: u8,
    },
    HoldIrq {
        cycles: u8,
    },
    PageCross,
}

impl std::fmt::Debug for ClockState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ReadInstruction => write!(f, "ReadInstruction"),
            Self::ReadOperands {
                instruction,
                ops,
                count,
                addr,
            } => f
                .debug_struct("ReadOperands")
                .field("instruction", instruction)
                .field("addr", addr)
                .field("ops", &&ops[..*count as usize])
                .finish(),
            Self::Hold {
                cycles,
                instruction,
                ops,
                count,
                addr,
            } => f
                .debug_struct("Hold")
                .field("cycles", cycles)
                .field("instruction", instruction)
                .field("addr", addr)
                .field("ops", &&ops[..*count as usize])
                .finish(),
            ClockState::HoldNmi { cycles } => {
                f.debug_struct("HoldNmi").field("cycles", cycles).finish()
            }
            ClockState::HoldIrq { cycles } => {
                f.debug_struct("HoldIrq").field("cycles", cycles).finish()
            }
            ClockState::Oops {
                instruction,
                ops,
                count,
                addr,
            } => f
                .debug_struct("Oops")
                .field("instruction", instruction)
                .field("addr", addr)
                .field("ops", &&ops[..*count as usize])
                .finish(),
            ClockState::PageCross => write!(f, "PageCross"),
        }
    }
}

enum ExecState {
    Done,
    MoreParams,
    Hold(u8),
    Oops,
    PageCross,
}

#[derive(Debug, Clone, Copy)]
pub enum CPUMMRegisters {
    PPU,
    APU,
}

impl Cpu {
    pub fn init() -> Self {
        Self {
            a: 0,
            x: 0,
            y: 0,
            pc: 0,
            sp: 0,
            status: CpuStatus(0),
            clock_state: ClockState::ReadInstruction,
            nmi_pending: false,
            irq_pending: false,
            nmi_line_state: false,

            cycle: 7,
            last_instruction: String::new(),
            debug_log: DebugLog::new(),
            halted: false,
        }
    }

    pub fn read_abs(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 {
        mem.read(u16::from_le_bytes([low, high]))
    }
    pub fn read_abs_x(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 {
        mem.read(u16::from_le_bytes([low, high]) + self.x as u16)
    }
    pub fn read_abs_y(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8) -> u8 {
        mem.read(u16::from_le_bytes([low, high]) + self.y as u16)
    }
    pub fn read_zp_x(&mut self, mem: &mut CpuMem<'_>, low: u8) -> u8 {
        mem.read(u16::from_le_bytes([low.wrapping_add(self.x), 0]))
    }
    pub fn read_zp_y(&mut self, mem: &mut CpuMem<'_>, low: u8) -> u8 {
        mem.read(u16::from_le_bytes([low.wrapping_add(self.y), 0]))
    }
    pub fn write_abs(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) {
        mem.write(u16::from_le_bytes([low, high]), val)
    }
    pub fn write_abs_x(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) {
        mem.write(u16::from_le_bytes([low, high]) + self.x as u16, val)
    }
    pub fn write_abs_y(&mut self, mem: &mut CpuMem<'_>, low: u8, high: u8, val: u8) {
        mem.write(u16::from_le_bytes([low, high]) + self.y as u16, val)
    }
    pub fn write_zp_x(&mut self, mem: &mut CpuMem<'_>, low: u8, val: u8) {
        mem.write(u16::from_le_bytes([low.wrapping_add(self.x), 0]), val)
    }
    pub fn write_zp_y(&mut self, mem: &mut CpuMem<'_>, low: u8, val: u8) {
        mem.write(u16::from_le_bytes([low.wrapping_add(self.y), 0]), val)
    }

    pub fn push(&mut self, mem: &mut CpuMem<'_>, val: u8) {
        mem.write(self.sp as u16 + 0x100, val);
        self.sp = self.sp.wrapping_sub(1);
    }
    pub fn pop(&mut self, mem: &mut CpuMem<'_>) -> u8 {
        self.sp = self.sp.wrapping_add(1);
        mem.read(self.sp as u16 + 0x100)
    }
    pub fn push_16(&mut self, mem: &mut CpuMem<'_>, val: u16) {
        debug!("Writing rc = {:04X} at {:02X}", val, self.sp);
        let [low, high] = val.to_le_bytes();
        self.push(mem, high);
        self.push(mem, low);
    }
    pub fn pop_16(&mut self, mem: &mut CpuMem<'_>) -> u16 {
        let low = self.pop(mem);
        let high = self.pop(mem);
        u16::from_le_bytes([low, high])
    }

    #[must_use]
    fn jmp_rel(&mut self, off: u8) -> bool {
        let pc = if off < 128 {
            self.pc.wrapping_add(off as u16)
        } else {
            self.pc.wrapping_sub(0x100 - off as u16)
        };
        let page_cross = pc & 0xFF00 != self.pc & 0xFF00;
        self.pc = pc;
        page_cross
    }

    fn halt(&mut self) {
        self.halted = true;
    }

    /// Returns true if more bytes are needed
    fn exec_instruction(
        &mut self,
        mem: &mut CpuMem<'_>,
        ins: u8,
        mut params: &[u8],
        held: bool,
        oops: bool,
        addr: u16,
    ) -> ExecState {
        macro_rules! inst {
            ($val:expr, $hold:expr, |$($name:pat_param),*| $eval:expr) => {{
                let hold_time: u8 = ($hold).into();
                if params.len() < ([$(inst!(@EMPTY $name)),*] as [(); _]).len() {
                    ExecState::MoreParams
                } else if !held && hold_time != 0 {
                    ExecState::Hold(hold_time - 1)
                } else {
                    // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.pc - (1 + params.len() as u16), $val, ins, params);
                    // debug!("Running 0x{:04X} {} :{:X} {:X?}", self.pc - (1 + params.len() as u16), $val, ins, params);
                    self.last_instruction = format!("0x{:04X} {} :{:X} {:X?}", addr, $val, ins, params);
                    $(
                        let $name = params[0];
                        #[allow(unused_assignments)]
                        { params = &params[1..]; }
                    )*
                    {$eval}
                    // self.status.set_interrupt_disable(true);
                    ExecState::Done
                }
            }};
            ($name:expr, $hold:expr, || $eval:expr) => {
                inst!($name, $hold, | | $eval)
            };
            (@EMPTY $n:pat_param) => {
                ()
            };
        }
        macro_rules! log {
            ($($t:tt)*) => {
                let args = format_args!($($t)*);
                self.debug_log.write_fmt(args).expect("Failed to write debug log");
                self.debug_log.write_char('\n').expect("Failed to write debug log");
            };
        }
        match ins {
            0x02 => inst!("HLT", 0, || {
                log!("{addr:04X}: HLT");
                self.halt()
            }),
            0x12 => inst!("DBG", 0, || {
                log!("{addr:04X}: DBG");
                self.halt()
            }),
            0x22 | 0x32 | 0x42 | 0x52 | 0x62 | 0x72 | 0x92 | 0xB2 | 0xD3 | 0xF2 => {
                panic!("Game crash by executing {ins:02X} at {addr:04X}")
            }
            0x38 => inst!("SEC", 1, || {
                log!("{addr:04X}: SEC");
                self.status.set_carry(true)
            }),
            0x18 => inst!("CLC", 1, || {
                log!("{addr:04X}: CLC");
                self.status.set_carry(false)
            }),
            0x78 => inst!("SEI", 1, || {
                log!("{addr:04X}: SEI");
                self.status.set_interrupt_disable(true)
            }),
            0x58 => inst!("CLI", 1, || {
                log!("{addr:04X}: CLI");
                self.status.set_interrupt_disable(false)
            }),
            0xF8 => inst!("SED", 1, || {
                log!("{addr:04X}: SED");
                self.status.set_decimal(true)
            }),
            0xD8 => inst!("CLD", 1, || {
                log!("{addr:04X}: CLD");
                self.status.set_decimal(false)
            }),
            0xB8 => inst!("CLV", 1, || {
                log!("{addr:04X}: CLV");
                self.status.set_overflow(false)
            }),
            0x00 => inst!("BRK", 5, |_ignored| {
                // TODO: this should probably just set `self.irq_pending` ...
                log!("{addr:04X}: BRK #${_ignored:02X}");
                self.push_16(mem, self.pc);
                self.push(mem, self.status.0 | 0b00110000);
                self.status.set_interrupt_disable(true);
                self.pc = u16::from_le_bytes([mem.read(0xFFFE), mem.read(0xFFFF)]);
                log!("    : - JMP ${:X}", self.pc);
            }),

            0x48 => inst!("PHA", 2, || {
                log!("{addr:04X}: PHA | {:02X} -> 01{:02X}", self.a, self.sp);
                self.push(mem, self.a);
            }),
            0x08 => inst!("PHP", 2, || {
                log!(
                    "{addr:04X}: PHP | {:02X} -> 01{:02X}",
                    self.status.0,
                    self.sp
                );
                self.push(mem, self.status.0 | 0b0011_0000);
            }),
            0x68 => inst!("PLA", 3, || {
                self.a = self.pop(mem);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: PLA | {:02X} <- 01{:02X}", self.a, self.sp);
            }),
            0x28 => inst!("PLP", 3, || {
                self.status.0 = self.pop(mem) & 0b1100_1111;
                log!(
                    "{addr:04X}: PLP | {:02X} <- 01{:02X}",
                    self.status.0,
                    self.sp
                );
            }),

            // Loads
            0xA9 => inst!("LDA imm", 0, |a| {
                self.a = a;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA #${:02X}", a);
            }),
            0xA5 => inst!("LDA zp", 1, |off| {
                self.a = mem.read(off as u16);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA ${:02X} | {:02X}", off, self.a);
            }),
            0xB5 => inst!("LDA zp,x", 2, |off| {
                self.a = self.read_zp_x(mem, off);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA ${:02X},x | {:02X}", off, self.a);
            }),
            0xAD => inst!("LDA abs", 1, |low, high| {
                self.a = self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0xBD => inst!("LDA abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                self.a = self.read_abs_x(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: LDA ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xB9 => inst!("LDA abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a = self.read_abs_y(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: LDA ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xA1 => inst!("LDA (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                self.a = self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA (${:02X},x) | {:02X}", off, self.a);
            }),
            0xB1 => inst!("LDA (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a = mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: LDA (${:02X}),y | {:02X}", off, self.a);
            }),
            0xA2 => inst!("LDX imm", 0, |x| {
                self.x = x;
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: LDX #${:02X}", x);
            }),
            0xA6 => inst!("LDX zp", 1, |off| {
                self.x = mem.read(off as u16);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: LDX ${:02X} | {:02X}", off, self.x);
            }),
            0xB6 => inst!("LDX zp,y", 2, |off| {
                self.x = self.read_zp_y(mem, off);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: LDX ${:02X},y | {:02X}", off, self.x);
            }),
            0xAE => inst!("LDX abs", 1, |low, high| {
                self.x = self.read_abs(mem, low, high);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: LDX ${:02X}{:02X} | {:02X}", high, low, self.x);
            }),
            0xBE => inst!("LDX abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.x = self.read_abs_y(mem, low, high);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!(
                    "{addr:04X}: LDX ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.x
                );
            }),
            0xA0 => inst!("LDY imm", 0, |y| {
                self.y = y;
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: LDY #${:02X}", y);
            }),
            0xA4 => inst!("LDY zp", 1, |off| {
                self.y = mem.read(off as u16);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: LDY ${:02X} | {:02X}", off, self.y);
            }),
            0xB4 => inst!("LDX zp,x", 2, |off| {
                self.y = self.read_zp_x(mem, off);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: LDY ${:02X},x | {:02X}", off, self.y);
            }),
            0xAC => inst!("LDX abs", 1, |low, high| {
                self.y = self.read_abs(mem, low, high);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: LDY ${:02X}{:02X} | {:02X}", high, low, self.y);
            }),
            0xBC => inst!("LDX abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                self.y = self.read_abs_x(mem, low, high);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!(
                    "{addr:04X}: LDY ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.y
                );
            }),

            // Stores
            0x85 => inst!("STA zp", 1, |off| {
                mem.write(off as u16, self.a);
                log!("{addr:04X}: STA ${:02X} | {:02X}", off, self.a);
            }),
            0x95 => inst!("STA zp,x", 2, |off| {
                self.write_zp_x(mem, off, self.a);
                log!("{addr:04X}: STA ${:02X},x | {:02X}", off, self.a);
            }),
            0x8D => inst!("STA abs", 1, |low, high| {
                self.write_abs(mem, low, high, self.a);
                log!("{addr:04X}: STA ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x9D => inst!("STA abs,x", 2, |low, high| {
                self.write_abs_x(mem, low, high, self.a);
                log!(
                    "{addr:04X}: STA ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x99 => inst!("STA abs,y", 2, |low, high| {
                self.write_abs_y(mem, low, high, self.a);
                log!(
                    "{addr:04X}: STA ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x81 => inst!("STA (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                self.write_abs(mem, low, high, self.a);
                log!("{addr:04X}: STA (${:02X},x) | {:02X}", off, self.a);
            }),
            0x91 => inst!("STA (ind),y", 4, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                mem.write(u16::from_le_bytes([low, high]) + self.y as u16, self.a);
                log!("{addr:04X}: STA (${:02X}),y | {:02X}", off, self.a);
            }),
            0x86 => inst!("STX zp", 1, |off| {
                mem.write(off as u16, self.x);
                log!("{addr:04X}: STX ${:02X} | {:02X}", off, self.x);
            }),
            0x96 => inst!("STX zp,y", 2, |off| {
                self.write_zp_y(mem, off, self.x);
                log!("{addr:04X}: STX ${:02X},y | {:02X}", off, self.x);
            }),
            0x8E => inst!("STX abs", 1, |low, high| {
                self.write_abs(mem, low, high, self.x);
                log!("{addr:04X}: STX ${:02X}{:02X} | {:02X}", high, low, self.x);
            }),
            0x84 => inst!("STY zp", 1, |off| {
                mem.write(off as u16, self.y);
                log!("{addr:04X}: STY ${:02X} | {:02X}", off, self.y);
            }),
            0x94 => inst!("STY zp,x", 2, |off| {
                self.write_zp_x(mem, off, self.y);
                log!("{addr:04X}: STY ${:02X},x | {:02X}", off, self.y);
            }),
            0x8C => inst!("STY abs", 1, |low, high| {
                self.write_abs(mem, low, high, self.y);
                log!("{addr:04X}: STY ${:02X}{:02X} | {:02X}", high, low, self.y);
            }),

            // Transfers
            0xAA => inst!("TAX", 1, || {
                self.x = self.a;
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: TAX | {:02X}", self.x);
            }),
            0xA8 => inst!("TAY", 1, || {
                self.y = self.a;
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: TAY | {:02X}", self.y);
            }),
            0xBA => inst!("TSX", 1, || {
                self.x = self.sp;
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: TSX | {:02X}", self.x);
            }),
            0x8A => inst!("TXA", 1, || {
                self.a = self.x;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: TXA | {:02X}", self.a);
            }),
            0x9A => inst!("TXS", 1, || {
                self.sp = self.x;
                log!("{addr:04X}: TXS | {:02X}", self.sp);
            }),
            0x98 => inst!("TYA", 1, || {
                self.a = self.y;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: TYA | {:02X}", self.a);
            }),

            // Branch
            0x90 => inst!("BCC", !self.status.carry(), |off| {
                log!("{addr:04X}: BCC ${:02}", off);
                if !self.status.carry() {
                    let cross = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if cross {
                        return ExecState::PageCross;
                    }
                }
            }),
            0xB0 => inst!("BCS", self.status.carry(), |off| {
                log!("{addr:04X}: BCS ${:02}", off);
                if self.status.carry() {
                    let page = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if page {
                        return ExecState::PageCross;
                    };
                }
            }),
            0xF0 => inst!("BEQ", self.status.zero(), |off| {
                log!("{addr:04X}: BEQ ${:02}", off);
                if self.status.zero() {
                    let page = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if page {
                        return ExecState::PageCross;
                    };
                }
            }),
            0x30 => inst!("BMI", self.status.negative(), |off| {
                log!("{addr:04X}: BMI ${:02}", off);
                if self.status.negative() {
                    let page = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if page {
                        return ExecState::PageCross;
                    };
                }
            }),
            0xD0 => inst!("BNE", !self.status.zero(), |off| {
                log!("{addr:04X}: BNE {:02}", off as i8);
                if !self.status.zero() {
                    let page = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if page {
                        return ExecState::PageCross;
                    };
                }
            }),
            0x10 => inst!("BPL", !self.status.negative(), |off| {
                log!("{addr:04X}: BPL ${:02}", off);
                if !self.status.negative() {
                    let page = self.jmp_rel(off);
                    log!("    : - JMP ${:04X}", self.pc);
                    if page {
                        return ExecState::PageCross;
                    };
                }
            }),
            0x4C => inst!("JMP abs", 0, |low, high| {
                self.pc = u16::from_le_bytes([low, high]);
                log!("{addr:04X}: JMP ${:04X}", self.pc);
            }),
            0x6C => inst!("JMP abs", 2, |low, high| {
                self.pc = u16::from_le_bytes([
                    self.read_abs(mem, low, high),
                    self.read_abs(mem, low.wrapping_add(1), high), // Known CPU bug
                ]);
                log!("{addr:04X}: JMP (${:02X}{:02X})", high, low);
                log!("    : - JMP ${:04X}", self.pc);
            }),
            0x20 => inst!("JSR", 3, |low, high| {
                self.push_16(mem, self.pc - 1);
                let tmp = self.pc - 1;
                self.pc = u16::from_le_bytes([low, high]);
                log!("{addr:04X}: JSR ${:04X} | ret ${:04X}", self.pc, tmp);
            }),
            0x60 => inst!("RTS", 5, || {
                self.pc = self.pop_16(mem) + 1;
                log!("{addr:04X}: RTS | ${:04X}", self.pc);
            }),
            0x40 => inst!("RTI", 5, || {
                self.status.0 = self.pop(mem) & 0b1100_1111;
                self.pc = self.pop_16(mem);
                log!("{addr:04X}: RTI | ${:04X}", self.pc);
            }),

            // CMP
            0xC9 => inst!("CMP imm", 0, |val| {
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP #${:02X} | {:02X} - {:02X} -> {:?}",
                    val,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xC5 => inst!("CMP zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X} | {:02X} - {:02X} -> {:?}",
                    off,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xD5 => inst!("CMP zp,x", 2, |off| {
                let val = self.read_zp_x(mem, off);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X},x | {:02X} - {:02X} -> {:?}",
                    off,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xCD => inst!("CMP abs", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X}{:02X} | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xDD => inst!("CMP abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_x(mem, low, high);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X}{:02X},x | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xD9 => inst!("CMP abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_y(mem, low, high);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xC1 => inst!("CMP (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                let val = self.read_abs(mem, low, high);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xD1 => inst!("CMP (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_y(mem, low, high);
                let v = self.a.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.a >= val);
                log!(
                    "{addr:04X}: CMP ${:02X}{:02X},y | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.a,
                    val,
                    self.status
                );
            }),
            0xE0 => inst!("CPX imm", 0, |val| {
                let v = self.x.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.x >= val);
                log!(
                    "{addr:04X}: CPX #${:02X} | {:02X} - {:02X} -> {:?}",
                    val,
                    self.x,
                    val,
                    self.status
                );
            }),
            0xE4 => inst!("CPX zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                let v = self.x.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.x >= val);
                log!(
                    "{addr:04X}: CPX ${:02X} | {:02X} - {:02X} -> {:?}",
                    off,
                    self.x,
                    val,
                    self.status
                );
            }),
            0xEC => inst!("CPX abs", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                let v = self.x.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.x >= val);
                log!(
                    "{addr:04X}: CPX ${:02X}{:02X} | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.x,
                    val,
                    self.status
                );
            }),
            0xC0 => inst!("CPY imm", 0, |val| {
                let v = self.y.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.y >= val);
                log!(
                    "{addr:04X}: CPY #${:02X} | {:02X} - {:02X} -> {:?}",
                    val,
                    self.y,
                    val,
                    self.status
                );
            }),
            0xC4 => inst!("CPY zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                let v = self.y.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.y >= val);
                log!(
                    "{addr:04X}: CPY ${:02X} | {:02X} - {:02X} -> {:?}",
                    off,
                    self.y,
                    val,
                    self.status
                );
            }),
            0xCC => inst!("CPY zp", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                let v = self.y.wrapping_sub(val);
                self.status.set_zero(v == 0);
                self.status.set_negative(v & 0x80 == 0x80);
                self.status.set_carry(self.y >= val);
                log!(
                    "{addr:04X}: CPY ${:02X}{:02X} | {:02X} - {:02X} -> {:?}",
                    high,
                    low,
                    self.y,
                    val,
                    self.status
                );
            }),

            // Arithmetic
            0x69 => inst!("ADC imm", 0, |val| {
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ADC #${:02X} | {:02X}", val, self.a);
            }),
            0x65 => inst!("ADC zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ADC ${:02X} | {:02X}", off, self.a);
            }),
            0x75 => inst!("ADC zp,x", 2, |off| {
                let val = self.read_zp_x(mem, off);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ADC ${:02X},x | {:02X}", off, self.a);
            }),
            0x6D => inst!("ADC abs", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ADC ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x7D => inst!("ADC abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_x(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ADC ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x79 => inst!("ADC abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_y(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ADC ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x61 => inst!("ADC (ind,x)", 4, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ADC (${:02X}{:02X},x) | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x71 => inst!("ADC (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                let (a, carry_1) = self.a.overflowing_add(val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ADC (${:02X}{:02X}),y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xE9 => inst!("SBC imm", 0, |val| {
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                // TODO: I'm pretty sure the carry logic is wrong here
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: SBC #${:02X} | {:02X}", val, self.a);
            }),
            0xE5 => inst!("SBC zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
            }),
            0xF5 => inst!("SBC zp,x", 2, |off| {
                let val = self.read_zp_x(mem, off);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: SBC ${:02X},x | {:02X}", off, self.a);
            }),
            0xED => inst!("SBC abs", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: SBC ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0xFD => inst!("SBC abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_x(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: SBC ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xF9 => inst!("SBC abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = self.read_abs_y(mem, low, high);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: SBC ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xE1 => inst!("SBC (ind,x)", 4, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: SBC (${:02X}{:02X},x) | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xF1 => inst!("SBC (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                let val = mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                let (a, carry_1) = self.a.overflowing_add(!val);
                let (a, carry_2) = a.overflowing_add(self.status.carry().into());
                self.status
                    .set_overflow((self.a ^ a) & (!val ^ a) & 0x80 != 0);
                self.a = a;
                self.status.set_carry(carry_1 | carry_2);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: SBC (${:02X}{:02X}),y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xC6 => inst!("DEC zp", 3, |off| {
                let val = self.read_abs(mem, off, 0).wrapping_sub(1);
                self.write_abs(mem, off, 0, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: DEC ${:02X} | {:02X}", off, val);
            }),
            0xD6 => inst!("DEC zp,x", 4, |off| {
                let val = self.read_zp_x(mem, off).wrapping_sub(1);
                self.write_zp_x(mem, off, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: DEC ${:02X},x | {:02X}", off, self.a);
            }),
            0xCE => inst!("DEC abs", 3, |low, high| {
                let val = self.read_abs(mem, low, high).wrapping_sub(1);
                self.write_abs(mem, low, high, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: DEC ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0xDE => inst!("DEC abs,x", 4, |low, high| {
                let val = self.read_abs_x(mem, low, high).wrapping_sub(1);
                self.write_abs_x(mem, low, high, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!(
                    "{addr:04X}: DEC ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xCA => inst!("DEX", 1, || {
                self.x = self.x.wrapping_sub(1);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: DEX | {:02X}", self.x);
            }),
            0x88 => inst!("DEY", 1, || {
                self.y = self.y.wrapping_sub(1);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: DEY | {:02X}", self.y);
            }),
            0xE6 => inst!("INC zp", 3, |off| {
                let val = self.read_abs(mem, off, 0).wrapping_add(1);
                self.write_abs(mem, off, 0, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: INC ${:02X} | {:02X}", off, val);
            }),
            0xF6 => inst!("INC zp,x", 4, |off| {
                let val = self.read_zp_x(mem, off).wrapping_add(1);
                self.write_zp_x(mem, off, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: INC ${:02X},x | {:02X}", off, self.a);
            }),
            0xEE => inst!("INC abs", 3, |low, high| {
                let val = self.read_abs(mem, low, high).wrapping_add(1);
                self.write_abs(mem, low, high, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: INC ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0xFE => inst!("INC abs,x", 4, |low, high| {
                let val = self.read_abs_x(mem, low, high).wrapping_add(1);
                self.write_abs_x(mem, low, high, val);
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                log!(
                    "{addr:04X}: INC ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0xE8 => inst!("INX", 1, || {
                self.x = self.x.wrapping_add(1);
                self.status.set_zero(self.x == 0);
                self.status.set_negative(self.x & 0x80 == 0x80);
                log!("{addr:04X}: INX | {:02X}", self.x);
            }),
            0xC8 => inst!("INY", 1, || {
                self.y = self.y.wrapping_add(1);
                self.status.set_zero(self.y == 0);
                self.status.set_negative(self.y & 0x80 == 0x80);
                log!("{addr:04X}: INY | {:02X}", self.y);
            }),

            // Logical
            0x09 => inst!("ORA imm", 0, |val| {
                self.a |= val;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ORA #${:02X} | {:02X}", val, self.a);
            }),
            0x05 => inst!("ORA zp", 1, |off| {
                self.a |= self.read_abs(mem, off, 0);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ORA ${:02X} | {:02X}", off, self.a);
            }),
            0x15 => inst!("ORA zp,x", 2, |off| {
                self.a |= self.read_zp_x(mem, off);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ORA ${:02X},x | {:02X}", off, self.a);
            }),
            0x0D => inst!("ORA abs", 1, |low, high| {
                self.a |= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ORA ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x1D => inst!("ORA abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                self.a |= self.read_abs_x(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ORA ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x19 => inst!("ORA abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a |= self.read_abs_y(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ORA ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x01 => inst!("ORA (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                self.a |= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ORA (${:02X}{:02X},x) | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x11 => inst!("ORA (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a |= mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: ORA (${:02X}{:02X}),y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x29 => inst!("AND imm", 0, |val| {
                self.a &= val;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: AND #${:02X} | {:02X}", val, self.a);
            }),
            0x25 => inst!("AND zp", 1, |off| {
                self.a &= self.read_abs(mem, off, 0);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: AND ${:02X} | {:02X}", off, self.a);
            }),
            0x35 => inst!("AND zp,x", 2, |off| {
                self.a &= self.read_zp_x(mem, off);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: AND ${:02X},x | {:02X}", off, self.a);
            }),
            0x2D => inst!("AND abs", 1, |low, high| {
                self.a &= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: AND ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x3D => inst!("AND abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                self.a &= self.read_abs_x(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: AND ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x39 => inst!("AND abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a &= self.read_abs_y(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: AND ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x21 => inst!("AND (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                self.a &= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: AND (${:02X}{:02X},x) | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x31 => inst!("AND (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a &= mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: AND (${:02X}{:02X}),y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x49 => inst!("EOR imm", 0, |val| {
                self.a ^= val;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: EOR #${:02X} | {:02X}", val, self.a);
            }),
            0x45 => inst!("EOR zp", 1, |off| {
                self.a ^= self.read_abs(mem, off, 0);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: EOR ${:02X} | {:02X}", off, self.a);
            }),
            0x55 => inst!("EOR zp,x", 2, |off| {
                self.a ^= self.read_zp_x(mem, off);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: EOR ${:02X},x | {:02X}", off, self.a);
            }),
            0x4D => inst!("EOR abs", 1, |low, high| {
                self.a ^= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: EOR ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x5D => inst!("EOR abs,x", 1, |low, high| {
                if !oops && low.checked_add(self.x).is_none() {
                    return ExecState::Oops;
                }
                self.a ^= self.read_abs_x(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: EOR ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x59 => inst!("EOR abs,y", 1, |low, high| {
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a ^= self.read_abs_y(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: EOR ${:02X}{:02X},y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x41 => inst!("EOR (ind,x)", 4, |off| {
                let low = self.read_zp_x(mem, off);
                let high = self.read_zp_x(mem, off.wrapping_add(1));
                self.a ^= self.read_abs(mem, low, high);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: EOR (${:02X}{:02X},x) | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x51 => inst!("EOR (ind),y", 3, |off| {
                let low = self.read_abs(mem, off, 0);
                let high = self.read_abs(mem, off.wrapping_add(1), 0);
                if !oops && low.checked_add(self.y).is_none() {
                    return ExecState::Oops;
                }
                self.a ^= mem.read(u16::from_le_bytes([low, high]) + self.y as u16);
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!(
                    "{addr:04X}: EOR (${:02X}{:02X}),y | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x24 => inst!("BIT zp", 1, |off| {
                let val = self.read_abs(mem, off, 0);
                self.status.set_zero(val & self.a == 0);
                self.status.set_overflow(val & 0x40 == 0x40);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: BIT ${:02X} | {:02X}", off, val);
            }),
            0x2C => inst!("BIT abs", 1, |low, high| {
                let val = self.read_abs(mem, low, high);
                self.status.set_zero(val & self.a == 0);
                self.status.set_overflow(val & 0x40 == 0x40);
                self.status.set_negative(val & 0x80 == 0x80);
                log!("{addr:04X}: BIT ${:02X}{:02X} | {:02X}", high, low, val);
            }),

            // Shifts
            0x4A => inst!("LSR A", 1, || {
                self.status.set_carry(self.a & 0b1 == 0b1);
                self.a = self.a >> 1;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(false);
                log!("{addr:04X}: LSR A | {:02X}", self.a);
            }),
            0x46 => inst!("LSR zp", 3, |off| {
                let val = self.read_abs(mem, off, 0);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(false);
                self.write_abs(mem, off, 0, val);
                log!("{addr:04X}: LSR ${:02X} | {:02X}", off, self.a);
            }),
            0x56 => inst!("LSR zp,x", 4, |off| {
                let val = self.read_zp_x(mem, off);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(false);
                self.write_zp_x(mem, off, val);
                log!("{addr:04X}: LSR ${:02X},x | {:02X}", off, self.a);
            }),
            0x4E => inst!("LSR abs", 3, |low, high| {
                let val = self.read_abs(mem, low, high);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(false);
                self.write_abs(mem, low, high, val);
                log!("{addr:04X}: LSR ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x5E => inst!("LSR abs,x", 4, |low, high| {
                let val = self.read_abs_x(mem, low, high);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(false);
                self.write_abs_x(mem, low, high, val);
                log!(
                    "{addr:04X}: LSR ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x0A => inst!("ASL A", 1, || {
                self.status.set_carry(self.a & 0x80 == 0x80);
                self.a = self.a << 1;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ASL A | {:02X}", self.a);
            }),
            0x06 => inst!("ASL zp", 3, |off| {
                let val = self.read_abs(mem, off, 0);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, off, 0, val);
                log!("{addr:04X}: ASL ${:02X} | {:02X}", off, self.a);
            }),
            0x16 => inst!("ASL zp,x", 4, |off| {
                let val = self.read_zp_x(mem, off);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_zp_x(mem, off, val);
                log!("{addr:04X}: ASL ${:02X},x | {:02X}", off, self.a);
            }),
            0x0E => inst!("ASL abs", 3, |low, high| {
                let val = self.read_abs(mem, low, high);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, low, high, val);
                log!("{addr:04X}: ASL ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x1E => inst!("ASL abs,x", 4, |low, high| {
                let val = self.read_abs_x(mem, low, high);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs_x(mem, low, high, val);
                log!(
                    "{addr:04X}: ASL ${:02X}{:02X},x | {:02X}",
                    high,
                    low,
                    self.a
                );
            }),
            0x6A => inst!("ROR A", 1, || {
                let old_carry = if self.status.carry() { 0x80 } else { 0x00 };
                self.status.set_carry(self.a & 0b1 == 0b1);
                self.a = self.a >> 1 | old_carry;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ROR A | {:02X}", self.a);
            }),
            0x66 => inst!("ROR zp", 3, |off| {
                let old_carry = if self.status.carry() { 0x80 } else { 0x00 };
                let val = self.read_abs(mem, off, 0);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, off, 0, val);
                log!("{addr:04X}: ROR ${:02X} | {:02X}", off, self.a);
            }),
            0x76 => inst!("ROR zp,x", 4, |off| {
                let old_carry = if self.status.carry() { 0x80 } else { 0x00 };
                let val = self.read_zp_x(mem, off);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_zp_x(mem, off, val);
                log!("{addr:04X}: ROR ${:02X},x | {:02X}", off, val);
            }),
            0x6E => inst!("ROR abs", 3, |low, high| {
                let old_carry = if self.status.carry() { 0x80 } else { 0x00 };
                let val = self.read_abs(mem, low, high);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, low, high, val);
                log!("{addr:04X}: ROR ${:02X}{:02X} | {:02X}", high, low, val);
            }),
            0x7E => inst!("ROR abs,x", 4, |low, high| {
                let old_carry = if self.status.carry() { 0x80 } else { 0x00 };
                let val = self.read_abs_x(mem, low, high);
                self.status.set_carry(val & 0b1 == 0b1);
                let val = val >> 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs_x(mem, low, high, val);
                log!("{addr:04X}: ROR ${:02X}{:02X},x | {:02X}", high, low, val);
            }),
            0x2A => inst!("ROL A", 1, || {
                let old_carry = if self.status.carry() { 0x01 } else { 0x00 };
                self.status.set_carry(self.a & 0x80 == 0x80);
                self.a = self.a << 1 | old_carry;
                self.status.set_zero(self.a == 0);
                self.status.set_negative(self.a & 0x80 == 0x80);
                log!("{addr:04X}: ROL A | {:02X}", self.a);
            }),
            0x26 => inst!("ROL zp", 3, |off| {
                let old_carry = if self.status.carry() { 0x01 } else { 0x00 };
                let val = self.read_abs(mem, off, 0);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, off, 0, val);
                log!("{addr:04X}: ROL ${:02X} | {:02X}", off, val);
            }),
            0x36 => inst!("ROL zp,x", 4, |off| {
                let old_carry = if self.status.carry() { 0x01 } else { 0x00 };
                let val = self.read_zp_x(mem, off);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_zp_x(mem, off, val);
                log!("{addr:04X}: ROL ${:02X},x | {:02X}", off, val);
            }),
            0x2E => inst!("ROL abs", 3, |low, high| {
                let old_carry = if self.status.carry() { 0x01 } else { 0x00 };
                let val = self.read_abs(mem, low, high);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs(mem, low, high, val);
                log!("{addr:04X}: ROL ${:02X}{:02X} | {:02X}", high, low, self.a);
            }),
            0x3E => inst!("ROL abs,x", 4, |low, high| {
                let old_carry = if self.status.carry() { 0x01 } else { 0x00 };
                let val = self.read_abs_x(mem, low, high);
                self.status.set_carry(val & 0x80 == 0x80);
                let val = val << 1 | old_carry;
                self.status.set_zero(val == 0);
                self.status.set_negative(val & 0x80 == 0x80);
                self.write_abs_x(mem, low, high, val);
                log!("{addr:04X}: ROL ${:02X}{:02X},x | {:02X}", high, low, val);
            }),

            0xEA => inst!("NOP", 1, || {
                log!("{addr:04X}: NOP");
            }),
            // _ => todo!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1),
            _ => {
                println!("ins: 0x{:04X}: 0x{ins:X}, {params:X?}", self.pc - 1);
                self.halt();
                ExecState::Done
            }
        }
    }

    pub fn clock_cpu(&mut self, mem: &mut CpuMem<'_>, nmi: bool, irq: bool, br: &Break) -> bool {
        self.clock_state = match self.clock_state {
            ClockState::HoldNmi { cycles } => {
                if cycles == 0 {
                    self.push_16(mem, self.pc);
                    self.push(mem, self.status.0 | 0x20);
                    self.pc = u16::from_le_bytes([mem.read(0xFFFA), mem.read(0xFFFB)]);
                    writeln!(self.debug_log, "Starting NMI Handler").unwrap();
                    ClockState::ReadInstruction
                } else {
                    ClockState::HoldNmi { cycles: cycles - 1 }
                }
            }
            ClockState::HoldIrq { cycles } => {
                if cycles == 0 {
                    // todo!("Run IRQ");
                    self.push_16(mem, self.pc);
                    self.push(mem, self.status.0 | 0b00110000);
                    self.status.set_interrupt_disable(true);
                    self.pc = u16::from_le_bytes([mem.read(0xFFFE), mem.read(0xFFFF)]);
                    writeln!(self.debug_log, "Starting IRQ handler").unwrap();
                    ClockState::ReadInstruction
                } else {
                    ClockState::HoldIrq { cycles: cycles - 1 }
                }
            }
            ClockState::ReadInstruction => {
                if br.cpu_exec {
                    println!("Returning early from clock_cpu");
                    return true;
                }
                let addr = self.pc;
                let instruction = mem.read(self.pc);
                if instruction != 0x02 {
                    self.pc = self.pc.wrapping_add(1);
                }
                match self.exec_instruction(mem, instruction, &[], false, false, addr) {
                    ExecState::Done => ClockState::ReadInstruction,
                    ExecState::MoreParams => ClockState::ReadOperands {
                        instruction,
                        ops: [0u8; 5],
                        count: 0,
                        addr,
                    },
                    ExecState::Hold(cycles) => ClockState::Hold {
                        cycles,
                        instruction,
                        ops: [0u8; 5],
                        count: 0,
                        addr,
                    },
                    ExecState::Oops => ClockState::Oops {
                        instruction,
                        ops: [0u8; 5],
                        count: 0,
                        addr,
                    },
                    ExecState::PageCross => ClockState::PageCross,
                }
            }
            ClockState::ReadOperands {
                instruction,
                mut ops,
                count,
                addr,
            } => {
                if count == 5 {
                    todo!()
                }
                ops[count as usize] = mem.read(self.pc);
                self.pc = self.pc.wrapping_add(1);
                match self.exec_instruction(
                    mem,
                    instruction,
                    &ops[..count as usize + 1],
                    false,
                    false,
                    addr,
                ) {
                    ExecState::Done => ClockState::ReadInstruction,
                    ExecState::MoreParams => ClockState::ReadOperands {
                        instruction,
                        ops,
                        count: count + 1,
                        addr,
                    },
                    ExecState::Hold(cycles) => ClockState::Hold {
                        cycles,
                        instruction,
                        ops,
                        count: count + 1,
                        addr,
                    },
                    ExecState::Oops => ClockState::Oops {
                        instruction,
                        ops,
                        count: count + 1,
                        addr,
                    },
                    ExecState::PageCross => ClockState::PageCross,
                }
            }
            ClockState::Hold {
                cycles,
                instruction,
                ops,
                count,
                addr,
            } => {
                if cycles == 0 {
                    match self.exec_instruction(
                        mem,
                        instruction,
                        &ops[..count as usize],
                        true,
                        false,
                        addr,
                    ) {
                        ExecState::Done => ClockState::ReadInstruction,
                        ExecState::MoreParams => {
                            panic!("Should never return MoreParams after holding")
                        }
                        ExecState::Hold(_) => panic!("Should never return Hold after holding"),
                        ExecState::Oops => ClockState::Oops {
                            instruction,
                            ops,
                            count,
                            addr,
                        },
                        ExecState::PageCross => ClockState::PageCross,
                    }
                } else {
                    ClockState::Hold {
                        cycles: cycles - 1,
                        instruction,
                        ops,
                        count,
                        addr,
                    }
                }
            }
            ClockState::Oops {
                instruction,
                ops,
                count,
                addr,
            } => {
                match self.exec_instruction(
                    mem,
                    instruction,
                    &ops[..count as usize],
                    true,
                    true,
                    addr,
                ) {
                    ExecState::Done => ClockState::ReadInstruction,
                    _ => panic!("Must execute after oops"),
                }
            }
            ClockState::PageCross => ClockState::ReadInstruction,
        };
        if self.clock_state == ClockState::ReadInstruction {
            if self.nmi_pending {
                self.nmi_pending = false;
                self.clock_state = ClockState::HoldNmi { cycles: 6 };
                writeln!(self.debug_log, "NMI detected").unwrap();
            } else if self.irq_pending && !self.status.interrupt_disable() {
                writeln!(self.debug_log, "IRQ detected").unwrap();
                self.clock_state = ClockState::HoldIrq { cycles: 6 };
            }
        }
        // Check NMI and IRQ line state. This happens in phi2, i.e. the second half of the instruction
        // let new_line_state = self.ppu.nmi_waiting() || self.apu.nmi_waiting();
        if nmi != self.nmi_line_state {
            // println!("ppu: {}, apu: {}", self.ppu.nmi_waiting(), self.apu.nmi_waiting());
            // println!("New: {new_line_state}, old: {}", self.nmi_line_state);
            self.nmi_line_state = nmi;
            self.nmi_pending |= nmi;
        }
        // self.irq_pending = self.ppu.irq_waiting() || self.apu.irq_waiting();
        self.irq_pending = irq;
        false
    }

    pub fn cpu_cycle_update(&mut self) {
        if !self.halted {
            self.cycle += 1;
        }
    }

    pub fn executed(&self) -> bool {
        matches!(self.clock_state, ClockState::ReadInstruction)
    }

    pub fn halted(&self) -> bool {
        self.halted
    }

    pub(crate) fn unhalt(&mut self) {
        self.halted = false;
    }

    pub fn reset(&mut self, mem: &mut CpuMem<'_>) {
        let debug_log = self.debug_log.enabled();
        *self = Self::init();
        if debug_log { self.debug_log.enable(); }
        self.pc = u16::from_le_bytes([mem.read(0xFFFC), mem.read(0xFFFD)]);
        self.sp = 0xFD;
        self.status.set_interrupt_disable(true);
    }
}

#[derive(Debug, Clone, Copy)]
pub enum DmaState {
    Passive,
    Idle(u16),
    Running {
        cpu_addr: u16,
        rem: u8,
        read: Option<u8>,
    },
}

impl DmaState {
    pub fn reset(&mut self) {
        *self = Self::Passive;
    }

    pub fn cycle(self, mem: &mut CpuMem<'_>, cpu: &mut Cpu) -> Option<Self> {
        match self {
            DmaState::Passive => None,
            DmaState::Idle(cpu_addr) => Some(DmaState::Running {
                cpu_addr,
                rem: 0xFF,
                read: None,
            }),
            DmaState::Running {
                cpu_addr,
                rem,
                read: Some(read),
            } => {
                mem.write(0x2004, read);
                // self.ppu.write_reg(4, read);
                writeln!(&mut cpu.debug_log, "OAM DMA write {:02X}", read).unwrap();
                if rem == 0 {
                    Some(DmaState::Passive)
                } else {
                    Some(DmaState::Running {
                        cpu_addr: cpu_addr + 1,
                        rem: rem - 1,
                        read: None,
                    })
                }
            }
            DmaState::Running {
                cpu_addr,
                rem,
                read: None,
            } => {
                if cpu.cycle % 2 == 0 {
                    writeln!(&mut cpu.debug_log, "DMA: Waiting for cycle").unwrap();
                    return Some(self);
                }
                let read = mem.read(cpu_addr);
                writeln!(
                    &mut cpu.debug_log,
                    "OAM DMA read {:04X} {:02X}",
                    cpu_addr, read
                )
                .unwrap();
                Some(DmaState::Running {
                    cpu_addr,
                    rem,
                    read: Some(read),
                })
            }
        }
    }
}

pub struct CycleResult {
    pub cpu_exec: bool,
    pub ppu_frame: bool,
    pub dma: bool,
    pub dbg_int: bool,
}
