// The console's address bus.
//
// The CPU and the PPU each see their own address space, and the cartridge sits
// between them: the CPU's $4020-$FFFF is the cartridge's ROM, and the PPU's
// pattern tables are the cartridge's character memory. This file wires the three
// together and owns the two things that belong to neither: work RAM and the
// controller shift registers.
//
// The mirroring here is not an implementation detail to be tidied up. Work RAM is
// 2 KiB but answers across 8 KiB of the CPU's map, and the PPU registers repeat
// every eight bytes across 32. A program that writes $0800 and reads $0000 must
// see the same byte, and games rely on it.

///|
/// The whole console: CPU, PPU, cartridge and the memories they share.
pub(all) struct Console {
  /// The processor.
  mut cpu : Cpu
  /// The picture unit.
  mut ppu : Ppu
  /// The cartridge, which answers $4020 and up for the CPU and supplies the
  /// PPU's pattern tables.
  mut cart : Cart
  /// 2 KiB of work RAM, mirrored across $0000-$1FFF.
  ram : FixedArray[Byte]
  /// Controller state for both ports; the low eight bits are the buttons, one
  /// per `Button::shift_index`.
  mut controllers : (UInt32, UInt32)
  /// How many bits each controller has shifted out since the strobe went low.
  mut controller_shift : (Int32, Int32)
  /// True while the CPU is stalled waiting for sprite DMA.
  mut dma_cycles : Int64
  /// The audio unit. It does not synthesize sound here, but it owns the frame
  /// counter, which is a CPU-visible timer.
  mut apu : Apu
  /// True when the APU's frame counter has raised an interrupt that the CPU has
  /// not taken yet.
  mut irq_pending : Bool
}

///|
/// A console with a freshly reset CPU and PPU and the given cartridge.
pub fn Console::new(cart : Cart) -> Console {
  let cpu = Cpu::new()
  let ppu = Ppu::new()
  ppu.mirroring = cart.current_mirroring()
  // The CHR array is shared by reference: a CHR-RAM cartridge writes through the
  // PPU's data port and the cartridge must see the same bytes when it answers a
  // pattern fetch.
  let chr = cart.chr
  ppu.chr = chr
  ppu.chr_ram = cart.chr_ram
  ppu.chr_bank = cart.chr_bank
  ppu.chr_pages = cart.chr_pages()
  {
    cpu,
    ppu,
    cart,
    ram: FixedArray::make(0x800, b'\x00'),
    controllers: (0U, 0U),
    controller_shift: (0, 0),
    dma_cycles: 0L,
    apu: Apu::new(),
    irq_pending: false,
  }.boot()
}

///|
/// Boots a freshly built console the way the hardware does.
///
/// `Cpu::new` leaves the program counter at zero, which is fine for a unit test
/// that sets it itself and wrong for every other caller: zero is the first byte
/// of work RAM, so a console that is never reset starts executing a zero-filled
/// page of RAM and runs whatever the zeros decode to. Booting here rather than
/// asking callers to remember `reset()` makes the common path correct.
fn Console::boot(self : Console) -> Console {
  self.reset()
  self
}

///|
/// Reads a byte from the CPU's address space.
///
/// Each region is handled explicitly rather than by subtraction from a base,
/// because the regions do not share a stride: work RAM repeats every 2 KiB while
/// the PPU registers repeat every 8 bytes, and a single formula would have to
/// special-case one of them anyway.
pub fn Console::read(self : Console, address : UInt32) -> UInt32 {
  let at = address & 0xFFFFU
  let offset = at.reinterpret_as_int()
  if offset < 0x2000 {
    self.ram[offset % 0x800].to_int().reinterpret_as_uint() & 0xFF
  } else if offset < 0x4000 {
    self.ppu.read_register((offset - 0x2000).reinterpret_as_uint() & 0x07U)
  } else if offset >= 0x4000 && offset <= 0x4013 {
    // The channel registers read back as open bus on the real hardware, which is
    // effectively zero here.
    0U
  } else if offset == 0x4015 {
    self.apu.read_status()
  } else if offset == 0x4016 {
    self.read_controller(0)
  } else if offset == 0x4017 {
    self.read_controller(1)
  } else if offset >= 0x4020 {
    self.cart_read(at)
  } else {
    0U
  }
}

