Documentation
use serde::{Deserialize, Serialize};

use crate::{context, util::trait_alias};

trait_alias!(pub trait Context = context::Bus + context::MemoryController + context::Mapper + context::Interrupt + context::Timing);

#[derive(Default, Serialize, Deserialize)]
pub struct Cpu {
    world: u64,
    counter: u64,
    reg: Register,
    nmi_prev: bool,
    i_flag_prev: bool,
}

#[derive(Default, Serialize, Deserialize)]
struct Register {
    a: u8,
    x: u8,
    y: u8,
    s: u8,
    pc: u16,
    flag: Flag,
}

#[derive(Default, Serialize, Deserialize)]
struct Flag {
    c: bool,
    z: bool,
    i: bool,
    d: bool,
    v: bool,
    n: bool,
}

impl Flag {
    fn set_u8(&mut self, v: u8) {
        self.n = (v & 0x80) != 0;
        self.v = (v & 0x40) != 0;
        self.d = (v & 0x08) != 0;
        self.i = (v & 0x04) != 0;
        self.z = (v & 0x02) != 0;
        self.c = (v & 0x01) != 0;
    }

    fn get_u8(&self, b: u8) -> u8 {
        let mut v = 0x20;
        v |= if self.n { 0x80 } else { 0 };
        v |= if self.v { 0x40 } else { 0 };
        v |= b << 4;
        v |= if self.d { 0x08 } else { 0 };
        v |= if self.i { 0x04 } else { 0 };
        v |= if self.z { 0x02 } else { 0 };
        v |= if self.c { 0x01 } else { 0 };
        v
    }

    fn set_nz(&mut self, v: u8) {
        self.z = v == 0;
        self.n = v & 0x80 != 0;
    }
}

#[derive(Debug)]
pub enum Interrupt {
    Rst,
    Irq,
    Nmi,
}

impl Interrupt {
    fn vector_addr(&self) -> u16 {
        match self {
            Interrupt::Rst => 0xFFFC,
            Interrupt::Irq => 0xFFFE,
            Interrupt::Nmi => 0xFFFA,
        }
    }
}

impl Cpu {
    pub fn reset(&mut self, ctx: &mut impl Context) {
        self.exec_interrupt(ctx, Interrupt::Rst, false);
    }

    pub fn set_pc(&mut self, pc: u16) {
        self.reg.pc = pc;
    }

    fn exec_interrupt(&mut self, ctx: &mut impl Context, interrupt: Interrupt, brk: bool) {
        log::info!("Interrupt: {:?}", interrupt);

        let vector = interrupt.vector_addr();

        self.push16(ctx, self.reg.pc);
        self.push8(ctx, self.reg.flag.get_u8(if brk { 3 } else { 2 }));
        self.reg.pc = self.read(ctx, vector) as u16 | (self.read(ctx, vector + 1) as u16) << 8;
        self.reg.flag.i = true;
    }

    fn read(&mut self, ctx: &mut impl Context, addr: u16) -> u8 {
        let ret = ctx.read(addr);
        self.tick_bus(ctx);
        log::trace!(target: "prgmem", "[${addr:04X}] -> ${ret:02X}");
        ret
    }

    fn write(&mut self, ctx: &mut impl Context, addr: u16, data: u8) {
        ctx.write(addr, data);
        self.tick_bus(ctx);
        log::trace!(target: "prgmem", "[${addr:04X}] <- ${data:02X}");
    }

    fn fetch8(&mut self, ctx: &mut impl Context) -> u8 {
        let ret = self.read(ctx, self.reg.pc);
        self.reg.pc = self.reg.pc.wrapping_add(1);
        ret
    }

    fn fetch16(&mut self, ctx: &mut impl Context) -> u16 {
        let lo = self.fetch8(ctx);
        let hi = self.fetch8(ctx);
        lo as u16 | (hi as u16) << 8
    }

    fn push8(&mut self, ctx: &mut impl Context, data: u8) {
        self.write(ctx, 0x100 + self.reg.s as u16, data);
        self.reg.s = self.reg.s.wrapping_sub(1);
    }

    fn push16(&mut self, ctx: &mut impl Context, data: u16) {
        self.push8(ctx, (data >> 8) as u8);
        self.push8(ctx, data as u8);
    }

    fn pop8(&mut self, ctx: &mut impl Context) -> u8 {
        self.reg.s = self.reg.s.wrapping_add(1);
        self.read(ctx, 0x100 + self.reg.s as u16)
    }

    fn pop16(&mut self, ctx: &mut impl Context) -> u16 {
        let lo = self.pop8(ctx) as u16;
        let hi = self.pop8(ctx) as u16;
        lo | (hi << 8)
    }
}

#[allow(clippy::upper_case_acronyms)]
enum AddrMode {
    IMP, // Implicit
    ACC, // Accumulator
    IMM, // Immediate: #v
    ZPG, // Zero Page: d
    ABS, // Absolute: a
    REL, // Relative: label
    IND, // Indirect: (d)
    ZPX, // Zero Page indexed: d,X
    ZPY, // Zero Page indexed: d,Y
    ABX, // Absolute indexed: a,X
    ABY, // Absolute indexed: a,Y
    INX, // Indirect indexed: (d,X)
    INY, // Indirect indexed: (d),Y
    UNK,
}

