// Bank switching.
//
// A mapper is not a device the CPU talks to; it is a small piece of logic on the
// board that watches the address bus and decides which ROM slice answers. The
// CPU writes to $8000-$FFFF expecting RAM, and the mapper swallows the write and
// changes its registers instead. Nothing tells the CPU this happened, which is
// why a mapper bug shows up as a game jumping into the wrong bank rather than as
// an error.
//
// Only the low bits of the written value matter, and how many bits matter comes
// from how many banks the board has: a game written for a 16-bank board still
// works on a 4-bank board because the extra bits are simply ignored, which is
// what the masks in each write handler reproduce.

///|
/// Decodes a header's mapper number into a wiring this build implements.
fn mapper_of(number : Int32) -> MapperKind {
  match number {
    0 => Nrom
    1 => Mmc1
    2 => UxRom
    3 => Cnrom
    4 => Mmc3
    _ => Unsupported(number.reinterpret_as_uint())
  }
}

///|
/// Reads a byte from the program ROM through the mapper's current bank.
///
/// `address` is a CPU address in $8000-$FFFF. Every board this build models puts
/// the last program bank at $C000 and switches only the lower window, except
/// that a 32 KiB board has enough ROM for the low window to be its own bank.
fn Cart::read_prg(self : Cart, address : UInt32) -> UInt32 {
  // The offset has to be the distance from the base of whichever 16 KiB window
  // the address falls in, not the address masked to fifteen bits: $C000 masked
  // would give $4000, which is one past the end of the bank it belongs to.
  let base = if address >= 0xC000U { 0xC000U } else { 0x8000U }
  let offset = (address - base).reinterpret_as_int().to_int64()
  let bank_bytes = 0x4000L
  let banks = self.prg.length().to_int64() / bank_bytes
  if banks <= 0L {
    return 0U
  }
  match self.mapper {
    // A board with no banking exposes its whole chip linearly: a 32 KiB NROM
    // fills both windows, and a 16 KiB one repeats in both. The window-relative
    // offset above is only right for banked boards, so the linear offset is
    // rebuilt here from the full address.
    Nrom =>
      self.prg_byte(
        (address & 0x7FFFU).reinterpret_as_int().to_int64() %
        self.prg.length().to_int64(),
      )
    // A UxROM board's low window is switchable and its high window is the last
    // bank, which is where the reset vector lives so that a bank switch cannot
    // move the code the CPU is already running.
    UxRom =>
      if address >= 0xC000U {
        self.prg_byte((banks - 1L) * bank_bytes + offset)
      } else {
        self.prg_byte(self.prg_bank * bank_bytes + offset)
      }
    // CNROM leaves the program ROM unbanked, so it decodes exactly as NROM does.
    Cnrom =>
      self.prg_byte(
        (address & 0x7FFFU).reinterpret_as_int().to_int64() %
        self.prg.length().to_int64(),
      )
    Mmc1 => {
      // Bits 2 and 3 of the control register pick one of four arrangements, and
      // the high window's behaviour is what distinguishes them: in two of them
      // it follows the register, in the other two it is pinned. Treating bit 3
      // as "16 KiB mode" alone loses the "$C000 fixed to the last bank" case
      // and leaves a game running its reset code out of the wrong bank.
      let mode = (self.control >> 2) & 0x03
      let largest = banks - 1L
      let low = self.prg_bank % banks
      let high_window = match mode {
        // 32 KiB: the register's low bit is ignored and the pair fills both
        // windows.
        0U | 1U => (low & 0xFE) + 1L
        // $8000 switched, $C000 fixed to the first bank.
        2U => 0L
        // $8000 switched, $C000 fixed to the last bank. This is the power-on
        // arrangement, which is why reset code can bank-switch itself safely.
        _ => largest
      }
      let low_window = match mode {
        0U | 1U => low & 0xFE
        _ => low
      }
      if address >= 0xC000U {
        self.prg_byte(high_window % banks * bank_bytes + offset)
      } else {
        self.prg_byte(low_window % banks * bank_bytes + offset)
      }
    }
    Mmc3 => {
      // MMC3 divides the upper half of the map into four 8 KiB windows, not two
      // 16 KiB ones, which is why this arm rebuilds its own offset instead of
      // using the 16 KiB one computed above.
      //
      // Two windows follow registers 6 and 7; the other two are pinned to the
      // last two banks. The mode bit decides which pair is which, and it is the
      // reason a game can move its own fixed code: setting the bit swaps $8000
      // with $C000 and $A000 with $E000.
      let small = 0x2000L
      let count = self.prg.length().to_int64() / small
      if count <= 0L {
        return 0U
      }
      let window = ((address - 0x8000U) >> 13).reinterpret_as_int().to_int64()
      let r6 = self.bank_registers[6] % count
      let r7 = self.bank_registers[7] % count
      let last = count - 1L
      let second_to_last = if count >= 2L { count - 2L } else { 0L }
      let bank = if self.prg_mode {
        // Swapped: the low pair is fixed and the high pair follows the
        // registers.
        match window {
          0L => second_to_last
          1L => last
          2L => r6
          _ => r7
        }
      } else {
        match window {
          0L => r6
          1L => r7
          2L => second_to_last
          _ => last
        }
      }
      let within = (address & 0x1FFFU).reinterpret_as_int().to_int64()
      self.prg_byte(bank % count * small + within)
    }
    Unsupported(_) => 0U
  }
}