///|
/// Writes a byte to the CPU's address space.
pub fn Console::write(self : Console, address : UInt32, value : UInt32) -> Unit {
  let at = address & 0xFFFFU
  let offset = at.reinterpret_as_int()
  if offset < 0x2000 {
    self.ram[offset % 0x800] = (value & 0xFF).to_byte()
  } else if offset < 0x4000 {
    self.ppu.write_register(
      (offset - 0x2000).reinterpret_as_uint() & 0x07U,
      value,
    )
  } else if offset == 0x4014 {
    // Sprite DMA: the CPU stalls for 513 or 514 cycles while 256 bytes are copied
    // from a page of memory into OAM. The extra cycle occurs when the source page
    // starts on an odd cycle, which this build does not model yet.
    self.sprite_dma(value)
  } else if offset >= 0x4000 && offset <= 0x4013 {
    self.apu.write_register((offset - 0x4000).reinterpret_as_uint(), value)
  } else if offset == 0x4017 {
    self.apu.write_register(0x17U, value)
  } else if offset == 0x4016 {
    self.strobe_controllers()
  } else if offset >= 0x4020 {
    // Program RAM, save RAM and the mapper's own registers share this range. The
    // boards modelled here have no save RAM in this window, so a write either
    // lands in a mapper register or disappears; either way the CPU cannot tell.
    // A mapper write can also change the character bank or the mirroring, so the
    // PPU's cached copies have to be refreshed afterwards.
    if self.cart.write_mapper(at, value) {
      self.ppu.chr_bank = self.cart.chr_bank
      self.ppu.chr_pages = self.cart.chr_pages()
      self.ppu.mirroring = self.cart.current_mirroring()
      // MMC3's scanline counter is armed and disarmed by the same writes that
      // change its banks, so the console has to re-read the line state after
      // every mapper write rather than only after the PPU advances.
      self.irq_pending = self.irq_pending || self.cart.irq_line
    }
  }
}

///|
/// Reads a byte from the cartridge through the mapper's current banking.
///
/// The address decode itself lives in `Cart::read_prg` because which slice of
/// ROM answers is the board's decision, not the console's: a 16 KiB NROM board
/// mirrors one chip into both windows, while a UxROM board puts the last bank in
/// the high window and a switchable one in the low window.
fn Console::cart_read(self : Console, address : UInt32) -> UInt32 {
  self.cart.read_prg(address)
}

///|
/// Copies 256 bytes from a page of memory into OAM and stalls the CPU.
fn Console::sprite_dma(self : Console, page : UInt32) -> Unit {
  let base = page.to_int64() * 256L
  for i in 0..<256 {
    let value = self.read(
      (base + i.to_int64()).to_int().reinterpret_as_uint() & 0xFFFFU,
    )
    self.ppu.oam[i] = (value & 0xFF).to_byte()
  }
  // 513 cycles on an even starting cycle, 514 on an odd one. The parity depends on
  // the CPU's cycle counter, which is what the extra cycle is for.
  let extra = if self.cpu.cycles % 2L == 0L { 513L } else { 514L }
  self.dma_cycles = extra
  self.cpu.stalled = extra
}

///|
/// Latches the controller state while the strobe is held high.
///
/// Writing $4016 with the low bit set holds both controllers' shift registers in a
/// loop; clearing it starts the serial read. Games pulse it high then low once
/// per frame.
fn Console::strobe_controllers(self : Console) -> Unit {
  self.controller_shift = (0, 0)
}

///|
/// Shifts one bit out of a controller.
///
/// After the eighth shift the register returns 1 forever, which is how a game
/// tells a standard pad from a device that sends more bits.
fn Console::read_controller(self : Console, port : Int32) -> UInt32 {
  let state = if port == 0 { self.controllers.0 } else { self.controllers.1 }
  let shifted = if port == 0 {
    self.controller_shift.0
  } else {
    self.controller_shift.1
  }
  let bit = if shifted < 8 { (state >> shifted) & 1U } else { 1U }
  if port == 0 {
    self.controller_shift = (shifted + 1, self.controller_shift.1)
  } else {
    self.controller_shift = (self.controller_shift.0, shifted + 1)
  }
  bit
}

///|
/// Sets the buttons held on a controller.
pub fn Console::set_buttons(
  self : Console,
  port : Int32,
  buttons : Array[Button],
) -> Unit {
  let mut bits = 0U
  for button in buttons {
    bits = bits | (1UL << button.shift_index()).to_uint()
  }
  if port == 0 {
    self.controllers = (bits, self.controllers.1)
  } else {
    self.controllers = (self.controllers.0, bits)
  }
}

