#include <cpu.h>
#include <memory.h>
#include <ppu.h>

CPU_STATE cpu;

byte CPU_RAM[0x8000];

byte op_code;             // Current instruction code
int op_value, op_address; // Arguments for current instruction
int op_cycles;            // Additional instruction cycles used (e.g. when paging occurs)

unsigned long cpu_cycles;  // Total CPU Cycles Since Power Up (wraps)

void (*cpu_op_address_mode[256])();       // Array of address modes
void (*cpu_op_handler[256])();            // Array of instruction function pointers
bool cpu_op_in_base_instruction_set[256]; // true if instruction is in base 6502 instruction set
char *cpu_op_name[256];                   // Instruction names
int cpu_op_cycles[256];                   // CPU cycles used by instructions

byte cpu_ram_read(word address);
void cpu_ram_write(word address, byte data);

// Interrupt Addresses
word cpu_nmi_interrupt_address();
word cpu_reset_interrupt_address();
word cpu_irq_interrupt_address();

// Updates Zero and Negative flags in P
void cpu_update_zn_flags(byte value);

// If OP_TRACE, print current instruction with all registers into the console
void cpu_trace_instruction();

// CPU Adressing Modes
void cpu_address_implied();
void cpu_address_immediate();
void cpu_address_zero_page();
void cpu_address_zero_page_x();
void cpu_address_zero_page_y();
void cpu_address_absolute();
void cpu_address_absolute_x();
void cpu_address_absolute_y();
void cpu_address_relative();
void cpu_address_indirect();
void cpu_address_indirect_x();
void cpu_address_indirect_y();

static const byte cpu_zn_flag_table[256] =
{
  zero_flag,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
  negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,negative_flag,
};



// CPU Memory

inline byte cpu_ram_read(word address)
{
    return CPU_RAM[address & 0x7FF];
}

void cpu_ram_write(word address, byte data)
{
    CPU_RAM[address & 0x7FF] = data;
}



// Interrupt Addresses

inline word cpu_nmi_interrupt_address()   { return memory_readw(0xFFFA); }
inline word cpu_reset_interrupt_address() { return memory_readw(0xFFFC); }
inline word cpu_irq_interrupt_address()   { return memory_readw(0xFFFE); }



// Stack Routines

void cpu_stack_pushb(byte data) { memory_writeb(0x100 + cpu.SP--, data);       }
void cpu_stack_pushw(word data) { memory_writew(0xFF + cpu.SP, data); cpu.SP -= 2; }
byte cpu_stack_popb()           { return memory_readb(0x100 + ++cpu.SP);       }
word cpu_stack_popw()           { cpu.SP += 2; return memory_readw(0xFF + cpu.SP); }



// CPU Instructions

void ____FE____() { /* Instruction for future Extension */ }

#define cpu_flag_set(flag) common_bit_set(cpu.P, flag)
#define cpu_modify_flag(flag, value) common_modify_bitb(&cpu.P, flag, value)
#define cpu_set_flag(flag) common_set_bitb(&cpu.P, flag)
#define cpu_unset_flag(flag) common_unset_bitb(&cpu.P, flag)

#define cpu_update_zn_flags(value) cpu.P = (cpu.P & ~(zero_flag | negative_flag)) | cpu_zn_flag_table[value]

#define cpu_branch(flag) if (flag) cpu.PC = op_address;
#define cpu_compare(reg) int result = reg - op_value; \
                         cpu_modify_flag(carry_bp, result >= 0); \
                         cpu_modify_flag(zero_bp, result == 0); \
                         cpu_modify_flag(negative_bp, (result >> 7) & 1);



// CPU Instructions

// NOP

void cpu_op_nop() {}

// Addition

void cpu_op_adc()
{
    int result = cpu.A + op_value + (cpu_flag_set(carry_bp) ? 1 : 0);
    cpu_modify_flag(carry_bp, !!(result & 0x100));
    cpu_modify_flag(overflow_bp, !!(~(cpu.A ^ op_value) & (cpu.A ^ result) & 0x80));
    cpu.A = result & 0xFF;
    cpu_update_zn_flags(cpu.A);
}