impl AddrMode {
    fn len(&self) -> usize {
        use AddrMode::*;
        match self {
            IMP | ACC => 1,
            IMM | ZPG | REL | ZPX | ZPY | INX | INY => 2,
            ABS | IND | ABX | ABY => 3,
            UNK => 1,
        }
    }
}

macro_rules! instructions {
    ($cont:ident) => {
        $cont! {
            0x00: BRK IMP, 0x01: ORA INX, 0x02: UNK UNK, 0x03:*SLO INX,
            0x04:*NOP ZPG, 0x05: ORA ZPG, 0x06: ASL ZPG, 0x07:*SLO ZPG,
            0x08: PHP IMP, 0x09: ORA IMM, 0x0A: ASL ACC, 0x0B:*AAC IMM,
            0x0C:*NOP ABS, 0x0D: ORA ABS, 0x0E: ASL ABS, 0x0F:*SLO ABS,
            0x10: BPL REL, 0x11: ORA INY, 0x12: UNK UNK, 0x13:*SLO INY,
            0x14:*NOP ZPX, 0x15: ORA ZPX, 0x16: ASL ZPX, 0x17:*SLO ZPX,
            0x18: CLC IMP, 0x19: ORA ABY, 0x1A:*NOP IMP, 0x1B:*SLO ABY,
            0x1C:*NOP ABX, 0x1D: ORA ABX, 0x1E: ASL ABX, 0x1F:*SLO ABX,
            0x20: JSR ABS, 0x21: AND INX, 0x22: UNK UNK, 0x23:*RLA INX,
            0x24: BIT ZPG, 0x25: AND ZPG, 0x26: ROL ZPG, 0x27:*RLA ZPG,
            0x28: PLP IMP, 0x29: AND IMM, 0x2A: ROL ACC, 0x2B:*AAC IMM,
            0x2C: BIT ABS, 0x2D: AND ABS, 0x2E: ROL ABS, 0x2F:*RLA ABS,
            0x30: BMI REL, 0x31: AND INY, 0x32: UNK UNK, 0x33:*RLA INY,
            0x34:*NOP ZPX, 0x35: AND ZPX, 0x36: ROL ZPX, 0x37:*RLA ZPX,
            0x38: SEC IMP, 0x39: AND ABY, 0x3A:*NOP IMP, 0x3B:*RLA ABY,
            0x3C:*NOP ABX, 0x3D: AND ABX, 0x3E: ROL ABX, 0x3F:*RLA ABX,
            0x40: RTI IMP, 0x41: EOR INX, 0x42: UNK UNK, 0x43:*SRE INX,
            0x44:*NOP ZPG, 0x45: EOR ZPG, 0x46: LSR ZPG, 0x47:*SRE ZPG,
            0x48: PHA IMP, 0x49: EOR IMM, 0x4A: LSR ACC, 0x4B:*ASR IMM,
            0x4C: JMP ABS, 0x4D: EOR ABS, 0x4E: LSR ABS, 0x4F:*SRE ABS,
            0x50: BVC REL, 0x51: EOR INY, 0x52: UNK UNK, 0x53:*SRE INY,
            0x54:*NOP ZPX, 0x55: EOR ZPX, 0x56: LSR ZPX, 0x57:*SRE ZPX,
            0x58: CLI IMP, 0x59: EOR ABY, 0x5A:*NOP IMP, 0x5B:*SRE ABY,
            0x5C:*NOP ABX, 0x5D: EOR ABX, 0x5E: LSR ABX, 0x5F:*SRE ABX,
            0x60: RTS IMP, 0x61: ADC INX, 0x62: UNK UNK, 0x63:*RRA INX,
            0x64:*NOP ZPG, 0x65: ADC ZPG, 0x66: ROR ZPG, 0x67:*RRA ZPG,
            0x68: PLA IMP, 0x69: ADC IMM, 0x6A: ROR ACC, 0x6B:*ARR IMM,
            0x6C: JMP IND, 0x6D: ADC ABS, 0x6E: ROR ABS, 0x6F:*RRA ABS,
            0x70: BVS REL, 0x71: ADC INY, 0x72: UNK UNK, 0x73:*RRA INY,
            0x74:*NOP ZPX, 0x75: ADC ZPX, 0x76: ROR ZPX, 0x77:*RRA ZPX,
            0x78: SEI IMP, 0x79: ADC ABY, 0x7A:*NOP IMP, 0x7B:*RRA ABY,
            0x7C:*NOP ABX, 0x7D: ADC ABX, 0x7E: ROR ABX, 0x7F:*RRA ABX,
            0x80:*NOP IMM, 0x81: STA INX, 0x82:*NOP IMM, 0x83:*SAX INX,
            0x84: STY ZPG, 0x85: STA ZPG, 0x86: STX ZPG, 0x87:*SAX ZPG,
            0x88: DEY IMP, 0x89:*NOP IMM, 0x8A: TXA IMP, 0x8B: UNK UNK,
            0x8C: STY ABS, 0x8D: STA ABS, 0x8E: STX ABS, 0x8F:*SAX ABS,
            0x90: BCC REL, 0x91: STA INY, 0x92: UNK UNK, 0x93: UNK UNK,
            0x94: STY ZPX, 0x95: STA ZPX, 0x96: STX ZPY, 0x97:*SAX ZPY,
            0x98: TYA IMP, 0x99: STA ABY, 0x9A: TXS IMP, 0x9B: UNK UNK,
            0x9C:*SYA ABX, 0x9D: STA ABX, 0x9E:*SXA ABY, 0x9F: UNK UNK,
            0xA0: LDY IMM, 0xA1: LDA INX, 0xA2: LDX IMM, 0xA3:*LAX INX,
            0xA4: LDY ZPG, 0xA5: LDA ZPG, 0xA6: LDX ZPG, 0xA7:*LAX ZPG,
            0xA8: TAY IMP, 0xA9: LDA IMM, 0xAA: TAX IMP, 0xAB:*ATX IMM,
            0xAC: LDY ABS, 0xAD: LDA ABS, 0xAE: LDX ABS, 0xAF:*LAX ABS,
            0xB0: BCS REL, 0xB1: LDA INY, 0xB2: UNK UNK, 0xB3:*LAX INY,
            0xB4: LDY ZPX, 0xB5: LDA ZPX, 0xB6: LDX ZPY, 0xB7:*LAX ZPY,
            0xB8: CLV IMP, 0xB9: LDA ABY, 0xBA: TSX IMP, 0xBB: UNK UNK,
            0xBC: LDY ABX, 0xBD: LDA ABX, 0xBE: LDX ABY, 0xBF:*LAX ABY,
            0xC0: CPY IMM, 0xC1: CMP INX, 0xC2:*NOP IMM, 0xC3:*DCP INX,
            0xC4: CPY ZPG, 0xC5: CMP ZPG, 0xC6: DEC ZPG, 0xC7:*DCP ZPG,
            0xC8: INY IMP, 0xC9: CMP IMM, 0xCA: DEX IMP, 0xCB:*AXS IMM,
            0xCC: CPY ABS, 0xCD: CMP ABS, 0xCE: DEC ABS, 0xCF:*DCP ABS,
            0xD0: BNE REL, 0xD1: CMP INY, 0xD2: UNK UNK, 0xD3:*DCP INY,
            0xD4:*NOP ZPX, 0xD5: CMP ZPX, 0xD6: DEC ZPX, 0xD7:*DCP ZPX,
            0xD8: CLD IMP, 0xD9: CMP ABY, 0xDA:*NOP IMP, 0xDB:*DCP ABY,
            0xDC:*NOP ABX, 0xDD: CMP ABX, 0xDE: DEC ABX, 0xDF:*DCP ABX,
            0xE0: CPX IMM, 0xE1: SBC INX, 0xE2:*NOP IMM, 0xE3:*ISB INX,
            0xE4: CPX ZPG, 0xE5: SBC ZPG, 0xE6: INC ZPG, 0xE7:*ISB ZPG,
            0xE8: INX IMP, 0xE9: SBC IMM, 0xEA: NOP IMP, 0xEB:*SBC IMM,
            0xEC: CPX ABS, 0xED: SBC ABS, 0xEE: INC ABS, 0xEF:*ISB ABS,
            0xF0: BEQ REL, 0xF1: SBC INY, 0xF2: UNK UNK, 0xF3:*ISB INY,
            0xF4:*NOP ZPX, 0xF5: SBC ZPX, 0xF6: INC ZPX, 0xF7:*ISB ZPX,
            0xF8: SED IMP, 0xF9: SBC ABY, 0xFA:*NOP IMP, 0xFB:*ISB ABY,
            0xFC:*NOP ABX, 0xFD: SBC ABX, 0xFE: INC ABX, 0xFF:*ISB ABX,
        }
    };
}