///|
/// Runs one instruction, advancing the PPU by the three dots per CPU cycle the
/// hardware uses.
///
/// Returns true when the instruction's final cycle raised the PPU's vblank and the
/// control register has NMI enabled, so the caller can service the interrupt.
pub fn Console::step(self : Console) -> Bool raise NesError {
  let result = self.cpu.step(self)
  let mut cycles = result.cycles
  if self.dma_cycles > 0L {
    cycles = cycles + self.dma_cycles
    self.dma_cycles = 0L
  }
  // The CPU and PPU run at a 3:1 ratio on NTSC, which is why one CPU cycle is
  // three dots and a frame is 29780 cycles.
  let mut started_vblank = false
  for _ in 0..<(cycles * 3L).to_int() {
    if self.ppu.tick() {
      started_vblank = true
    }
  }
  // The board's scanline counter watches the same line boundaries the PPU just
  // crossed, so edges are delivered after the dots rather than polled from the
  // mapper every cycle.
  let mut edges = self.ppu.scanlines
  self.ppu.scanlines = 0U
  while edges > 0U {
    self.cart.clock_scanline()
    if self.cart.irq_line {
      self.irq_pending = true
    }
    edges = edges - 1U
  }
  // The APU counts CPU cycles, not dots, so it advances once per instruction
  // rather than in the loop above.
  let frame_interrupt = self.apu.tick(cycles)
  if frame_interrupt {
    self.irq_pending = true
  }
  started_vblank && self.ppu.ctrl.nmi_enabled
}

///|
/// Resets the console as the hardware does: the CPU reads its program counter from
/// the vector at $FFFC and the stack pointer starts at $FD.
pub fn Console::reset(self : Console) -> Unit {
  self.cpu = Cpu::new()
  let low = self.cart_read(0xFFFCU)
  let high = self.cart_read(0xFFFDU)
  self.cpu.pc = low | (high << 8)
  self.cpu.sp = 0xFDU
  self.cpu.p = FLAG_UNUSED | FLAG_INTERRUPT
}

///|
/// Services an NMI by pushing the return address and status and vectoring through
/// $FFFA, which is what the hardware does when the PPU raises the line.
pub fn Console::nmi(self : Console) -> Unit {
  let ret = self.cpu.pc
  self.cpu.push(self, (ret >> 8) & 0xFF)
  self.cpu.push(self, ret & 0xFF)
  // The pushed status has the break bit clear, which distinguishes an NMI from a
  // BRK when the handler inspects the stack.
  self.cpu.push(self, (self.cpu.p | FLAG_UNUSED) & (FLAG_BREAK ^ 0xFFFF_FFFFU))
  self.cpu.set_flag(Interrupt, true)
  let low = self.cart_read(0xFFFAU)
  let high = self.cart_read(0xFFFBU)
  self.cpu.pc = low | (high << 8)
}

///|
/// Takes a maskable interrupt through the vector at `$FFFE`.
///
/// The difference from `NMI` is more than the vector. Panning the pushed status:
/// the hardware clears the break bit for both, but a maskable interrupt is also
/// suppressed while the interrupt-disable flag is set, and an NMI is not. That
/// distinction is what makes `SEI` a usable critical section.
pub fn Console::irq(self : Console) -> Unit {
  if self.cpu.flag(Interrupt) {
    return
  }
  let ret = self.cpu.pc
  self.cpu.push(self, (ret >> 8) & 0xFF)
  self.cpu.push(self, ret & 0xFF)
  self.cpu.push(self, (self.cpu.p | FLAG_UNUSED) & (FLAG_BREAK ^ 0xFFFF_FFFFU))
  self.cpu.set_flag(Interrupt, true)
  let low = self.cart_read(0xFFFEU)
  let high = self.cart_read(0xFFFFU)
  self.cpu.pc = low | (high << 8)
}

///|
/// Runs until the PPU completes a frame, returning the number of CPU instructions
/// executed.
///
/// Bounded so that a ROM stuck in a loop cannot hang a test: the limit is a few
/// frames' worth of instructions, and hitting it returns the count rather than
/// spinning forever.
pub fn Console::run_frame(
  self : Console,
  limit : Int32,
) -> Int32 raise NesError {
  self.ppu.frame_complete = false
  let mut executed = 0
  while !self.ppu.frame_complete && executed < limit {
    let interrupt = self.step()
    if interrupt {
      self.nmi()
    }
    // The frame counter's interrupt is maskable, so it is checked after the
    // instruction rather than delivered as part of it: a program that has run
    // SEI must not be interrupted no matter how overdue the counter is.
    if self.irq_pending {
      self.irq_pending = false
      self.irq()
    }
    executed = executed + 1
  }
  executed
}

///|
/// Reaches the console's bus through the trait the CPU is written against.
pub impl Bus for Console with fn read(self, address) {
  self.read(address)
}

///|
/// Writes through the console's bus.
pub impl Bus for Console with fn write(self, address, value) {
  self.write(address, value)
}