// The 6502 CPU as it appears inside the NES's 2A03.
//
// The design follows one rule that makes the golden-log tests meaningful: an
// instruction is a single `step` that consumes exactly as many cycles as the
// silicon, and the cycle count is computed rather than approximated. Branch and
// page-crossing penalties are therefore modelled explicitly, because
// `nestest.log` records the cumulative cycle count on every line and a single
// missed penalty desynchronises the remaining eight thousand lines.
//
// The 2A03 differs from a stock 6502 in one way that matters: decimal mode has
// no BCD adder behind it, so the decimal flag only suppresses the carry-based
// correction rather than changing the result.

///|
/// A 6502 register file plus the memory it addresses.
///
/// Memory is the console's 64 KiB bus, not the CPU's own store: reads and writes
/// go through `Bus` so that the PPU, APU and cartridge can intercept the
/// addresses the hardware routes to them.
pub(all) struct Cpu {
  /// Accumulator.
  mut a : UInt32
  /// Index register X.
  mut x : UInt32
  /// Index register Y.
  mut y : UInt32
  /// Stack pointer; the stack lives at $0100 + this value.
  mut sp : UInt32
  /// Program counter.
  mut pc : UInt32
  /// Packed status register. Bits are read through `Cpu::flag`.
  mut p : UInt32
  /// Total cycles executed since reset. This is the number `nestest.log`
  /// records, so it counts CPU cycles only, not PPU dots.
  mut cycles : Int64
  /// Cycles of DMA still owed to the bus; the CPU does not advance while this
  /// is positive.
  mut stalled : Int64
  /// The memory location the last instruction read or wrote, for the trace
  /// column `nestest.log` renders as `= XX`.
  mut last_access : MemoryAccess?
}

///|
/// A read/write window the CPU can reach.
///
/// The real machine has no single flat memory; the CPU addresses cartridge ROM,
/// work RAM, and memory-mapped registers that all answer through the bus. Keeping
/// that indirection here is what lets the CPU be tested with a plain RAM array
/// and then dropped into the console unchanged.
pub(open) trait Bus {
  /// Reads one byte, returning 0 for addresses nothing drives.
  fn read(self : Self, address : UInt32) -> UInt32
  /// Writes one byte, ignoring addresses that are read-only.
  fn write(self : Self, address : UInt32, value : UInt32) -> Unit
}

///|
/// A flat 64 KiB of RAM, used by tests and by the CPU-only trace runner.
///
/// It is deliberately not the console's bus: `nestest` executes with the
/// cartridge mapped at $C000 and plain RAM everywhere else, which is exactly
/// what this provides.
pub(all) struct FlatRam {
  bytes : FixedArray[Byte]
}

///|
/// Allocates 64 KiB of zeroed RAM.
pub fn FlatRam::new() -> FlatRam {
  FlatRam::{ bytes: FixedArray::make(0x10000, b'\x00'), }
}

