// The 6502 operations, independent of how their operands were addressed.
//
// Each function here takes the already-resolved operand and performs the
// operation, returning the cycle count for that addressing mode's fixed part.
// The addressing modes are in `addressing.mbt` and the opcode-to-mode mapping is
// in `isa.mbt`, so an operation is written once no matter how many addressing
// modes reach it.

///|
/// Loads the accumulator.
pub fn Cpu::lda(self : Cpu, value : UInt32) -> Unit {
  self.a = value & 0xFF
  self.set_zn(self.a)
}

///|
/// Loads X.
pub fn Cpu::ldx(self : Cpu, value : UInt32) -> Unit {
  self.x = value & 0xFF
  self.set_zn(self.x)
}

///|
/// Loads Y.
pub fn Cpu::ldy(self : Cpu, value : UInt32) -> Unit {
  self.y = value & 0xFF
  self.set_zn(self.y)
}

///|
/// Stores the accumulator, recording the write for the trace column.
pub fn Cpu::store(
  self : Cpu,
  bus : &Bus,
  address : UInt32,
  value : UInt32,
) -> Unit {
  let v = value & 0xFF
  bus.write(address, v)
  self.last_access = Some({ address, value: v, write: true, })
}

///|
/// `AND` with the accumulator.
pub fn Cpu::and_(self : Cpu, value : UInt32) -> Unit {
  self.a = self.a & value
  self.set_zn(self.a)
}

///|
/// `ORA` with the accumulator.
pub fn Cpu::ora(self : Cpu, value : UInt32) -> Unit {
  self.a = self.a | value
  self.set_zn(self.a)
}

///|
/// `EOR` with the accumulator.
pub fn Cpu::eor(self : Cpu, value : UInt32) -> Unit {
  self.a = self.a ^ value
  self.set_zn(self.a)
}

///|
/// `ADC`: add with carry.
///
/// The 2A03 has no decimal adder, so the decimal flag only suppresses the
/// binary result's meaning on real hardware; the arithmetic performed is always
/// binary. The overflow flag is set from the sign bits of the operands rather
/// than from the result's carry, because carries out of bit 6 and bit 7 disagree
/// in the overflow case.
pub fn Cpu::adc(self : Cpu, value : UInt32) -> Unit {
  let a = self.a & 0xFF
  let m = value & 0xFF
  let carry = if self.flag(Carry) { 1U } else { 0U }
  let sum = a + m + carry
  let result = sum & 0xFF
  self.set_flag(Carry, sum > 0xFF)
  self.set_flag(Overflow, ((a ^ result) & (m ^ result) & 0x80) != 0)
  self.a = result
  self.set_zn(result)
}

///|
/// `SBC`: subtract with borrow, which on the 6502 is `ADC` of the complement.
pub fn Cpu::sbc(self : Cpu, value : UInt32) -> Unit {
  let a = self.a & 0xFF
  let m = value & 0xFF
  let borrow = if self.flag(Carry) { 0U } else { 1U }
  let diff = a - m - borrow
  let result = diff & 0xFF
  self.set_flag(Carry, diff < 0x100 && a >= m + borrow)
  self.set_flag(Overflow, ((a ^ result) & (0xFF ^ m ^ result) & 0x80) != 0)
  self.a = result
  self.set_zn(result)
}

///|
/// Compares a register with a value, setting carry when the register is at
/// least the value.
pub fn Cpu::compare(self : Cpu, register : UInt32, value : UInt32) -> Unit {
  let r = register & 0xFF
  let v = value & 0xFF
  self.set_flag(Carry, r >= v)
  self.set_zn((r - v) & 0xFF)
}

///|
/// Increments a value without touching memory.
pub fn Cpu::inc(self : Cpu, value : UInt32) -> UInt32 {
  let v = (value + 1) & 0xFF
  self.set_zn(v)
  v
}

///|
/// Decrements a value without touching memory.
pub fn Cpu::dec(self : Cpu, value : UInt32) -> UInt32 {
  let v = (value - 1) & 0xFF
  self.set_zn(v)
  v
}