///|
/// Applies a write the CPU made into cartridge space.
///
/// Returns true when a mapper consumed the write. Returning false means the
/// board has no register there, which for the boards modelled here is the same
/// as the write disappearing into nothing.
fn Cart::write_mapper(self : Cart, address : UInt32, value : UInt32) -> Bool {
  let value = value & 0xFFU
  match self.mapper {
    Nrom => false
    UxRom => {
      // One register, anywhere in $8000-$FFFF. The mask picks the low bits the
      // board actually has; the upper-bit difference is why cheap UxROM boards
      // need 74HC161 counters to bank correctly.
      let banks = self.prg.length().to_int64() / 0x4000L
      if banks > 0L {
        self.prg_bank = (value & 0x0FU).reinterpret_as_int().to_int64() % banks
      }
      true
    }
    Cnrom => {
      let banks = self.chr.length().to_int64() / 0x2000L
      if banks > 0L {
        self.chr_bank = (value & 0x03U).reinterpret_as_int().to_int64() % banks
      }
      true
    }
    Mmc1 => {
      // MMC1 takes its registers one bit at a time, least significant bit first.
      // Five writes of one bit each assemble a byte; the sixth write would start
      // over, and a write with bit 7 set resets the register instead of
      // contributing a bit. Games depend on that reset, because it is the only
      // way to recover from a partial write.
      if (value & 0x80U) != 0 {
        self.shift = 0U
        self.shift_count = 0U
        self.control = self.control | 0x0CU
        return true
      }
      self.shift = self.shift |
        ((value & 0x01U) << self.shift_count.reinterpret_as_int())
      self.shift_count = self.shift_count + 1U
      if self.shift_count == 5U {
        let latched = self.shift & 0x1FU
        self.shift = 0U
        self.shift_count = 0U
        // The destination register is chosen by the top two address bits, which
        // is why a game writes the same address five times in a row.
        let target = (address >> 13) & 0x03U
        match target {
          0U => {
            self.control = latched
            self.mirroring_from_mapper = true
          }
          1U => {
            let banks = self.chr.length().to_int64() / 0x1000L
            if banks > 0L {
              self.chr_bank = (latched & 0x1FU).reinterpret_as_int().to_int64() %
                banks
            }
          }
          2U => {
            let banks = self.chr.length().to_int64() / 0x1000L
            if banks > 0L {
              self.chr_bank = self.chr_bank +
                (latched & 0x1FU).reinterpret_as_int().to_int64() %
                banks *
                0x1000L
            }
          }
          _ => {
            let banks = self.prg.length().to_int64() / 0x4000L
            if banks > 0L {
              self.prg_bank = (latched & 0x0FU).reinterpret_as_int().to_int64() %
                banks
            }
          }
        }
      }
      true
    }
    Mmc3 => {
      // MMC3 is even/odd addressed: each 8 KiB block in $8000-$FFFF holds two
      // registers, one at an even address and one at the odd address next to it.
      // A write's address alone decides which register it reaches, and the same
      // block means different things on reads and writes, so getting this wrong
      // is silent — both are "somewhere in the upper half".
      let even = (address & 0x01U) == 0U
      match (address >> 13) & 0x03U {
        // $8000: even selects the bank register, odd writes it.
        0U =>
          if even {
            self.bank_select = value & 0x07U
            self.prg_mode = (value & 0x40U) != 0
            self.chr_mode = (value & 0x80U) != 0
          } else {
            let index = self.bank_select.reinterpret_as_int()
            self.bank_registers[index] = value.reinterpret_as_int().to_int64()
            // A character bank register holds an even or odd 1 KiB index
            // depending on which of the pair it is, and the mode bit swaps the
            // two halves, so the effective bank is computed on read instead.
            self.chr_bank = self.effective_chr_bank()
          }
        // $A000: even sets the nametable arrangement, odd is the (unused) PRG
        // RAM protect register.
        1U =>
          if even {
            // Four-screen boards ignore this: their extra RAM wins over the
            // header's mirroring bits.
            if !self.four_screen() {
              self.write_mirroring(value & 0x01U)
            }
          }
        // $C000 loads the counter's reload value; $C001 clears the counter
        // without reloading it, which is what gives a program a short first
        // interval rather than a full latch's worth.
        2U =>
          if even {
            self.irq_latch = value
          } else {
            self.irq_counter = 0U
            self.irq_line = false
          }
        // $E000 disarms the counter; $E001 arms it.
        _ =>
          if even {
            self.irq_enabled = false
            self.irq_line = false
          } else {
            self.irq_enabled = true
          }
      }
      true
    }
    Unsupported(_) => false
  }
}