///|
/// Copies an image into RAM starting at `base`.
///
/// Used to place a test ROM's code where the real console's mapper would have
/// put it, without pulling in a cartridge.
pub fn FlatRam::load(self : FlatRam, base : UInt32, image : Bytes) -> Unit {
  for i in 0.. Cpu {
  {
    a: 0,
    x: 0,
    y: 0,
    sp: 0xFD,
    pc: 0,
    p: FLAG_UNUSED | FLAG_INTERRUPT,
    cycles: 0L,
    stalled: 0L,
    last_access: None,
  }
}

///|
/// Reads a status flag.
pub fn Cpu::flag(self : Cpu, flag : Flag) -> Bool {
  (self.p & flag.mask()) != 0
}

///|
/// Sets or clears a status flag.
pub fn Cpu::set_flag(self : Cpu, flag : Flag, value : Bool) -> Unit {
  if value {
    self.p = self.p | flag.mask()
  } else {
    self.p = self.p & (flag.mask() ^ 0xFFFF_FFFFU)
  }
}

///|
/// Sets or clears the zero and negative flags from an 8-bit result.
///
/// Every instruction that produces a value calls this once, which is why the
/// two flags never drift: there is exactly one place that touches them.
pub fn Cpu::set_zn(self : Cpu, value : UInt32) -> Unit {
  self.set_flag(Zero, (value & 0xFF) == 0)
  self.set_flag(Negative, (value & 0x80) != 0)
}

///|
/// The address an interrupt pushes as its return address, which is the program
/// counter plus one for a BRK and the raw program counter for a hardware
/// interrupt.
fn Cpu::push(self : Cpu, bus : &Bus, value : UInt32) -> Unit {
  let at = 0x0100U + self.sp
  bus.write(at, value & 0xFF)
  self.sp = (self.sp - 1) & 0xFF
}

///|
/// Pops a byte from the stack and returns it.
fn Cpu::pop(self : Cpu, bus : &Bus) -> UInt32 {
  self.sp = (self.sp + 1) & 0xFF
  bus.read(0x0100U + self.sp) & 0xFF
}

///|
/// Fetches the byte at the program counter and advances it.
fn Cpu::fetch(self : Cpu, bus : &Bus) -> UInt32 {
  let value = bus.read(self.pc) & 0xFF
  self.pc = (self.pc + 1) & 0xFFFF
  value
}

///|
/// Fetches a little-endian 16-bit operand and advances the program counter by
/// two.
fn Cpu::fetch16(self : Cpu, bus : &Bus) -> UInt32 {
  let lo = self.fetch(bus)
  let hi = self.fetch(bus)
  lo | (hi << 8)
}

///|
/// Reads a 16-bit value from an arbitrary address without incrementing the
/// program counter.
///
/// Only used where an instruction reads a pointer from memory; the 6502 has a
/// famous bug in this path for `JMP ($xxFF)`, reproduced separately.
fn read16(bus : &Bus, address : UInt32) -> UInt32 {
  let lo = bus.read(address & 0xFFFF)
  let hi = bus.read((address + 1) & 0xFFFF)
  lo | (hi << 8)
}

///|
/// The result of one instruction: how many cycles it consumed and whether it
/// touched memory in a way the caller should know about.
pub(all) struct StepResult {
  /// Cycles consumed, including branch and page-crossing penalties.
  cycles : Int64
  /// The opcode that was executed.
  opcode : UInt32
  /// The address of the opcode's first byte, so a trace can print the same
  /// `PC` column the golden log does.
  at : UInt32
  /// Set when the instruction read or wrote a memory location; `address` and
  /// `value` then describe it, which is how `nestest` renders operands such as
  /// `STX $00 = 00`.
  memory : MemoryAccess?
}

///|
/// A memory location an instruction touched, with the value after the access.
pub(all) struct MemoryAccess {
  address : UInt32
  /// The value written, or the value read if the instruction only read.
  value : UInt32
  /// True when this was a write, which the trace renders with `=`.
  write : Bool
}

///|
/// Executes one instruction and returns what it did.
///
/// `bus` is mutable through its trait object: the CPU is the only component that
/// drives the address lines, so everything else observes memory through the
/// effects of this call.
pub fn Cpu::step(self : Cpu, bus : &Bus) -> StepResult raise NesError {
  // A pending DMA stalls the CPU before the next fetch, and the stall is what
  // makes sprite DMA cost 513 cycles rather than 1.
  if self.stalled > 0L {
    let waited = self.stalled
    self.stalled = 0L
    self.cycles = self.cycles + waited
    return { cycles: waited, opcode: 0xFF, at: self.pc, memory: None, }
  }
  let at = self.pc
  let opcode = self.fetch(bus)
  let result = self.execute(bus, opcode, at)
  self.cycles = self.cycles + result.cycles
  result
}

///|
/// Fetches a byte without touching memory, used by the disassembler and the
/// trace runner to render the operand bytes of an instruction that has not
/// executed yet.
pub fn peek(bus : &Bus, address : UInt32) -> UInt32 {
  bus.read(address & 0xFFFF) & 0xFF
}