impl Cpu {
    pub fn tick(&mut self, ctx: &mut impl Context) {
        let stall = ctx.cpu_stall();
        for _ in 0..stall {
            self.tick_bus(ctx);
        }

        self.world += 1;

        while self.counter < self.world {
            let nmi_cur = ctx.nmi();
            let nmi_prev = self.nmi_prev;
            self.nmi_prev = nmi_cur;

            let irq_prev = ctx.irq();
            self.i_flag_prev = self.reg.flag.i;

            self.exec_one(ctx);

            if nmi_prev && !nmi_cur {
                self.exec_interrupt(ctx, Interrupt::Nmi, false);
                continue;
            }

            if !self.i_flag_prev && irq_prev {
                self.exec_interrupt(ctx, Interrupt::Irq, false);
                continue;
            }
        }
    }

    fn tick_bus(&mut self, ctx: &mut impl Context) {
        self.counter += 1;
        ctx.tick_bus();
    }

    fn exec_one(&mut self, ctx: &mut impl Context) {
        if log::log_enabled!(log::Level::Trace) {
            self.trace(ctx);
        }

        let opaddr = self.reg.pc;
        let opc = self.fetch8(ctx);

        macro_rules! gen_code {
            ($($opc:literal: $a:tt $b:ident $($c:ident)?, )*) => {{
                match opc {
                    $( $opc => exec!($a $b $($c)*), )*
                }
            }};
        }

        macro_rules! is_read {
            (STA) => {
                false
            };
            (LSR) => {
                false
            };
            (ASL) => {
                false
            };
            (ROR) => {
                false
            };
            (ROL) => {
                false
            };
            (INC) => {
                false
            };
            (DEC) => {
                false
            };
            ($mne:ident) => {
                true
            };
        }

        macro_rules! exec {
            (*$mne:ident $mode:ident) => {
                exec!($mne $mode)
            };
            ($mne:ident IMP) => {{
                let _ = self.read(ctx, self.reg.pc);
                exec_op!($mne)
            }};
            ($mne:ident ACC) => {{
                let _ = self.read(ctx, self.reg.pc);
                exec_op!($mne, ACC)
            }};


            ($mne:ident $mode:ident) => {{
                #[allow(unused_variables)]
                let read = is_read!($mne);
                #[allow(unused_variables)]
                let addr = effaddr!($mode, read);
                exec_op!($mne, addr)
            }};
        }

        macro_rules! effaddr {
            (IMM, $read:ident) => {{
                let ret = self.reg.pc;
                self.reg.pc = self.reg.pc.wrapping_add(1);
                ret
            }};
            (ABS, $read:ident) => {{
                self.fetch16(ctx)
            }};
            (ABX, $read:ident) => {
                effaddr!(abs_ix, x, $read)
            };
            (ABY, $read:ident) => {
                effaddr!(abs_ix, y, $read)
            };
            (abs_ix, $reg:ident, $read:ident) => {{
                let addr = self.fetch16(ctx);
                let tmp = (addr & 0xff) + self.reg.$reg as u16;
                if !$read || tmp >= 0x100 {
                    let _ = self.read(ctx, addr & 0xff00 | tmp & 0xff);
                }
                addr.wrapping_add(self.reg.$reg as u16)
            }};
            (IND, $read:ident) => {{
                let lo = self.fetch16(ctx);
                let hi = (lo & 0xff00) | (lo as u8).wrapping_add(1) as u16;
                self.read(ctx, lo) as u16 | (self.read(ctx, hi) as u16) << 8
            }};
            (ZPG, $read:ident) => {{
                self.fetch8(ctx) as u16
            }};
            (ZPX, $read:ident) => {{
                let addr = self.fetch8(ctx);
                self.read(ctx, addr as u16);
                addr.wrapping_add(self.reg.x) as u16
            }};
            (ZPY, $read:ident) => {{
                let addr = self.fetch8(ctx);
                self.read(ctx, addr as u16);
                addr.wrapping_add(self.reg.y) as u16
            }};
            (INX, $read:ident) => {{
                let a = self.fetch8(ctx);
                let _ = self.read(ctx, a as u16);
                let a = a.wrapping_add(self.reg.x);
                let lo = self.read(ctx, a as u16);
                let hi = self.read(ctx, a.wrapping_add(1) as u16);
                lo as u16 | (hi as u16) << 8
            }};
            (INY, $read:ident) => {{
                let a = self.fetch8(ctx);
                let lo = self.read(ctx, a as u16) as u16;
                let hi = self.read(ctx, a.wrapping_add(1) as u16) as u16;
                let addr = (lo | hi << 8);
                let tmp = lo + self.reg.y as u16;
                if !$read || tmp >= 0x100 {
                    let _ = self.read(ctx, hi << 8 | tmp & 0xff);
                }
                addr.wrapping_add(self.reg.y as u16)
            }};
            (REL, $read:ident) => {{
                let rel = self.fetch8(ctx) as i8;
                self.reg.pc.wrapping_add(rel as u16)
            }};
            (UNK, $read:ident) => {{}};
        }

        macro_rules! exec_op {
            (ADC, $addr:ident) => {{
                let a = self.reg.a as u16;
                let b = self.read(ctx, $addr) as u16;
                let c = self.reg.flag.c as u16;
                let r = a.wrapping_add(b).wrapping_add(c);
                self.reg.flag.c = r > 0xff;
                self.reg.flag.v = (a ^ r) & (b ^ r) & 0x80 != 0;
                self.reg.a = r as u8;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (SBC, $addr:ident) => {{
                let a = self.reg.a as u16;
                let b = self.read(ctx, $addr) as u16;
                let c = self.reg.flag.c as u16;
                let r = a.wrapping_sub(b).wrapping_sub(1 - c);
                self.reg.flag.c = r <= 0xff;
                self.reg.flag.v = (a ^ b) & (a ^ r) & 0x80 != 0;
                self.reg.a = r as u8;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (AND, $addr:ident) => {{
                self.reg.a &= self.read(ctx, $addr);
                self.reg.flag.set_nz(self.reg.a);
            }};
            (ORA, $addr:ident) => {{
                self.reg.a |= self.read(ctx, $addr);
                self.reg.flag.set_nz(self.reg.a);
            }};
            (EOR, $addr:ident) => {{
                self.reg.a ^= self.read(ctx, $addr);
                self.reg.flag.set_nz(self.reg.a);
            }};
            (BIT, $addr:ident) => {{
                let r = self.read(ctx, $addr);
                self.reg.flag.v = r & 0x40 != 0;
                self.reg.flag.n = r & 0x80 != 0;
                self.reg.flag.z = (self.reg.a & r) == 0;
            }};

            (cmp, $reg:ident, $addr:ident) => {{
                let a = self.reg.$reg as u16;
                let b = self.read(ctx, $addr) as u16;
                let r = a.wrapping_sub(b);
                self.reg.flag.c = r <= 0xff;
                self.reg.flag.set_nz(r as u8);
            }};
            (CMP, $addr:ident) => {{
                exec_op!(cmp, a, $addr);
            }};
            (CPX, $addr:ident) => {{
                exec_op!(cmp, x, $addr);
            }};
            (CPY, $addr:ident) => {{
                exec_op!(cmp, y, $addr);
            }};

            (ld, $reg:ident, $addr:ident) => {{
                self.reg.$reg = self.read(ctx, $addr);
                self.reg.flag.set_nz(self.reg.$reg);
            }};
            (LDA, $addr:ident) => {{
                exec_op!(ld, a, $addr)
            }};
            (LDX, $addr:ident) => {{
                exec_op!(ld, x, $addr)
            }};
            (LDY, $addr:ident) => {{
                exec_op!(ld, y, $addr)
            }};

            (st, $reg:ident, $addr:ident) => {{
                self.write(ctx, $addr, self.reg.$reg);
            }};
            (STA, $addr:ident) => {{
                exec_op!(st, a, $addr)
            }};
            (STX, $addr:ident) => {{
                exec_op!(st, x, $addr)
            }};
            (STY, $addr:ident) => {{
                exec_op!(st, y, $addr)
            }};

            (mov, s, $src:ident) => {{
                self.reg.s = self.reg.$src;
            }};
            (mov, $dest:ident, $src:ident) => {{
                self.reg.$dest = self.reg.$src;
                self.reg.flag.set_nz(self.reg.$dest);
            }};
            (TAX) => {{
                exec_op!(mov, x, a);
            }};
            (TAY) => {{
                exec_op!(mov, y, a);
            }};
            (TXA) => {{
                exec_op!(mov, a, x);
            }};
            (TYA) => {{
                exec_op!(mov, a, y);
            }};
            (TSX) => {{
                exec_op!(mov, x, s);
            }};
            (TXS) => {{
                exec_op!(mov, s, x);
            }};

            (rmw, $op:ident, $addr:ident) => {{
                let mut a = self.read(ctx, $addr);
                self.write(ctx, $addr, a);
                exec_op!($op, a);
                self.write(ctx, $addr, a);
            }};

            (asl, $var:expr) => {{
                self.reg.flag.c = $var & 0x80 != 0;
                $var <<= 1;
                self.reg.flag.set_nz($var);
            }};
            (lsr, $var:expr) => {{
                self.reg.flag.c = $var & 1 != 0;
                $var >>= 1;
                self.reg.flag.set_nz($var);
            }};
            (rol, $var:expr) => {{
                let t = $var;
                $var = (t << 1) | self.reg.flag.c as u8;
                self.reg.flag.c = t & 0x80 != 0;
                self.reg.flag.set_nz($var);
            }};
            (ror, $var:expr) => {{
                let t = $var;
                $var = (t >> 1) | (self.reg.flag.c as u8) << 7;
                self.reg.flag.c = t & 1 != 0;
                self.reg.flag.set_nz($var);
            }};
            (inc, $var:expr) => {{
                $var = $var.wrapping_add(1);
                self.reg.flag.set_nz($var);
            }};
            (dec, $var:expr) => {{
                $var = $var.wrapping_sub(1);
                self.reg.flag.set_nz($var);
            }};

            (ASL, ACC) => {{
                exec_op!(asl, self.reg.a);
            }};
            (ASL, $addr:ident) => {{
                exec_op!(rmw, asl, $addr);
            }};
            (LSR, ACC) => {{
                exec_op!(lsr, self.reg.a);
            }};
            (LSR, $addr:ident) => {{
                exec_op!(rmw, lsr, $addr);
            }};
            (ROL, ACC) => {{
                exec_op!(rol, self.reg.a);
            }};
            (ROL, $addr:ident) => {{
                exec_op!(rmw, rol, $addr);
            }};
            (ROR, ACC) => {{
                exec_op!(ror, self.reg.a);
            }};
            (ROR, $addr:ident) => {{
                exec_op!(rmw, ror, $addr);
            }};
            (INX) => {{
                exec_op!(inc, self.reg.x);
            }};
            (INY) => {{
                exec_op!(inc, self.reg.y);
            }};
            (INC, $addr:ident) => {{
                exec_op!(rmw, inc, $addr);
            }};
            (DEX) => {{
                exec_op!(dec, self.reg.x);
            }};
            (DEY) => {{
                exec_op!(dec, self.reg.y);
            }};
            (DEC, $addr:ident) => {{
                exec_op!(rmw, dec, $addr);
            }};

            (JMP, $addr:ident) => {{
                self.reg.pc = $addr;
            }};
            (JSR, $addr:ident) => {{
                let _ = self.read(ctx, self.reg.s as u16 | 0x100);
                self.push16(ctx, self.reg.pc.wrapping_sub(1));
                self.reg.pc = $addr;
            }};
            (RTS) => {{
                let _ = self.read(ctx, self.reg.s as u16 | 0x100);
                let pc = self.pop16(ctx);
                let _ = self.read(ctx, pc);
                self.reg.pc = pc.wrapping_add(1);
            }};
            (RTI) => {{
                let _ = self.read(ctx, self.reg.s as u16 | 0x100);
                let p = self.pop8(ctx);
                self.reg.flag.set_u8(p);
                // Flag set by RTI affects interrupts
                self.i_flag_prev = self.reg.flag.i;
                self.reg.pc = self.pop16(ctx);
            }};

            (bra, $cond:ident, $val:expr, $addr:ident) => {{
                if self.reg.flag.$cond == $val {
                    let _ = self.read(ctx, self.reg.pc);
                    if self.reg.pc & 0xff00 != $addr & 0xff00 {
                        self.read(ctx, self.reg.pc & 0xff00 | $addr & 0xff);
                    }
                    self.reg.pc = $addr;
                }
            }};
            (BCC, $addr:ident) => {{
                exec_op!(bra, c, false, $addr)
            }};
            (BCS, $addr:ident) => {{
                exec_op!(bra, c, true, $addr)
            }};
            (BNE, $addr:ident) => {{
                exec_op!(bra, z, false, $addr)
            }};
            (BEQ, $addr:ident) => {{
                exec_op!(bra, z, true, $addr)
            }};
            (BPL, $addr:ident) => {{
                exec_op!(bra, n, false, $addr)
            }};
            (BMI, $addr:ident) => {{
                exec_op!(bra, n, true, $addr)
            }};
            (BVC, $addr:ident) => {{
                exec_op!(bra, v, false, $addr)
            }};
            (BVS, $addr:ident) => {{
                exec_op!(bra, v, true, $addr)
            }};

            (SEC) => {{
                self.reg.flag.c = true;
            }};
            (SED) => {{
                self.reg.flag.d = true;
            }};
            (SEI) => {{
                self.reg.flag.i = true;
            }};
            (CLC) => {{
                self.reg.flag.c = false;
            }};
            (CLD) => {{
                self.reg.flag.d = false;
            }};
            (CLI) => {{
                self.reg.flag.i = false;
            }};
            (CLV) => {{
                self.reg.flag.v = false;
            }};

            (PHA) => {{
                self.push8(ctx, self.reg.a);
            }};
            (PHP) => {{
                self.push8(ctx, self.reg.flag.get_u8(3));
            }};
            (PLA) => {{
                let _ = self.read(ctx, self.reg.s as u16 | 0x100);
                self.reg.a = self.pop8(ctx);
                self.reg.flag.set_nz(self.reg.a);
            }};
            (PLP) => {{
                let _ = self.read(ctx, self.reg.s as u16 | 0x100);
                let p = self.pop8(ctx);
                self.reg.flag.set_u8(p);
            }};

            (BRK) => {{
                self.reg.pc = self.reg.pc.wrapping_add(1);
                self.exec_interrupt(ctx, Interrupt::Irq, true);
                // Interrupt after BRK did not happen
                self.i_flag_prev = self.reg.flag.i;
            }};

            (NOP) => {{}};

            // Undocumented
            (NOP, $addr:ident) => {{
                let _ = self.read(ctx, $addr);
            }};

            (LAX, $addr:ident) => {{
                self.reg.a = self.read(ctx, $addr);
                self.reg.x = self.reg.a;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (SAX, $addr:ident) => {{
                self.write(ctx, $addr, self.reg.a & self.reg.x);
            }};
            (DCP, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                let b = b.wrapping_sub(1);
                self.write(ctx, $addr, b);
                let r = (self.reg.a as u16).wrapping_sub(b as u16);
                self.reg.flag.c = r <= 0xff;
                self.reg.flag.set_nz(r as u8);
            }};
            (ISB, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                let b = b.wrapping_add(1);
                self.write(ctx, $addr, b);
                let a = self.reg.a as u16;
                let b = b as u16;
                let c = self.reg.flag.c as u16;
                let r = a.wrapping_sub(b).wrapping_sub(1 - c);
                self.reg.flag.c = r <= 0xff;
                self.reg.flag.v = (a ^ b) & (a ^ r) & 0x80 != 0;
                self.reg.a = r as u8;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (SLO, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                self.reg.flag.c = b >> 7 != 0;
                let b = b << 1;
                self.reg.a |= b;
                self.write(ctx, $addr, b);
                self.reg.flag.set_nz(self.reg.a);
            }};
            (RLA, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                let c = self.reg.flag.c;
                self.reg.flag.c = b >> 7 != 0;
                let b = (b << 1) | c as u8;
                self.write(ctx, $addr, b);
                self.reg.a &= b;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (SRE, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                self.reg.flag.c = b & 1 != 0;
                let b = b >> 1;
                self.write(ctx, $addr, b);
                self.reg.a ^= b;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (RRA, $addr:ident) => {{
                let b = self.read(ctx, $addr);
                self.write(ctx, $addr, b);
                let c = self.reg.flag.c as u8;
                self.reg.flag.c = b & 1 != 0;
                let b = (b >> 1) | (c << 7);
                self.write(ctx, $addr, b);
                let a = self.reg.a as u16;
                let b = b as u16;
                let c = self.reg.flag.c as u16;
                let r = a.wrapping_add(b).wrapping_add(c);
                self.reg.flag.c = r > 0xff;
                self.reg.flag.v = (a ^ r) & (b ^ r) & 0x80 != 0;
                self.reg.a = r as u8;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (AAC, $addr:ident) => {{
                self.reg.a &= self.read(ctx, $addr);
                self.reg.flag.set_nz(self.reg.a);
                self.reg.flag.c = self.reg.flag.n;
            }};
            (ASR, $addr:ident) => {{
                self.reg.a &= self.read(ctx, $addr);
                self.reg.flag.c = self.reg.a & 1 != 0;
                self.reg.a >>= 1;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (ARR, $addr:ident) => {{
                self.reg.a &= self.read(ctx, $addr);
                self.reg.a = (self.reg.a >> 1) | (self.reg.flag.c as u8) << 7;
                self.reg.flag.set_nz(self.reg.a);
                self.reg.flag.c = (self.reg.a >> 6) & 1 != 0;
                self.reg.flag.v = ((self.reg.a >> 5) & 1 != 0) != self.reg.flag.c;
            }};
            (ATX, $addr:ident) => {{
                self.reg.a = self.read(ctx, $addr);
                self.reg.x = self.reg.a;
                self.reg.flag.set_nz(self.reg.a);
            }};
            (AXS, $addr:ident) => {{
                let t =
                    ((self.reg.x & self.reg.a) as u16).wrapping_sub(self.read(ctx, $addr) as u16);
                self.reg.x = t as u8;
                self.reg.flag.set_nz(self.reg.x);
                self.reg.flag.c = t <= 0xff;
            }};
            (SYA, $addr:ident) => {{
                let t = self.reg.y & (($addr >> 8) + 1) as u8;
                if self.reg.x as u16 + self.read(ctx, opaddr.wrapping_add(1)) as u16 <= 0xff {
                    self.write(ctx, $addr, t);
                }
            }};
            (SXA, $addr:ident) => {{
                let t = self.reg.x & (($addr >> 8) + 1) as u8;
                if self.reg.y as u16 + self.read(ctx, opaddr.wrapping_add(1)) as u16 <= 0xff {
                    self.write(ctx, $addr, t);
                }
            }};

            (UNK, $addr:ident) => {{
                log::warn!("invalid opcode: ${opc:02X}");
            }};
        }

        instructions!(gen_code);
    }

    fn trace(&self, ctx: &impl Context) {
        use crate::consts::{LINES_PER_FRAME, PPU_CLOCK_PER_LINE};

        let pc = self.reg.pc;
        let opc = ctx.read_pure(pc).unwrap_or(0);
        let opr = ctx.read_pure(pc + 1).unwrap_or(0) as u16
            | (ctx.read_pure(pc + 2).unwrap_or(0) as u16) << 8;

        let ppu_cycle = self.counter * 3;
        let line = ppu_cycle / PPU_CLOCK_PER_LINE % LINES_PER_FRAME as u64;
        let col = ppu_cycle % PPU_CLOCK_PER_LINE;

        let asm = disasm(pc, opc, opr);
        let prg_page = if pc & 0x8000 != 0 {
            format!("{:02X}", ctx.prg_page(((pc & !0x8000) / 0x2000) as _))
        } else {
            "  ".to_string()
        };

        log::trace!(target: "disasm",
            "{prg_page}:{pc:04X}: {asm:13} | A:{a:02X} X:{x:02X} Y:{y:02X} S:{s:02X} P:{n}{v}{d}{i}{z}{c} PPU:{line:3},{col:3}",
            pc = self.reg.pc,
            a = self.reg.a,
            x = self.reg.x,
            y = self.reg.y,
            s = self.reg.s,
            n = if self.reg.flag.n { 'N' } else { '-' },
            v = if self.reg.flag.v { 'V' } else { '-' },
            d = if self.reg.flag.d { 'D' } else { '-' },
            i = if self.reg.flag.i { 'I' } else { '-' },
            z = if self.reg.flag.z { 'Z' } else { '-' },
            c = if self.reg.flag.c { 'C' } else { '-' },
        );

        let bytes = match INSTR_TABLE[opc as usize].1.len() {
            1 => format!("{opc:02X}"),
            2 => format!("{opc:02X} {:02X}", opr & 0xff),
            3 => format!("{opc:02X} {:02X} {:02X}", opr & 0xff, opr >> 8),
            _ => unreachable!(),
        };

        let read = |addr: u16| {
            if !(0x2000..0x8000).contains(&addr) {
                format!("{:02X}", ctx.read_pure(addr).unwrap_or(0))
            } else {
                "??".to_string()
            }
        };

        let ctx = match &INSTR_TABLE[opc as usize].1 {
            AddrMode::ZPG => format!(" = {}", read(opr & 0xff)),
            AddrMode::ABS => {
                if !matches!(INSTR_TABLE[opc as usize].0, "JMP" | "JSR") {
                    format!(" = {}", read(opr))
                } else {
                    "".to_string()
                }
            }
            AddrMode::IND => format!(
                " = {}{}",
                read((opr & 0xff00) | (opr as u8).wrapping_add(1) as u16),
                read(opr)
            ),
            AddrMode::ZPX => {
                let addr = (opr as u8).wrapping_add(self.reg.x);
                format!(" @ {addr:02X} = {}", read(addr as u16))
            }
            AddrMode::ZPY => {
                let addr = (opr as u8).wrapping_add(self.reg.y);
                format!(" @ {addr:02X} = {}", read(addr as u16))
            }
            AddrMode::ABX => {
                let addr = opr.wrapping_add(self.reg.x as u16);
                format!(" @ {addr:04X} = {}", read(addr as u16))
            }
            AddrMode::ABY => {
                let addr = opr.wrapping_add(self.reg.y as u16);
                format!(" @ {addr:04X} = {}", read(addr as u16))
            }
            AddrMode::INX => {
                let addr = (opr as u8).wrapping_add(self.reg.x);
                let ind = ctx.read_pure(addr as u16).unwrap_or(0) as u16
                    | (ctx.read_pure(addr.wrapping_add(1) as u16).unwrap_or(0) as u16) << 8;
                format!(" @ {addr:02X} = {ind:04X} = {}", read(ind))
            }
            AddrMode::INY => {
                let ind = ctx.read_pure((opr as u8) as u16).unwrap_or(0) as u16
                    | (ctx
                        .read_pure((opr as u8).wrapping_add(1) as u16)
                        .unwrap_or(0) as u16)
                        << 8;
                let addr = ind.wrapping_add(self.reg.y as u16);
                format!(" = {ind:04X} @ {addr:04X} = {}", read(addr))
            }

            AddrMode::IMP | AddrMode::ACC | AddrMode::IMM | AddrMode::REL | AddrMode::UNK => {
                "".to_string()
            }
        };

        let asm = format!("{}{}", asm, ctx);

        log::trace!(target: "disasnt",
            "{pc:04X}  {bytes:8} {asm:32} \
            A:{a:02X} X:{x:02X} Y:{y:02X} P:{p:02X} SP:{s:02X} \
            PPU:{line:3},{col:3} CYC:{cyc}",
            pc = self.reg.pc,
            a = self.reg.a,
            x = self.reg.x,
            y = self.reg.y,
            s = self.reg.s,
            p = self.reg.flag.get_u8(2),
            cyc = self.counter,
        );
    }
}

macro_rules! instr_table {
    ($($opc:literal: $a:tt $b:ident $($c:ident)?, )*) => {{
        [$(
            instr_entry!($a $b $($c)*),
        )*]
    }};
}

macro_rules! instr_entry {
    (*$mne:ident $mode:ident) => {{
        (stringify!($mne), AddrMode::$mode, false)
    }};
    ($mne:ident $mode:ident) => {{
        (stringify!($mne), AddrMode::$mode, true)
    }};
}

const INSTR_TABLE: [(&str, AddrMode, bool); 256] = instructions!(instr_table);

fn disasm(pc: u16, opc: u8, opr: u16) -> String {
    let opc = opc as usize;
    let (mne, addr_mode, official) = &INSTR_TABLE[opc];
    let u = if *official { ' ' } else { '*' };

    match addr_mode {
        AddrMode::IMP => format!("{u}{mne}"),
        AddrMode::IMM => format!("{u}{mne} #${:02X}", opr & 0xff),
        AddrMode::ACC => format!("{u}{mne} A"),
        AddrMode::ABS => format!("{u}{mne} ${opr:04X}"),
        AddrMode::ABX => format!("{u}{mne} ${opr:04X},X"),
        AddrMode::ABY => format!("{u}{mne} ${opr:04X},Y"),
        AddrMode::IND => format!("{u}{mne} (${opr:04X})"),
        AddrMode::ZPG => format!("{u}{mne} ${:02X}", opr & 0xff),
        AddrMode::ZPX => format!("{u}{mne} ${:02X},X", opr & 0xff),
        AddrMode::ZPY => format!("{u}{mne} ${:02X},Y", opr & 0xff),
        AddrMode::INX => format!("{u}{mne} (${:02X},X)", opr & 0xff),
        AddrMode::INY => format!("{u}{mne} (${:02X}),Y", opr & 0xff),
        AddrMode::REL => {
            let addr = pc.wrapping_add((opr & 0xff) as i8 as u16).wrapping_add(2);
            format!("{u}{mne} ${:04X}", addr)
        }
        AddrMode::UNK => format!("{u}{mne} ???"),
    }
}