///|
/// The mirroring the cartridge currently asks the PPU for.
///
/// MMC1 can change it at run time, which a game does when it switches between a
/// horizontally scrolling level and a vertically scrolling one. The header's
/// value is a starting point, not a constant.
fn Cart::current_mirroring(self : Cart) -> Mirroring {
  if !self.mirroring_from_mapper {
    return self.mirroring
  }
  // MMC3 keeps its mirroring in a single bit rather than MMC1's two, and four-
  // screen boards ignore the register entirely because their extra RAM is
  // already wired to all four tables.
  match self.mapper {
    Mmc3 => if (self.control & 0x01U) == 0U { SingleLow } else { SingleHigh }
    Mmc1 =>
      match (self.control & 0x03U).reinterpret_as_int() {
        0 => SingleLow
        1 => SingleHigh
        2 => Vertical
        _ => Horizontal
      }
    _ => self.mirroring
  }
}

///|
/// True when the cartridge supplies its own four nametables.
fn Cart::four_screen(self : Cart) -> Bool {
  match self.mirroring {
    FourScreen => true
    _ => false
  }
}

///|
/// Switches between the two single-screen arrangements.
///
/// MMC3 has a single bit for this rather than a full enum: zero means both
/// nametables show the first page, one means both show the second. A game that
/// switches mid-frame to draw a status bar uses it.
fn Cart::write_mirroring(self : Cart, bit : UInt32) -> Unit {
  match (self.mirroring, bit) {
    // The enum is not mutable on `Cart`, so the chosen arrangement is recorded
    // through the control field the console already reads. Keeping one place
    // that answers "what mirroring is active" matters more than which field it
    // is stored in.
    _ => {
      self.mirroring_from_mapper = true
      self.control = bit
    }
  }
}