// Subtraction

void cpu_op_sbc()
{
    int result = cpu.A - op_value - (cpu_flag_set(carry_bp) ? 0 : 1);
    cpu_modify_flag(carry_bp, !(result & 0x100));
    cpu_modify_flag(overflow_bp, !!((cpu.A ^ op_value) & (cpu.A ^ result) & 0x80));
    cpu.A = result & 0xFF;
    cpu_update_zn_flags(cpu.A);
}

// Bit Manipulation Operations

void cpu_op_and() { cpu_update_zn_flags(cpu.A &= op_value); }
void cpu_op_bit() { cpu_modify_flag(zero_bp, !(cpu.A & op_value)); cpu.P = (cpu.P & 0x3F) | (0xC0 & op_value); }
void cpu_op_eor() { cpu_update_zn_flags(cpu.A ^= op_value); }
void cpu_op_ora() { cpu_update_zn_flags(cpu.A |= op_value); }
void cpu_op_asla()
{
    cpu_modify_flag(carry_bp, cpu.A & 0x80);
    cpu.A <<= 1;
    cpu_update_zn_flags(cpu.A);
}
void cpu_op_asl()
{
    cpu_modify_flag(carry_bp, op_value & 0x80);
    op_value <<= 1;
    op_value &= 0xFF;
    cpu_update_zn_flags(op_value);
    memory_writeb(op_address, op_value);
}
void cpu_op_lsra()
{
    int value = cpu.A >> 1;
    cpu_modify_flag(carry_bp, cpu.A & 0x01);
    cpu.A = value & 0xFF;
    cpu_update_zn_flags(value);
}
void cpu_op_lsr()
{
    cpu_modify_flag(carry_bp, op_value & 0x01);
    op_value >>= 1;
    op_value &= 0xFF;
    memory_writeb(op_address, op_value);
    cpu_update_zn_flags(op_value);
}

void cpu_op_rola()
{
    int value = cpu.A << 1;
    value |= cpu_flag_set(carry_bp) ? 1 : 0;
    cpu_modify_flag(carry_bp, value > 0xFF);
    cpu.A = value & 0xFF;
    cpu_update_zn_flags(cpu.A);
}
void cpu_op_rol()
{
    op_value <<= 1;
    op_value |= cpu_flag_set(carry_bp) ? 1 : 0;
    cpu_modify_flag(carry_bp, op_value > 0xFF);
    op_value &= 0xFF;
    memory_writeb(op_address, op_value);
    cpu_update_zn_flags(op_value);
}
void cpu_op_rora()
{
    unsigned char carry = cpu_flag_set(carry_bp);
    cpu_modify_flag(carry_bp, cpu.A & 0x01);
    cpu.A = (cpu.A >> 1) | (carry << 7);
    cpu_modify_flag(zero_bp, cpu.A == 0);
    cpu_modify_flag(negative_bp, !!carry);
}
void cpu_op_ror()
{
    unsigned char carry = cpu_flag_set(carry_bp);
    cpu_modify_flag(carry_bp, op_value & 0x01);
    op_value = ((op_value >> 1) | (carry << 7)) & 0xFF;
    cpu_modify_flag(zero_bp, op_value == 0);
    cpu_modify_flag(negative_bp, !!carry);
    memory_writeb(op_address, op_value);
}

// Loading

void cpu_op_lda() { cpu_update_zn_flags(cpu.A = op_value); }
void cpu_op_ldx() { cpu_update_zn_flags(cpu.X = op_value); }
void cpu_op_ldy() { cpu_update_zn_flags(cpu.Y = op_value); }

// Storing

void cpu_op_sta() { memory_writeb(op_address, cpu.A); }
void cpu_op_stx() { memory_writeb(op_address, cpu.X); }
void cpu_op_sty() { memory_writeb(op_address, cpu.Y); }

// Transfering