///|
/// Arithmetic shift left: bit 7 moves into carry, bit 0 becomes zero.
pub fn Cpu::asl(self : Cpu, value : UInt32) -> (UInt32, UInt32) {
  let v = value & 0xFF
  self.set_flag(Carry, (v & 0x80) != 0)
  let result = (v << 1) & 0xFF
  self.set_zn(result)
  (result, result)
}

///|
/// Logical shift right: bit 0 moves into carry, bit 7 becomes zero.
pub fn Cpu::lsr(self : Cpu, value : UInt32) -> (UInt32, UInt32) {
  let v = value & 0xFF
  self.set_flag(Carry, (v & 0x01) != 0)
  let result = (v >> 1) & 0xFF
  self.set_zn(result)
  (result, result)
}

///|
/// Rotate left through carry.
pub fn Cpu::rol(self : Cpu, value : UInt32) -> (UInt32, UInt32) {
  let v = value & 0xFF
  let carry_in = if self.flag(Carry) { 1U } else { 0U }
  self.set_flag(Carry, (v & 0x80) != 0)
  let result = ((v << 1) | carry_in) & 0xFF
  self.set_zn(result)
  (result, result)
}

///|
/// Rotate right through carry.
pub fn Cpu::ror(self : Cpu, value : UInt32) -> (UInt32, UInt32) {
  let v = value & 0xFF
  let carry_in = if self.flag(Carry) { 0x80U } else { 0U }
  self.set_flag(Carry, (v & 0x01) != 0)
  let result = ((v >> 1) | carry_in) & 0xFF
  self.set_zn(result)
  (result, result)
}

///|
/// A taken branch: adds the signed displacement and one cycle, plus another when
/// the branch crosses into a new page.
pub fn Cpu::branch(self : Cpu, bus : &Bus, taken : Bool) -> Int64 {
  let displacement = self.fetch(bus)
  if !taken {
    return 2L
  }
  let offset = if (displacement & 0x80) != 0 {
    displacement.to_int64() - 0x100L
  } else {
    displacement.to_int64()
  }
  let from = self.pc
  let target = ((self.pc.to_int64() + offset) & 0xFFFFL)
    .to_int()
    .reinterpret_as_uint()
  self.pc = target
  let penalty = if (from & 0xFF00) != (target & 0xFF00) { 1L } else { 0L }
  3L + penalty
}

///|
/// `JMP ($addr)`: the 6502's indirect jump does not carry into the high byte of
/// the pointer, so a pointer at `$xxFF` reads its high byte from `$xx00`.
///
/// Reproducing the bug matters because real ROMs jump through tables placed to
/// avoid it, and because `nestest` checks it.
pub fn Cpu::jmp_indirect(self : Cpu, bus : &Bus) -> Int64 {
  let pointer = self.fetch16(bus)
  let lo = bus.read(pointer)
  let hi_addr = (pointer & 0xFF00) | ((pointer + 1) & 0xFF)
  let hi = bus.read(hi_addr)
  self.pc = lo | (hi << 8)
  5L
}

///|
/// `BRK`: pushes the return address plus two, the break flag and the status,
/// then vectors through the IRQ vector with the interrupt mask set.
pub fn Cpu::brk(self : Cpu, bus : &Bus) -> Int64 {
  let ret = (self.pc + 1) & 0xFFFF
  self.push(bus, (ret >> 8) & 0xFF)
  self.push(bus, ret & 0xFF)
  self.push(bus, self.p | FLAG_BREAK | FLAG_UNUSED)
  self.set_flag(Interrupt, true)
  let vector = read16(bus, 0xFFFE)
  self.pc = vector
  7L
}

///|
/// `BIT`: tests bits of a memory value against the accumulator.
///
/// The zero flag is set from the AND, but the negative and overflow flags are
/// copied straight from bits 7 and 6 of the operand, which is why `BIT` can be
/// used as a two-bit branch on a single memory read.
pub fn Cpu::bit(self : Cpu, value : UInt32) -> Unit {
  let v = value & 0xFF
  self.set_flag(Zero, (self.a & v) == 0)
  self.set_flag(Negative, (v & 0x80) != 0)
  self.set_flag(Overflow, (v & 0x40) != 0)
}