///|
/// The character bank the current MMC3 registers select for the window at
/// $0000.
///
/// MMC3 splits the 8 KiB character space into two 4 KiB halves, each of which is
/// built from one 2 KiB register and two 1 KiB registers. The mode bit swaps
/// which half is which, so the same registers drive different windows depending
/// on it. The PPU asks for one flat bank number, so this collapses the four
/// registers of the requested half into one.
fn Cart::effective_chr_bank(self : Cart) -> Int64 {
  // The PPU's flat bank covers 8 KiB, but MMC3's finest granularity is 1 KiB, so
  // the real selection has to happen per fetch. What is stored here is the base
  // of the half the PPU's two windows share; `chr_bank_offset` refines it.
  let banks = self.chr.length().to_int64() / 0x2000L
  if banks <= 0L {
    return 0L
  }
  let first = self.bank_registers[if self.chr_mode { 2 } else { 0 }]
  (first & 0xFEU.reinterpret_as_int().to_int64()) % banks
}

///|
/// The eight 1 KiB character banks the current mapper registers select.
///
/// The PPU fetches one byte at a time and cannot afford to consult a mapper's
/// register layout on every fetch, so the mapping is resolved once here whenever
/// a register changes and handed over as a small table. Boards coarser than 8 KiB
/// return `None`, and the PPU falls back to its single bank number.
///
/// MMC3's arrangement is the reason this is not just a multiply: it splits the
/// 8 KiB into two 4 KiB halves, each made of one 2 KiB and two 1 KiB windows, and
/// a mode bit decides which half the registers drive. Registers 0 and 1 are the
/// 2 KiB windows and supply two adjacent pages each; registers 2 to 5 are the
/// four 1 KiB pages.
fn Cart::chr_pages(self : Cart) -> FixedArray[Int64]? {
  match self.mapper {
    Mmc3 => {
      let count = self.chr.length().to_int64() / 0x400L
      if count <= 0L {
        return None
      }
      let pages : FixedArray[Int64] = FixedArray::make(8, 0L)
      // Registers 0 and 1 are 2 KiB windows, so each supplies two consecutive
      // 1 KiB pages; their low bit is ignored, which is why they are masked even.
      let r0 = self.bank_registers[0] & 0xFEU.reinterpret_as_int().to_int64()
      let r1 = self.bank_registers[1] & 0xFEU.reinterpret_as_int().to_int64()
      // The first half of the pattern space is [R0, R0+1, R1, R1+1]; the second
      // is the four 1 KiB registers in order.
      let first_half : Array[Int64] = [r0, r0 + 1L, r1, r1 + 1L]
      let second_half : Array[Int64] = [
        self.bank_registers[2],
        self.bank_registers[3],
        self.bank_registers[4],
        self.bank_registers[5],
      ]
      for page in 0..<8 {
        // The mode bit swaps which half of the pattern space the registers drive,
        // which is how a game keeps its background tiles in one half while
        // switching sprite tiles per scanline in the other.
        let value = if self.chr_mode {
          if page < 4 {
            second_half[page]
          } else {
            first_half[page - 4]
          }
        } else if page < 4 {
          first_half[page]
        } else {
          second_half[page - 4]
        }
        pages[page] = value % count
      }
      Some(pages)
    }
    _ => None
  }
}

///|
/// Advances MMC3's scanline counter by one A12 rising edge.
///
/// The counter is the board's only view of the raster, and it is deliberately
/// coarse: it watches bit 12 of the PPU's address bus, which goes high when the
/// PPU fetches sprite pattern data and low again during the background fetches.
/// One complete scanline is therefore one rising edge. Games use it to change
/// banks at an exact scanline, which is how a status bar stays fixed while the
/// playfield scrolls.
///
/// The counter reloads on reaching zero rather than on the next edge, and the
/// reload value is used immediately, so a latch of zero means "interrupt every
/// scanline" rather than "interrupt on the second one".
fn Cart::clock_scanline(self : Cart) -> Unit {
  match self.mapper {
    Mmc3 =>
      if self.irq_counter == 0U {
        self.irq_counter = self.irq_latch
        if self.irq_enabled && self.irq_latch == 0U {
          self.irq_line = true
        }
      } else {
        self.irq_counter = self.irq_counter - 1U
        if self.irq_counter == 0U && self.irq_enabled {
          self.irq_line = true
        }
      }
    _ => ()
  }
}