void cpu_op_tax() { cpu_update_zn_flags(cpu.X = cpu.A);  }
void cpu_op_txa() { cpu_update_zn_flags(cpu.A = cpu.X);  }
void cpu_op_tay() { cpu_update_zn_flags(cpu.Y = cpu.A);  }
void cpu_op_tya() { cpu_update_zn_flags(cpu.A = cpu.Y);  }
void cpu_op_tsx() { cpu_update_zn_flags(cpu.X = cpu.SP); }
void cpu_op_txs() { cpu.SP = cpu.X; }

// Branching Positive

void cpu_op_bcs() { cpu_branch(cpu_flag_set(carry_bp));     }
void cpu_op_beq() { cpu_branch(cpu_flag_set(zero_bp));      }
void cpu_op_bmi() { cpu_branch(cpu_flag_set(negative_bp));  }
void cpu_op_bvs() { cpu_branch(cpu_flag_set(overflow_bp));  }

// Branching Negative

void cpu_op_bne() { cpu_branch(!cpu_flag_set(zero_bp));     }
void cpu_op_bcc() { cpu_branch(!cpu_flag_set(carry_bp));    }
void cpu_op_bpl() { cpu_branch(!cpu_flag_set(negative_bp)); }
void cpu_op_bvc() { cpu_branch(!cpu_flag_set(overflow_bp)); }

// Jumping

void cpu_op_jmp() { cpu.PC = op_address; }

// Subroutines

void cpu_op_jsr() { cpu_stack_pushw(cpu.PC - 1); cpu.PC = op_address; }
void cpu_op_rts() { cpu.PC = cpu_stack_popw() + 1; }

// Interruptions

void cpu_op_brk() { cpu_stack_pushw(cpu.PC - 1); cpu_stack_pushb(cpu.P); cpu.P |= unused_flag | break_flag; cpu.PC = cpu_nmi_interrupt_address(); }
void cpu_op_rti() { cpu.P = cpu_stack_popb() | unused_flag; cpu.PC = cpu_stack_popw(); }

// Flags

void cpu_op_clc() { cpu_unset_flag(carry_bp);     }
void cpu_op_cld() { cpu_unset_flag(decimal_bp);   }
void cpu_op_cli() { cpu_unset_flag(interrupt_bp); }
void cpu_op_clv() { cpu_unset_flag(overflow_bp);  }
void cpu_op_sec() { cpu_set_flag(carry_bp);       }
void cpu_op_sed() { cpu_set_flag(decimal_bp);     }
void cpu_op_sei() { cpu_set_flag(interrupt_bp);   }

// Comparison

void cpu_op_cmp() { cpu_compare(cpu.A); }
void cpu_op_cpx() { cpu_compare(cpu.X); }
void cpu_op_cpy() { cpu_compare(cpu.Y); }

// Increment

void cpu_op_inc() { byte result = op_value + 1; memory_writeb(op_address, result); cpu_update_zn_flags(result); }
void cpu_op_inx() { cpu_update_zn_flags(++cpu.X); }
void cpu_op_iny() { cpu_update_zn_flags(++cpu.Y); }

// Decrement

void cpu_op_dec() { byte result = op_value - 1; memory_writeb(op_address, result); cpu_update_zn_flags(result); }
void cpu_op_dex() { cpu_update_zn_flags(--cpu.X); }
void cpu_op_dey() { cpu_update_zn_flags(--cpu.Y); }

// Stack

void cpu_op_php() { cpu_stack_pushb(cpu.P | 0x30); }
void cpu_op_pha() { cpu_stack_pushb(cpu.A); }
void cpu_op_pla() { cpu.A = cpu_stack_popb(); cpu_update_zn_flags(cpu.A); }
void cpu_op_plp() { cpu.P = (cpu_stack_popb() & 0xEF) | 0x20; }



// Extended Instruction Set

void cpu_op_aso() { cpu_op_asl(); cpu_op_ora(); }
void cpu_op_axa() { memory_writeb(op_address, cpu.A & cpu.X & (op_address >> 8)); }
void cpu_op_axs() { memory_writeb(op_address, cpu.A & cpu.X); }
void cpu_op_dcm()
{
    op_value--;
    op_value &= 0xFF;
    memory_writeb(op_address, op_value);
    cpu_op_cmp();
}
void cpu_op_ins()
{
    op_value = (op_value + 1) & 0xFF;
    memory_writeb(op_address, op_value);
    cpu_op_sbc();
}
void cpu_op_lax() { cpu_update_zn_flags(cpu.A = cpu.X = op_value); }
void cpu_op_lse() { cpu_op_lsr(); cpu_op_eor(); }
void cpu_op_rla() { cpu_op_rol(); cpu_op_and(); }
void cpu_op_rra() { cpu_op_ror(); cpu_op_adc(); }





// Base 6502 instruction set

#define CPU_OP_BIS(o, c, f, n, a) cpu_op_cycles[0x##o] = c; \
                                  cpu_op_handler[0x##o] = cpu_op_##f; \
                                  cpu_op_name[0x##o] = n; \
                                  cpu_op_address_mode[0x##o] = cpu_address_##a; \
                                  cpu_op_in_base_instruction_set[0x##o] = true;

// Not implemented instructions

#define CPU_OP_NII(o, a) cpu_op_cycles[0x##o] = 1; \
                         cpu_op_handler[0x##o] = ____FE____; \
                         cpu_op_name[0x##o] = "NOP"; \
                         cpu_op_address_mode[0x##o] = cpu_address_##a; \
                         cpu_op_in_base_instruction_set[0x##o] = false;

// Extended instruction set found in other CPUs and implemented for compatibility

#define CPU_OP_EIS(o, c, f, n, a) cpu_op_cycles[0x##o] = c; \
                                  cpu_op_handler[0x##o] = cpu_op_##f; \
                                  cpu_op_name[0x##o] = n; \
                                  cpu_op_address_mode[0x##o] = cpu_address_##a; \
                                  cpu_op_in_base_instruction_set[0x##o] = false;



// CPU Lifecycle

void cpu_init()
{
    CPU_OP_BIS(00, 7, brk, "BRK", implied)
    CPU_OP_BIS(01, 6, ora, "ORA", indirect_x)
    CPU_OP_BIS(05, 3, ora, "ORA", zero_page)
    CPU_OP_BIS(06, 5, asl, "ASL", zero_page)
    CPU_OP_BIS(08, 3, php, "PHP", implied)
    CPU_OP_BIS(09, 2, ora, "ORA", immediate)
    CPU_OP_BIS(0A, 2, asla,"ASL", implied)
    CPU_OP_BIS(0D, 4, ora, "ORA", absolute)
    CPU_OP_BIS(0E, 6, asl, "ASL", absolute)
    CPU_OP_BIS(10, 2, bpl, "BPL", relative)
    CPU_OP_BIS(11, 5, ora, "ORA", indirect_y)
    CPU_OP_BIS(15, 4, ora, "ORA", zero_page_x)
    CPU_OP_BIS(16, 6, asl, "ASL", zero_page_x)
    CPU_OP_BIS(18, 2, clc, "CLC", implied)
    CPU_OP_BIS(19, 4, ora, "ORA", absolute_y)
    CPU_OP_BIS(1D, 4, ora, "ORA", absolute_x)
    CPU_OP_BIS(1E, 7, asl, "ASL", absolute_x)
    CPU_OP_BIS(20, 6, jsr, "JSR", absolute)
    CPU_OP_BIS(21, 6, and, "AND", indirect_x)
    CPU_OP_BIS(24, 3, bit, "BIT", zero_page)
    CPU_OP_BIS(25, 3, and, "AND", zero_page)
    CPU_OP_BIS(26, 5, rol, "ROL", zero_page)
    CPU_OP_BIS(28, 4, plp, "PLP", implied)
    CPU_OP_BIS(29, 2, and, "AND", immediate)
    CPU_OP_BIS(2A, 2, rola,"ROL", implied)
    CPU_OP_BIS(2C, 4, bit, "BIT", absolute)
    CPU_OP_BIS(2D, 2, and, "AND", absolute)
    CPU_OP_BIS(2E, 6, rol, "ROL", absolute)
    CPU_OP_BIS(30, 2, bmi, "BMI", relative)
    CPU_OP_BIS(31, 5, and, "AND", indirect_y)
    CPU_OP_BIS(35, 4, and, "AND", zero_page_x)
    CPU_OP_BIS(36, 6, rol, "ROL", zero_page_x)
    CPU_OP_BIS(38, 2, sec, "SEC", implied)
    CPU_OP_BIS(39, 4, and, "AND", absolute_y)
    CPU_OP_BIS(3D, 4, and, "AND", absolute_x)
    CPU_OP_BIS(3E, 7, rol, "ROL", absolute_x)
    CPU_OP_BIS(40, 6, rti, "RTI", implied)
    CPU_OP_BIS(41, 6, eor, "EOR", indirect_x)
    CPU_OP_BIS(45, 3, eor, "EOR", zero_page)
    CPU_OP_BIS(46, 5, lsr, "LSR", zero_page)
    CPU_OP_BIS(48, 3, pha, "PHA", implied)
    CPU_OP_BIS(49, 2, eor, "EOR", immediate)
    CPU_OP_BIS(4A, 2, lsra,"LSR", implied)
    CPU_OP_BIS(4C, 3, jmp, "JMP", absolute)
    CPU_OP_BIS(4D, 4, eor, "EOR", absolute)
    CPU_OP_BIS(4E, 6, lsr, "LSR", absolute)
    CPU_OP_BIS(50, 2, bvc, "BVC", relative)
    CPU_OP_BIS(51, 5, eor, "EOR", indirect_y)
    CPU_OP_BIS(55, 4, eor, "EOR", zero_page_x)
    CPU_OP_BIS(56, 6, lsr, "LSR", zero_page_x)
    CPU_OP_BIS(58, 2, cli, "CLI", implied)
    CPU_OP_BIS(59, 4, eor, "EOR", absolute_y)
    CPU_OP_BIS(5D, 4, eor, "EOR", absolute_x)
    CPU_OP_BIS(5E, 7, lsr, "LSR", absolute_x)
    CPU_OP_BIS(60, 6, rts, "RTS", implied)
    CPU_OP_BIS(61, 6, adc, "ADC", indirect_x)
    CPU_OP_BIS(65, 3, adc, "ADC", zero_page)
    CPU_OP_BIS(66, 5, ror, "ROR", zero_page)
    CPU_OP_BIS(68, 4, pla, "PLA", implied)
    CPU_OP_BIS(69, 2, adc, "ADC", immediate)
    CPU_OP_BIS(6A, 2, rora,"ROR", implied)
    CPU_OP_BIS(6C, 5, jmp, "JMP", indirect)
    CPU_OP_BIS(6D, 4, adc, "ADC", absolute)
    CPU_OP_BIS(6E, 6, ror, "ROR", absolute)
    CPU_OP_BIS(70, 2, bvs, "BVS", relative)
    CPU_OP_BIS(71, 5, adc, "ADC", indirect_y)
    CPU_OP_BIS(75, 4, adc, "ADC", zero_page_x)
    CPU_OP_BIS(76, 6, ror, "ROR", zero_page_x)
    CPU_OP_BIS(78, 2, sei, "SEI", implied)
    CPU_OP_BIS(79, 4, adc, "ADC", absolute_y)
    CPU_OP_BIS(7D, 4, adc, "ADC", absolute_x)
    CPU_OP_BIS(7E, 7, ror, "ROR", absolute_x)
    CPU_OP_BIS(81, 6, sta, "STA", indirect_x)
    CPU_OP_BIS(84, 3, sty, "STY", zero_page)
    CPU_OP_BIS(85, 3, sta, "STA", zero_page)
    CPU_OP_BIS(86, 3, stx, "STX", zero_page)
    CPU_OP_BIS(88, 2, dey, "DEY", implied)
    CPU_OP_BIS(8A, 2, txa, "TXA", implied)
    CPU_OP_BIS(8C, 4, sty, "STY", absolute)
    CPU_OP_BIS(8D, 4, sta, "STA", absolute)
    CPU_OP_BIS(8E, 4, stx, "STX", absolute)
    CPU_OP_BIS(90, 2, bcc, "BCC", relative)
    CPU_OP_BIS(91, 6, sta, "STA", indirect_y)
    CPU_OP_BIS(94, 4, sty, "STY", zero_page_x)
    CPU_OP_BIS(95, 4, sta, "STA", zero_page_x)
    CPU_OP_BIS(96, 4, stx, "STX", zero_page_y)
    CPU_OP_BIS(98, 2, tya, "TYA", implied)
    CPU_OP_BIS(99, 5, sta, "STA", absolute_y)
    CPU_OP_BIS(9A, 2, txs, "TXS", implied)
    CPU_OP_BIS(9D, 5, sta, "STA", absolute_x)
    CPU_OP_BIS(A0, 2, ldy, "LDY", immediate)
    CPU_OP_BIS(A1, 6, lda, "LDA", indirect_x)
    CPU_OP_BIS(A2, 2, ldx, "LDX", immediate)
    CPU_OP_BIS(A4, 3, ldy, "LDY", zero_page)
    CPU_OP_BIS(A5, 3, lda, "LDA", zero_page)
    CPU_OP_BIS(A6, 3, ldx, "LDX", zero_page)
    CPU_OP_BIS(A8, 2, tay, "TAY", implied)
    CPU_OP_BIS(A9, 2, lda, "LDA", immediate)
    CPU_OP_BIS(AA, 2, tax, "TAX", implied)
    CPU_OP_BIS(AC, 4, ldy, "LDY", absolute)
    CPU_OP_BIS(AD, 4, lda, "LDA", absolute)
    CPU_OP_BIS(AE, 4, ldx, "LDX", absolute)
    CPU_OP_BIS(B0, 2, bcs, "BCS", relative)
    CPU_OP_BIS(B1, 5, lda, "LDA", indirect_y)
    CPU_OP_BIS(B4, 4, ldy, "LDY", zero_page_x)
    CPU_OP_BIS(B5, 4, lda, "LDA", zero_page_x)
    CPU_OP_BIS(B6, 4, ldx, "LDX", zero_page_y)
    CPU_OP_BIS(B8, 2, clv, "CLV", implied)
    CPU_OP_BIS(B9, 4, lda, "LDA", absolute_y)
    CPU_OP_BIS(BA, 2, tsx, "TSX", implied)
    CPU_OP_BIS(BC, 4, ldy, "LDY", absolute_x)
    CPU_OP_BIS(BD, 4, lda, "LDA", absolute_x)
    CPU_OP_BIS(BE, 4, ldx, "LDX", absolute_y)
    CPU_OP_BIS(C0, 2, cpy, "CPY", immediate)
    CPU_OP_BIS(C1, 6, cmp, "CMP", indirect_x)
    CPU_OP_BIS(C4, 3, cpy, "CPY", zero_page)
    CPU_OP_BIS(C5, 3, cmp, "CMP", zero_page)
    CPU_OP_BIS(C6, 5, dec, "DEC", zero_page)
    CPU_OP_BIS(C8, 2, iny, "INY", implied)
    CPU_OP_BIS(C9, 2, cmp, "CMP", immediate)
    CPU_OP_BIS(CA, 2, dex, "DEX", implied)
    CPU_OP_BIS(CC, 4, cpy, "CPY", absolute)
    CPU_OP_BIS(CD, 4, cmp, "CMP", absolute)
    CPU_OP_BIS(CE, 6, dec, "DEC", absolute)
    CPU_OP_BIS(D0, 2, bne, "BNE", relative)
    CPU_OP_BIS(D1, 5, cmp, "CMP", indirect_y)
    CPU_OP_BIS(D5, 4, cmp, "CMP", zero_page_x)
    CPU_OP_BIS(D6, 6, dec, "DEC", zero_page_x)
    CPU_OP_BIS(D8, 2, cld, "CLD", implied)
    CPU_OP_BIS(D9, 4, cmp, "CMP", absolute_y)
    CPU_OP_BIS(DD, 4, cmp, "CMP", absolute_x)
    CPU_OP_BIS(DE, 7, dec, "DEC", absolute_x)
    CPU_OP_BIS(E0, 2, cpx, "CPX", immediate)
    CPU_OP_BIS(E1, 6, sbc, "SBC", indirect_x)
    CPU_OP_BIS(E4, 3, cpx, "CPX", zero_page)
    CPU_OP_BIS(E5, 3, sbc, "SBC", zero_page)
    CPU_OP_BIS(E6, 5, inc, "INC", zero_page)
    CPU_OP_BIS(E8, 2, inx, "INX", implied)
    CPU_OP_BIS(E9, 2, sbc, "SBC", immediate)
    CPU_OP_BIS(EA, 2, nop, "NOP", implied)
    CPU_OP_BIS(EC, 4, cpx, "CPX", absolute)
    CPU_OP_BIS(ED, 4, sbc, "SBC", absolute)
    CPU_OP_BIS(EE, 6, inc, "INC", absolute)
    CPU_OP_BIS(F0, 2, beq, "BEQ", relative)
    CPU_OP_BIS(F1, 5, sbc, "SBC", indirect_y)
    CPU_OP_BIS(F5, 4, sbc, "SBC", zero_page_x)
    CPU_OP_BIS(F6, 6, inc, "INC", zero_page_x)
    CPU_OP_BIS(F8, 2, sed, "SED", implied)
    CPU_OP_BIS(F9, 4, sbc, "SBC", absolute_y)
    CPU_OP_BIS(FD, 4, sbc, "SBC", absolute_x)
    CPU_OP_BIS(FE, 7, inc, "INC", absolute_x)

    CPU_OP_EIS(03, 8, aso, "SLO", indirect_x)
    CPU_OP_EIS(07, 5, aso, "SLO", zero_page)
    CPU_OP_EIS(0F, 6, aso, "SLO", absolute)
    CPU_OP_EIS(13, 8, aso, "SLO", indirect_y)
    CPU_OP_EIS(17, 6, aso, "SLO", zero_page_x)
    CPU_OP_EIS(1B, 7, aso, "SLO", absolute_y)
    CPU_OP_EIS(1F, 7, aso, "SLO", absolute_x)
    CPU_OP_EIS(23, 8, rla, "RLA", indirect_x)
    CPU_OP_EIS(27, 5, rla, "RLA", zero_page)
    CPU_OP_EIS(2F, 6, rla, "RLA", absolute)
    CPU_OP_EIS(33, 8, rla, "RLA", indirect_y)
    CPU_OP_EIS(37, 6, rla, "RLA", zero_page_x)
    CPU_OP_EIS(3B, 7, rla, "RLA", absolute_y)
    CPU_OP_EIS(3F, 7, rla, "RLA", absolute_x)
    CPU_OP_EIS(43, 8, lse, "SRE", indirect_x)
    CPU_OP_EIS(47, 5, lse, "SRE", zero_page)
    CPU_OP_EIS(4F, 6, lse, "SRE", absolute)
    CPU_OP_EIS(53, 8, lse, "SRE", indirect_y)
    CPU_OP_EIS(57, 6, lse, "SRE", zero_page_x)
    CPU_OP_EIS(5B, 7, lse, "SRE", absolute_y)
    CPU_OP_EIS(5F, 7, lse, "SRE", absolute_x)
    CPU_OP_EIS(63, 8, rra, "RRA", indirect_x)
    CPU_OP_EIS(67, 5, rra, "RRA", zero_page)
    CPU_OP_EIS(6F, 6, rra, "RRA", absolute)
    CPU_OP_EIS(73, 8, rra, "RRA", indirect_y)
    CPU_OP_EIS(77, 6, rra, "RRA", zero_page_x)
    CPU_OP_EIS(7B, 7, rra, "RRA", absolute_y)
    CPU_OP_EIS(7F, 7, rra, "RRA", absolute_x)
    CPU_OP_EIS(83, 6, axs, "SAX", indirect_x)
    CPU_OP_EIS(87, 3, axs, "SAX", zero_page)
    CPU_OP_EIS(8F, 4, axs, "SAX", absolute)
    CPU_OP_EIS(93, 6, axa, "SAX", indirect_y)
    CPU_OP_EIS(97, 4, axs, "SAX", zero_page_y)
    CPU_OP_EIS(9F, 5, axa, "SAX", absolute_y)
    CPU_OP_EIS(A3, 6, lax, "LAX", indirect_x)
    CPU_OP_EIS(A7, 3, lax, "LAX", zero_page)
    CPU_OP_EIS(AF, 4, lax, "LAX", absolute)
    CPU_OP_EIS(B3, 5, lax, "LAX", indirect_y)
    CPU_OP_EIS(B7, 4, lax, "LAX", zero_page_y)
    CPU_OP_EIS(BF, 4, lax, "LAX", absolute_y)
    CPU_OP_EIS(C3, 8, dcm, "DCP", indirect_x)
    CPU_OP_EIS(C7, 5, dcm, "DCP", zero_page)
    CPU_OP_EIS(CF, 6, dcm, "DCP", absolute)
    CPU_OP_EIS(D3, 8, dcm, "DCP", indirect_y)
    CPU_OP_EIS(D7, 6, dcm, "DCP", zero_page_x)
    CPU_OP_EIS(DB, 7, dcm, "DCP", absolute_y)
    CPU_OP_EIS(DF, 7, dcm, "DCP", absolute_x)
    CPU_OP_EIS(E3, 8, ins, "ISB", indirect_x)
    CPU_OP_EIS(E7, 5, ins, "ISB", zero_page)
    CPU_OP_EIS(EB, 2, sbc, "SBC", immediate)
    CPU_OP_EIS(EF, 6, ins, "ISB", absolute)
    CPU_OP_EIS(F3, 8, ins, "ISB", indirect_y)
    CPU_OP_EIS(F7, 6, ins, "ISB", zero_page_x)
    CPU_OP_EIS(FB, 7, ins, "ISB", absolute_y)
    CPU_OP_EIS(FF, 7, ins, "ISB", absolute_x)

    CPU_OP_NII(04, zero_page)
    CPU_OP_NII(0C, absolute)
    CPU_OP_NII(14, zero_page_x)
    CPU_OP_NII(1A, implied)
    CPU_OP_NII(1C, absolute_x)
    CPU_OP_NII(34, zero_page_x)
    CPU_OP_NII(3A, implied)
    CPU_OP_NII(3C, absolute_x)
    CPU_OP_NII(44, zero_page)
    CPU_OP_NII(54, zero_page_x)
    CPU_OP_NII(5A, implied)
    CPU_OP_NII(5C, absolute_x)
    CPU_OP_NII(64, zero_page)
    CPU_OP_NII(74, zero_page_x)
    CPU_OP_NII(7A, implied)
    CPU_OP_NII(7C, absolute_x)
    CPU_OP_NII(80, immediate)
    CPU_OP_NII(D4, zero_page_x)
    CPU_OP_NII(DA, implied)
    CPU_OP_NII(DC, absolute_x)
    CPU_OP_NII(F4, zero_page_x)
    CPU_OP_NII(FA, implied)
    CPU_OP_NII(FC, absolute_x)

    cpu.P = 0x24;
    cpu.SP = 0x00;
    cpu.A = cpu.X = cpu.Y = 0;
}

void cpu_reset()
{
    cpu.PC = cpu_reset_interrupt_address();
    cpu.SP -= 3;
    cpu.P |= interrupt_flag;
}

void cpu_interrupt()
{
    // if (ppu_in_vblank()) {
        if (ppu_generates_nmi()) {
            cpu.P |= interrupt_flag;
            cpu_unset_flag(unused_bp);
            cpu_stack_pushw(cpu.PC);
            cpu_stack_pushb(cpu.P);
            cpu.PC = cpu_nmi_interrupt_address();
        }
    // }
}

inline unsigned long cpu_clock()
{
    return cpu_cycles;
}

void cpu_run(long cycles)
{
    while (cycles > 0) {
        op_code = memory_readb(cpu.PC++);
        if (cpu_op_address_mode[op_code] == 0) {
        }
        else {
            cpu_op_address_mode[op_code]();
            cpu_op_handler[op_code]();
        }
        cycles -= cpu_op_cycles[op_code] + op_cycles;
        cpu_cycles -= cpu_op_cycles[op_code] + op_cycles;
        op_cycles = 0;
    }
}

