
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

#include "spc.h"
#include "apu.h"

static const int cyclesPerOpcode[256] = {
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 6, 8,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 6, 5, 2, 2, 4, 6,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 4, 5, 4,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 6, 5, 2, 2, 3, 8,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 6, 6,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 4, 5, 2, 2, 4, 3,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 4, 5, 5,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 5, 5, 2, 2, 3, 6,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 5, 4, 5, 2, 4, 5,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 5, 5, 2, 2, 12,5,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 6, 4, 4, 5, 2, 4, 4,
  2, 8, 4, 5, 4, 5, 5, 6, 5, 5, 5, 5, 2, 2, 3, 4,
  2, 8, 4, 5, 4, 5, 4, 7, 2, 5, 6, 4, 5, 2, 4, 9,
  2, 8, 4, 5, 5, 6, 6, 7, 4, 5, 5, 5, 2, 2, 6, 3,
  2, 8, 4, 5, 3, 4, 3, 6, 2, 4, 5, 3, 4, 3, 4, 3,
  2, 8, 4, 5, 4, 5, 5, 6, 3, 4, 5, 4, 2, 2, 4, 3
};

static uint8_t spc_read(Spc* spc, uint16_t adr);
static void spc_write(Spc* spc, uint16_t adr, uint8_t val);
static uint8_t spc_readOpcode(Spc* spc);
static uint16_t spc_readOpcodeWord(Spc* spc);
static uint8_t spc_getFlags(Spc* spc);
static void spc_setFlags(Spc* spc, uint8_t value);
static void spc_setZN(Spc* spc, uint8_t value);
static void spc_doBranch(Spc* spc, uint8_t value, bool check);
static uint8_t spc_pullByte(Spc* spc);
static void spc_pushByte(Spc* spc, uint8_t value);
static uint16_t spc_pullWord(Spc* spc);
static void spc_pushWord(Spc* spc, uint16_t value);
static uint16_t spc_readWord(Spc* spc, uint16_t adrl, uint16_t adrh);
static void spc_writeWord(Spc* spc, uint16_t adrl, uint16_t adrh, uint16_t value);
static void spc_doOpcode(Spc* spc, uint8_t opcode);

// addressing modes and opcode functions not declared, only used after defintions

static uint8_t spc_read(Spc* spc, uint16_t adr) {
  return apu_cpuRead(spc->apu, adr);
}

static void spc_write(Spc* spc, uint16_t adr, uint8_t val) {
  apu_cpuWrite(spc->apu, adr, val);
}

Spc* spc_init(Apu* apu) {
  Spc* spc = malloc(sizeof(Spc));
  spc->apu = apu;
  return spc;
}

void spc_free(Spc* spc) {
  free(spc);
}

void spc_reset(Spc* spc) {
  spc->a = 0;
  spc->x = 0;
  spc->y = 0;
  spc->sp = 0;
  spc->pc = spc_read(spc, 0xfffe) | (spc_read(spc, 0xffff) << 8);
  spc->c = false;
  spc->z = false;
  spc->v = false;
  spc->n = false;
  spc->i = false;
  spc->h = false;
  spc->p = false;
  spc->b = false;
  spc->stopped = false;
  spc->cyclesUsed = 0;
}

void spc_saveload(Spc *spc, SaveLoadFunc *func, void *ctx) {
  func(ctx, &spc->a, offsetof(Spc, cyclesUsed) - offsetof(Spc, a));
}

int spc_runOpcode(Spc* spc) {
  spc->cyclesUsed = 0;
  if(spc->stopped) return 1;
  uint8_t opcode = spc_readOpcode(spc);
  spc->cyclesUsed = cyclesPerOpcode[opcode];
  spc_doOpcode(spc, opcode);
  return spc->cyclesUsed;
}

static uint8_t spc_readOpcode(Spc* spc) {
  return spc_read(spc, spc->pc++);
}

static uint16_t spc_readOpcodeWord(Spc* spc) {
  uint8_t low = spc_readOpcode(spc);
  return low | (spc_readOpcode(spc) << 8);
}

static uint8_t spc_getFlags(Spc* spc) {
  uint8_t val = spc->n << 7;
  val |= spc->v << 6;
  val |= spc->p << 5;
  val |= spc->b << 4;
  val |= spc->h << 3;
  val |= spc->i << 2;
  val |= spc->z << 1;
  val |= spc->c;
  return val;
}

static void spc_setFlags(Spc* spc, uint8_t val) {
  spc->n = val & 0x80;
  spc->v = val & 0x40;
  spc->p = val & 0x20;
  spc->b = val & 0x10;
  spc->h = val & 8;
  spc->i = val & 4;
  spc->z = val & 2;
  spc->c = val & 1;
}

static void spc_setZN(Spc* spc, uint8_t value) {
  spc->z = value == 0;
  spc->n = value & 0x80;
}

static void spc_doBranch(Spc* spc, uint8_t value, bool check) {
  if(check) {
    spc->cyclesUsed += 2; // taken branch: 2 extra cycles
    spc->pc += (int8_t) value;
  }
}

static uint8_t spc_pullByte(Spc* spc) {
  spc->sp++;
  return spc_read(spc, 0x100 | spc->sp);
}

static void spc_pushByte(Spc* spc, uint8_t value) {
  spc_write(spc, 0x100 | spc->sp, value);
  spc->sp--;
}

static uint16_t spc_pullWord(Spc* spc) {
  uint8_t value = spc_pullByte(spc);
  return value | (spc_pullByte(spc) << 8);
}

static void spc_pushWord(Spc* spc, uint16_t value) {
  spc_pushByte(spc, value >> 8);
  spc_pushByte(spc, value & 0xff);
}

static uint16_t spc_readWord(Spc* spc, uint16_t adrl, uint16_t adrh) {
  uint8_t value = spc_read(spc, adrl);
  return value | (spc_read(spc, adrh) << 8);
}

static void spc_writeWord(Spc* spc, uint16_t adrl, uint16_t adrh, uint16_t value) {
  spc_write(spc, adrl, value & 0xff);
  spc_write(spc, adrh, value >> 8);
}

// adressing modes

static uint16_t spc_adrDp(Spc* spc) {
  return spc_readOpcode(spc) | (spc->p << 8);
}

static uint16_t spc_adrAbs(Spc* spc) {
  return spc_readOpcodeWord(spc);
}

static uint16_t spc_adrInd(Spc* spc) {
  return spc->x | (spc->p << 8);
}

static uint16_t spc_adrIdx(Spc* spc) {
  uint8_t pointer = spc_readOpcode(spc);
  return spc_readWord(spc, ((pointer + spc->x) & 0xff) | (spc->p << 8), ((pointer + spc->x + 1) & 0xff) | (spc->p << 8));
}

static uint16_t spc_adrImm(Spc* spc) {
  return spc->pc++;
}

static uint16_t spc_adrDpx(Spc* spc) {
  return ((spc_readOpcode(spc) + spc->x) & 0xff) | (spc->p << 8);
}

static uint16_t spc_adrDpy(Spc* spc) {
  return ((spc_readOpcode(spc) + spc->y) & 0xff) | (spc->p << 8);
}

static uint16_t spc_adrAbx(Spc* spc) {
  return (spc_readOpcodeWord(spc) + spc->x) & 0xffff;
}

static uint16_t spc_adrAby(Spc* spc) {
  return (spc_readOpcodeWord(spc) + spc->y) & 0xffff;
}

static uint16_t spc_adrIdy(Spc* spc) {
  uint8_t pointer = spc_readOpcode(spc);
  uint16_t adr = spc_readWord(spc, pointer | (spc->p << 8), ((pointer + 1) & 0xff) | (spc->p << 8));
  return (adr + spc->y) & 0xffff;
}

static uint16_t spc_adrDpDp(Spc* spc, uint16_t* src) {
  *src = spc_readOpcode(spc) | (spc->p << 8);
  return spc_readOpcode(spc) | (spc->p << 8);
}

static uint16_t spc_adrDpImm(Spc* spc, uint16_t* src) {
  *src = spc->pc++;
  return spc_readOpcode(spc) | (spc->p << 8);
}

static uint16_t spc_adrIndInd(Spc* spc, uint16_t* src) {
  *src = spc->y | (spc->p << 8);
  return spc->x | (spc->p << 8);
}

static uint8_t spc_adrAbsBit(Spc* spc, uint16_t* adr) {
  uint16_t adrBit = spc_readOpcodeWord(spc);
  *adr = adrBit & 0x1fff;
  return adrBit >> 13;
}

static uint16_t spc_adrDpWord(Spc* spc, uint16_t* low) {
  uint8_t adr = spc_readOpcode(spc);
  *low = adr | (spc->p << 8);
  return ((adr + 1) & 0xff) | (spc->p << 8);
}

static uint16_t spc_adrIndP(Spc* spc) {
  return spc->x++ | (spc->p << 8);
}

// opcode functions

static void spc_and(Spc* spc, uint16_t adr) {
  spc->a &= spc_read(spc, adr);
  spc_setZN(spc, spc->a);
}

static void spc_andm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src);
  uint8_t result = spc_read(spc, dst) & value;
  spc_write(spc, dst, result);
  spc_setZN(spc, result);
}

static void spc_or(Spc* spc, uint16_t adr) {
  spc->a |= spc_read(spc, adr);
  spc_setZN(spc, spc->a);
}

static void spc_orm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src);
  uint8_t result = spc_read(spc, dst) | value;
  spc_write(spc, dst, result);
  spc_setZN(spc, result);
}

static void spc_eor(Spc* spc, uint16_t adr) {
  spc->a ^= spc_read(spc, adr);
  spc_setZN(spc, spc->a);
}

static void spc_eorm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src);
  uint8_t result = spc_read(spc, dst) ^ value;
  spc_write(spc, dst, result);
  spc_setZN(spc, result);
}

static void spc_adc(Spc* spc, uint16_t adr) {
  uint8_t value = spc_read(spc, adr);
  int result = spc->a + value + spc->c;
  spc->v = (spc->a & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
  spc->h = ((spc->a & 0xf) + (value & 0xf) + spc->c) > 0xf;
  spc->c = result > 0xff;
  spc->a = result;
  spc_setZN(spc, spc->a);
}

static void spc_adcm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src);
  uint8_t applyOn = spc_read(spc, dst);
  int result = applyOn + value + spc->c;
  spc->v = (applyOn & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
  spc->h = ((applyOn & 0xf) + (value & 0xf) + spc->c) > 0xf;
  spc->c = result > 0xff;
  spc_write(spc, dst, result);
  spc_setZN(spc, result);
}

static void spc_sbc(Spc* spc, uint16_t adr) {
  uint8_t value = spc_read(spc, adr) ^ 0xff;
  int result = spc->a + value + spc->c;
  spc->v = (spc->a & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
  spc->h = ((spc->a & 0xf) + (value & 0xf) + spc->c) > 0xf;
  spc->c = result > 0xff;
  spc->a = result;
  spc_setZN(spc, spc->a);
}

static void spc_sbcm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src) ^ 0xff;
  uint8_t applyOn = spc_read(spc, dst);
  int result = applyOn + value + spc->c;
  spc->v = (applyOn & 0x80) == (value & 0x80) && (value & 0x80) != (result & 0x80);
  spc->h = ((applyOn & 0xf) + (value & 0xf) + spc->c) > 0xf;
  spc->c = result > 0xff;
  spc_write(spc, dst, result);
  spc_setZN(spc, result);
}

static void spc_cmp(Spc* spc, uint16_t adr) {
  uint8_t value = spc_read(spc, adr) ^ 0xff;
  int result = spc->a + value + 1;
  spc->c = result > 0xff;
  spc_setZN(spc, result);
}

static void spc_cmpx(Spc* spc, uint16_t adr) {
  uint8_t value = spc_read(spc, adr) ^ 0xff;
  int result = spc->x + value + 1;
  spc->c = result > 0xff;
  spc_setZN(spc, result);
}

static void spc_cmpy(Spc* spc, uint16_t adr) {
  uint8_t value = spc_read(spc, adr) ^ 0xff;
  int result = spc->y + value + 1;
  spc->c = result > 0xff;
  spc_setZN(spc, result);
}

static void spc_cmpm(Spc* spc, uint16_t dst, uint16_t src) {
  uint8_t value = spc_read(spc, src) ^ 0xff;
  int result = spc_read(spc, dst) + value + 1;
  spc->c = result > 0xff;
  spc_setZN(spc, result);
}

static void spc_mov(Spc* spc, uint16_t adr) {
  spc->a = spc_read(spc, adr);
  spc_setZN(spc, spc->a);
}

static void spc_movx(Spc* spc, uint16_t adr) {
  spc->x = spc_read(spc, adr);
  spc_setZN(spc, spc->x);
}

static void spc_movy(Spc* spc, uint16_t adr) {
  spc->y = spc_read(spc, adr);
  spc_setZN(spc, spc->y);
}

static void spc_movs(Spc* spc, uint16_t adr) {
  spc_read(spc, adr);
  spc_write(spc, adr, spc->a);
}

static void spc_movsx(Spc* spc, uint16_t adr) {
  spc_read(spc, adr);
  spc_write(spc, adr, spc->x);
}

static void spc_movsy(Spc* spc, uint16_t adr) {
  spc_read(spc, adr);
  spc_write(spc, adr, spc->y);
}

static void spc_asl(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr);
  spc->c = val & 0x80;
  val <<= 1;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_lsr(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr);
  spc->c = val & 1;
  val >>= 1;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_rol(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr);
  bool newC = val & 0x80;
  val = (val << 1) | spc->c;
  spc->c = newC;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_ror(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr);
  bool newC = val & 1;
  val = (val >> 1) | (spc->c << 7);
  spc->c = newC;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_inc(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr) + 1;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_dec(Spc* spc, uint16_t adr) {
  uint8_t val = spc_read(spc, adr) - 1;
  spc_write(spc, adr, val);
  spc_setZN(spc, val);
}

static void spc_doOpcode(Spc* spc, uint8_t opcode) {
  switch(opcode) {
    case 0x00: { // nop imp
      // no operation
      break;
    }
    case 0x01:
    case 0x11:
    case 0x21:
    case 0x31:
    case 0x41:
    case 0x51:
    case 0x61:
    case 0x71:
    case 0x81:
    case 0x91:
    case 0xa1:
    case 0xb1:
    case 0xc1:
    case 0xd1:
    case 0xe1:
    case 0xf1: { // tcall imp
      spc_pushWord(spc, spc->pc);
      uint16_t adr = 0xffde - (2 * (opcode >> 4));
      spc->pc = spc_readWord(spc, adr, adr + 1);
      break;
    }
    case 0x02:
    case 0x22:
    case 0x42:
    case 0x62:
    case 0x82:
    case 0xa2:
    case 0xc2:
    case 0xe2: { // set1 dp
      uint16_t adr = spc_adrDp(spc);
      spc_write(spc, adr, spc_read(spc, adr) | (1 << (opcode >> 5)));
      break;
    }
    case 0x12:
    case 0x32:
    case 0x52:
    case 0x72:
    case 0x92:
    case 0xb2:
    case 0xd2:
    case 0xf2: { // clr1 dp
      uint16_t adr = spc_adrDp(spc);
      spc_write(spc, adr, spc_read(spc, adr) & ~(1 << (opcode >> 5)));
      break;
    }
    case 0x03:
    case 0x23:
    case 0x43:
    case 0x63:
    case 0x83:
    case 0xa3:
    case 0xc3:
    case 0xe3: { // bbs dp, rel
      uint8_t val = spc_read(spc, spc_adrDp(spc));
      spc_doBranch(spc, spc_readOpcode(spc), val & (1 << (opcode >> 5)));
      break;
    }
    case 0x13:
    case 0x33:
    case 0x53:
    case 0x73:
    case 0x93:
    case 0xb3:
    case 0xd3:
    case 0xf3: { // bbc dp, rel
      uint8_t val = spc_read(spc, spc_adrDp(spc));
      spc_doBranch(spc, spc_readOpcode(spc), (val & (1 << (opcode >> 5))) == 0);
      break;
    }
    case 0x04: { // or  dp
      spc_or(spc, spc_adrDp(spc));
      break;
    }
    case 0x05: { // or  abs
      spc_or(spc, spc_adrAbs(spc));
      break;
    }
    case 0x06: { // or  ind
      spc_or(spc, spc_adrInd(spc));
      break;
    }
    case 0x07: { // or  idx
      spc_or(spc, spc_adrIdx(spc));
      break;
    }
    case 0x08: { // or  imm
      spc_or(spc, spc_adrImm(spc));
      break;
    }
    case 0x09: { // orm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_orm(spc, dst, src);
      break;
    }
    case 0x0a: { // or1 abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = spc->c | ((spc_read(spc, adr) >> bit) & 1);
      break;
    }
    case 0x0b: { // asl dp
      spc_asl(spc, spc_adrDp(spc));
      break;
    }
    case 0x0c: { // asl abs
      spc_asl(spc, spc_adrAbs(spc));
      break;
    }
    case 0x0d: { // pushp imp
      spc_pushByte(spc, spc_getFlags(spc));
      break;
    }
    case 0x0e: { // tset1 abs
      uint16_t adr = spc_adrAbs(spc);
      uint8_t val = spc_read(spc, adr);
      uint8_t result = spc->a + (val ^ 0xff) + 1;
      spc_setZN(spc, result);
      spc_write(spc, adr, val | spc->a);
      break;
    }
    case 0x0f: { // brk imp
      spc_pushWord(spc, spc->pc);
      spc_pushByte(spc, spc_getFlags(spc));
      spc->i = false;
      spc->b = true;
      spc->pc = spc_readWord(spc, 0xffde, 0xffdf);
      break;
    }
    case 0x10: { // bpl rel
      spc_doBranch(spc, spc_readOpcode(spc), !spc->n);
      break;
    }
    case 0x14: { // or  dpx
      spc_or(spc, spc_adrDpx(spc));
      break;
    }
    case 0x15: { // or  abx
      spc_or(spc, spc_adrAbx(spc));
      break;
    }
    case 0x16: { // or  aby
      spc_or(spc, spc_adrAby(spc));
      break;
    }
    case 0x17: { // or  idy
      spc_or(spc, spc_adrIdy(spc));
      break;
    }
    case 0x18: { // orm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_orm(spc, dst, src);
      break;
    }
    case 0x19: { // orm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_orm(spc, dst, src);
      break;
    }
    case 0x1a: { // decw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t value = spc_readWord(spc, low, high) - 1;
      spc->z = value == 0;
      spc->n = value & 0x8000;
      spc_writeWord(spc, low, high, value);
      break;
    }
    case 0x1b: { // asl dpx
      spc_asl(spc, spc_adrDpx(spc));
      break;
    }
    case 0x1c: { // asla imp
      spc->c = spc->a & 0x80;
      spc->a <<= 1;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x1d: { // decx imp
      spc->x--;
      spc_setZN(spc, spc->x);
      break;
    }
    case 0x1e: { // cmpx abs
      spc_cmpx(spc, spc_adrAbs(spc));
      break;
    }
    case 0x1f: { // jmp iax
      uint16_t pointer = spc_readOpcodeWord(spc);
      spc->pc = spc_readWord(spc, (pointer + spc->x) & 0xffff, (pointer + spc->x + 1) & 0xffff);
      break;
    }
    case 0x20: { // clrp imp
      spc->p = false;
      break;
    }
    case 0x24: { // and dp
      spc_and(spc, spc_adrDp(spc));
      break;
    }
    case 0x25: { // and abs
      spc_and(spc, spc_adrAbs(spc));
      break;
    }
    case 0x26: { // and ind
      spc_and(spc, spc_adrInd(spc));
      break;
    }
    case 0x27: { // and idx
      spc_and(spc, spc_adrIdx(spc));
      break;
    }
    case 0x28: { // and imm
      spc_and(spc, spc_adrImm(spc));
      break;
    }
    case 0x29: { // andm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_andm(spc, dst, src);
      break;
    }
    case 0x2a: { // or1n abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = spc->c | (~(spc_read(spc, adr) >> bit) & 1);
      break;
    }
    case 0x2b: { // rol dp
      spc_rol(spc, spc_adrDp(spc));
      break;
    }
    case 0x2c: { // rol abs
      spc_rol(spc, spc_adrAbs(spc));
      break;
    }
    case 0x2d: { // pusha imp
      spc_pushByte(spc, spc->a);
      break;
    }
    case 0x2e: { // cbne dp, rel
      uint8_t val = spc_read(spc, spc_adrDp(spc)) ^ 0xff;
      uint8_t result = spc->a + val + 1;
      spc_doBranch(spc, spc_readOpcode(spc), result != 0);
      break;
    }
    case 0x2f: { // bra rel
      spc->pc += (int8_t) spc_readOpcode(spc);
      break;
    }
    case 0x30: { // bmi rel
      spc_doBranch(spc, spc_readOpcode(spc), spc->n);
      break;
    }
    case 0x34: { // and dpx
      spc_and(spc, spc_adrDpx(spc));
      break;
    }
    case 0x35: { // and abx
      spc_and(spc, spc_adrAbx(spc));
      break;
    }
    case 0x36: { // and aby
      spc_and(spc, spc_adrAby(spc));
      break;
    }
    case 0x37: { // and idy
      spc_and(spc, spc_adrIdy(spc));
      break;
    }
    case 0x38: { // andm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_andm(spc, dst, src);
      break;
    }
    case 0x39: { // andm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_andm(spc, dst, src);
      break;
    }
    case 0x3a: { // incw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t value = spc_readWord(spc, low, high) + 1;
      spc->z = value == 0;
      spc->n = value & 0x8000;
      spc_writeWord(spc, low, high, value);
      break;
    }
    case 0x3b: { // rol dpx
      spc_rol(spc, spc_adrDpx(spc));
      break;
    }
    case 0x3c: { // rola imp
      bool newC = spc->a & 0x80;
      spc->a = (spc->a << 1) | spc->c;
      spc->c = newC;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x3d: { // incx imp
      spc->x++;
      spc_setZN(spc, spc->x);
      break;
    }
    case 0x3e: { // cmpx dp
      spc_cmpx(spc, spc_adrDp(spc));
      break;
    }
    case 0x3f: { // call abs
      uint16_t dst = spc_readOpcodeWord(spc);
      spc_pushWord(spc, spc->pc);
      spc->pc = dst;
      break;
    }
    case 0x40: { // setp imp
      spc->p = true;
      break;
    }
    case 0x44: { // eor dp
      spc_eor(spc, spc_adrDp(spc));
      break;
    }
    case 0x45: { // eor abs
      spc_eor(spc, spc_adrAbs(spc));
      break;
    }
    case 0x46: { // eor ind
      spc_eor(spc, spc_adrInd(spc));
      break;
    }
    case 0x47: { // eor idx
      spc_eor(spc, spc_adrIdx(spc));
      break;
    }
    case 0x48: { // eor imm
      spc_eor(spc, spc_adrImm(spc));
      break;
    }
    case 0x49: { // eorm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_eorm(spc, dst, src);
      break;
    }
    case 0x4a: { // and1 abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = spc->c & ((spc_read(spc, adr) >> bit) & 1);
      break;
    }
    case 0x4b: { // lsr dp
      spc_lsr(spc, spc_adrDp(spc));
      break;
    }
    case 0x4c: { // lsr abs
      spc_lsr(spc, spc_adrAbs(spc));
      break;
    }
    case 0x4d: { // pushx imp
      spc_pushByte(spc, spc->x);
      break;
    }
    case 0x4e: { // tclr1 abs
      uint16_t adr = spc_adrAbs(spc);
      uint8_t val = spc_read(spc, adr);
      uint8_t result = spc->a + (val ^ 0xff) + 1;
      spc_setZN(spc, result);
      spc_write(spc, adr, val & ~spc->a);
      break;
    }
    case 0x4f: { // pcall dp
      uint8_t dst = spc_readOpcode(spc);
      spc_pushWord(spc, spc->pc);
      spc->pc = 0xff00 | dst;
      break;
    }
    case 0x50: { // bvc rel
      spc_doBranch(spc, spc_readOpcode(spc), !spc->v);
      break;
    }
    case 0x54: { // eor dpx
      spc_eor(spc, spc_adrDpx(spc));
      break;
    }
    case 0x55: { // eor abx
      spc_eor(spc, spc_adrAbx(spc));
      break;
    }
    case 0x56: { // eor aby
      spc_eor(spc, spc_adrAby(spc));
      break;
    }
    case 0x57: { // eor idy
      spc_eor(spc, spc_adrIdy(spc));
      break;
    }
    case 0x58: { // eorm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_eorm(spc, dst, src);
      break;
    }
    case 0x59: { // eorm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_eorm(spc, dst, src);
      break;
    }
    case 0x5a: { // cmpw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t value = spc_readWord(spc, low, high) ^ 0xffff;
      uint16_t ya = spc->a | (spc->y << 8);
      int result = ya + value + 1;
      spc->c = result > 0xffff;
      spc->z = (result & 0xffff) == 0;
      spc->n = result & 0x8000;
      break;
    }
    case 0x5b: { // lsr dpx
      spc_lsr(spc, spc_adrDpx(spc));
      break;
    }
    case 0x5c: { // lsra imp
      spc->c = spc->a & 1;
      spc->a >>= 1;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x5d: { // movxa imp
      spc->x = spc->a;
      spc_setZN(spc, spc->x);
      break;
    }
    case 0x5e: { // cmpy abs
      spc_cmpy(spc, spc_adrAbs(spc));
      break;
    }
    case 0x5f: { // jmp abs
      spc->pc = spc_readOpcodeWord(spc);
      break;
    }
    case 0x60: { // clrc imp
      spc->c = false;
      break;
    }
    case 0x64: { // cmp dp
      spc_cmp(spc, spc_adrDp(spc));
      break;
    }
    case 0x65: { // cmp abs
      spc_cmp(spc, spc_adrAbs(spc));
      break;
    }
    case 0x66: { // cmp ind
      spc_cmp(spc, spc_adrInd(spc));
      break;
    }
    case 0x67: { // cmp idx
      spc_cmp(spc, spc_adrIdx(spc));
      break;
    }
    case 0x68: { // cmp imm
      spc_cmp(spc, spc_adrImm(spc));
      break;
    }
    case 0x69: { // cmpm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_cmpm(spc, dst, src);
      break;
    }
    case 0x6a: { // and1n abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = spc->c & (~(spc_read(spc, adr) >> bit) & 1);
      break;
    }
    case 0x6b: { // ror dp
      spc_ror(spc, spc_adrDp(spc));
      break;
    }
    case 0x6c: { // ror abs
      spc_ror(spc, spc_adrAbs(spc));
      break;
    }
    case 0x6d: { // pushy imp
      spc_pushByte(spc, spc->y);
      break;
    }
    case 0x6e: { // dbnz dp, rel
      uint16_t adr = spc_adrDp(spc);
      uint8_t result = spc_read(spc, adr) - 1;
      spc_write(spc, adr, result);
      spc_doBranch(spc, spc_readOpcode(spc), result != 0);
      break;
    }
    case 0x6f: { // ret imp
      spc->pc = spc_pullWord(spc);
      break;
    }
    case 0x70: { // bvs rel
      spc_doBranch(spc, spc_readOpcode(spc), spc->v);
      break;
    }
    case 0x74: { // cmp dpx
      spc_cmp(spc, spc_adrDpx(spc));
      break;
    }
    case 0x75: { // cmp abx
      spc_cmp(spc, spc_adrAbx(spc));
      break;
    }
    case 0x76: { // cmp aby
      spc_cmp(spc, spc_adrAby(spc));
      break;
    }
    case 0x77: { // cmp idy
      spc_cmp(spc, spc_adrIdy(spc));
      break;
    }
    case 0x78: { // cmpm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_cmpm(spc, dst, src);
      break;
    }
    case 0x79: { // cmpm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_cmpm(spc, dst, src);
      break;
    }
    case 0x7a: { // addw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t value = spc_readWord(spc, low, high);
      uint16_t ya = spc->a | (spc->y << 8);
      int result = ya + value;
      spc->v = (ya & 0x8000) == (value & 0x8000) && (value & 0x8000) != (result & 0x8000);
      spc->h = ((ya & 0xfff) + (value & 0xfff) + 1) > 0xfff;
      spc->c = result > 0xffff;
      spc->z = (result & 0xffff) == 0;
      spc->n = result & 0x8000;
      spc->a = result & 0xff;
      spc->y = result >> 8;
      break;
    }
    case 0x7b: { // ror dpx
      spc_ror(spc, spc_adrDpx(spc));
      break;
    }
    case 0x7c: { // rora imp
      bool newC = spc->a & 1;
      spc->a = (spc->a >> 1) | (spc->c << 7);
      spc->c = newC;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x7d: { // movax imp
      spc->a = spc->x;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x7e: { // cmpy dp
      spc_cmpy(spc, spc_adrDp(spc));
      break;
    }
    case 0x7f: { // reti imp
      spc_setFlags(spc, spc_pullByte(spc));
      spc->pc = spc_pullWord(spc);
      break;
    }
    case 0x80: { // setc imp
      spc->c = true;
      break;
    }
    case 0x84: { // adc dp
      spc_adc(spc, spc_adrDp(spc));
      break;
    }
    case 0x85: { // adc abs
      spc_adc(spc, spc_adrAbs(spc));
      break;
    }
    case 0x86: { // adc ind
      spc_adc(spc, spc_adrInd(spc));
      break;
    }
    case 0x87: { // adc idx
      spc_adc(spc, spc_adrIdx(spc));
      break;
    }
    case 0x88: { // adc imm
      spc_adc(spc, spc_adrImm(spc));
      break;
    }
    case 0x89: { // adcm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_adcm(spc, dst, src);
      break;
    }
    case 0x8a: { // eor1 abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = spc->c ^ ((spc_read(spc, adr) >> bit) & 1);
      break;
    }
    case 0x8b: { // dec dp
      spc_dec(spc, spc_adrDp(spc));
      break;
    }
    case 0x8c: { // dec abs
      spc_dec(spc, spc_adrAbs(spc));
      break;
    }
    case 0x8d: { // movy imm
      spc_movy(spc, spc_adrImm(spc));
      break;
    }
    case 0x8e: { // popp imp
      spc_setFlags(spc, spc_pullByte(spc));
      break;
    }
    case 0x8f: { // movm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      uint8_t val = spc_read(spc, src);
      spc_read(spc, dst);
      spc_write(spc, dst, val);
      break;
    }
    case 0x90: { // bcc rel
      spc_doBranch(spc, spc_readOpcode(spc), !spc->c);
      break;
    }
    case 0x94: { // adc dpx
      spc_adc(spc, spc_adrDpx(spc));
      break;
    }
    case 0x95: { // adc abx
      spc_adc(spc, spc_adrAbx(spc));
      break;
    }
    case 0x96: { // adc aby
      spc_adc(spc, spc_adrAby(spc));
      break;
    }
    case 0x97: { // adc idy
      spc_adc(spc, spc_adrIdy(spc));
      break;
    }
    case 0x98: { // adcm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_adcm(spc, dst, src);
      break;
    }
    case 0x99: { // adcm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_adcm(spc, dst, src);
      break;
    }
    case 0x9a: { // subw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t value = spc_readWord(spc, low, high) ^ 0xffff;
      uint16_t ya = spc->a | (spc->y << 8);
      int result = ya + value + 1;
      spc->v = (ya & 0x8000) == (value & 0x8000) && (value & 0x8000) != (result & 0x8000);
      spc->h = ((ya & 0xfff) + (value & 0xfff) + 1) > 0xfff;
      spc->c = result > 0xffff;
      spc->z = (result & 0xffff) == 0;
      spc->n = result & 0x8000;
      spc->a = result & 0xff;
      spc->y = result >> 8;
      break;
    }
    case 0x9b: { // dec dpx
      spc_dec(spc, spc_adrDpx(spc));
      break;
    }
    case 0x9c: { // deca imp
      spc->a--;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x9d: { // movxp imp
      spc->x = spc->sp;
      spc_setZN(spc, spc->x);
      break;
    }
    case 0x9e: { // div imp
      // TODO: proper division algorithm
      uint16_t value = spc->a | (spc->y << 8);
      int result = 0xffff;
      int mod = spc->a;
      if(spc->x != 0) {
        result = value / spc->x;
        mod = value % spc->x;
      }
      spc->v = result > 0xff;
      spc->h = (spc->x & 0xf) <= (spc->y & 0xf);
      spc->a = result;
      spc->y = mod;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0x9f: { // xcn imp
      spc->a = (spc->a >> 4) | (spc->a << 4);
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xa0: { // ei  imp
      spc->i = true;
      break;
    }
    case 0xa4: { // sbc dp
      spc_sbc(spc, spc_adrDp(spc));
      break;
    }
    case 0xa5: { // sbc abs
      spc_sbc(spc, spc_adrAbs(spc));
      break;
    }
    case 0xa6: { // sbc ind
      spc_sbc(spc, spc_adrInd(spc));
      break;
    }
    case 0xa7: { // sbc idx
      spc_sbc(spc, spc_adrIdx(spc));
      break;
    }
    case 0xa8: { // sbc imm
      spc_sbc(spc, spc_adrImm(spc));
      break;
    }
    case 0xa9: { // sbcm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      spc_sbcm(spc, dst, src);
      break;
    }
    case 0xaa: { // mov1 abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      spc->c = (spc_read(spc, adr) >> bit) & 1;
      break;
    }
    case 0xab: { // inc dp
      spc_inc(spc, spc_adrDp(spc));
      break;
    }
    case 0xac: { // inc abs
      spc_inc(spc, spc_adrAbs(spc));
      break;
    }
    case 0xad: { // cmpy imm
      spc_cmpy(spc, spc_adrImm(spc));
      break;
    }
    case 0xae: { // popa imp
      spc->a = spc_pullByte(spc);
      break;
    }
    case 0xaf: { // movs ind+
      uint16_t adr = spc_adrIndP(spc);
      spc_write(spc, adr, spc->a);
      break;
    }
    case 0xb0: { // bcs rel
      spc_doBranch(spc, spc_readOpcode(spc), spc->c);
      break;
    }
    case 0xb4: { // sbc dpx
      spc_sbc(spc, spc_adrDpx(spc));
      break;
    }
    case 0xb5: { // sbc abx
      spc_sbc(spc, spc_adrAbx(spc));
      break;
    }
    case 0xb6: { // sbc aby
      spc_sbc(spc, spc_adrAby(spc));
      break;
    }
    case 0xb7: { // sbc idy
      spc_sbc(spc, spc_adrIdy(spc));
      break;
    }
    case 0xb8: { // sbcm dp, imm
      uint16_t src = 0;
      uint16_t dst = spc_adrDpImm(spc, &src);
      spc_sbcm(spc, dst, src);
      break;
    }
    case 0xb9: { // sbcm ind, ind
      uint16_t src = 0;
      uint16_t dst = spc_adrIndInd(spc, &src);
      spc_sbcm(spc, dst, src);
      break;
    }
    case 0xba: { // movw dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      uint16_t val = spc_readWord(spc, low, high);
      spc->a = val & 0xff;
      spc->y = val >> 8;
      spc->z = val == 0;
      spc->n = val & 0x8000;
      break;
    }
    case 0xbb: { // inc dpx
      spc_inc(spc, spc_adrDpx(spc));
      break;
    }
    case 0xbc: { // inca imp
      spc->a++;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xbd: { // movpx imp
      spc->sp = spc->x;
      break;
    }
    case 0xbe: { // das imp
      if(spc->a > 0x99 || !spc->c) {
        spc->a -= 0x60;
        spc->c = false;
      }
      if((spc->a & 0xf) > 9 || !spc->h) {
        spc->a -= 6;
      }
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xbf: { // mov ind+
      uint16_t adr = spc_adrIndP(spc);
      spc->a = spc_read(spc, adr);
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xc0: { // di  imp
      spc->i = false;
      break;
    }
    case 0xc4: { // movs dp
      spc_movs(spc, spc_adrDp(spc));
      break;
    }
    case 0xc5: { // movs abs
      spc_movs(spc, spc_adrAbs(spc));
      break;
    }
    case 0xc6: { // movs ind
      spc_movs(spc, spc_adrInd(spc));
      break;
    }
    case 0xc7: { // movs idx
      spc_movs(spc, spc_adrIdx(spc));
      break;
    }
    case 0xc8: { // cmpx imm
      spc_cmpx(spc, spc_adrImm(spc));
      break;
    }
    case 0xc9: { // movsx abs
      spc_movsx(spc, spc_adrAbs(spc));
      break;
    }
    case 0xca: { // mov1s abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      uint8_t result = (spc_read(spc, adr) & (~(1 << bit))) | (spc->c << bit);
      spc_write(spc, adr, result);
      break;
    }
    case 0xcb: { // movsy dp
      spc_movsy(spc, spc_adrDp(spc));
      break;
    }
    case 0xcc: { // movsy abs
      spc_movsy(spc, spc_adrAbs(spc));
      break;
    }
    case 0xcd: { // movx imm
      spc_movx(spc, spc_adrImm(spc));
      break;
    }
    case 0xce: { // popx imp
      spc->x = spc_pullByte(spc);
      break;
    }
    case 0xcf: { // mul imp
      uint16_t result = spc->a * spc->y;
      spc->a = result & 0xff;
      spc->y = result >> 8;
      spc_setZN(spc, spc->y);
      break;
    }
    case 0xd0: { // bne rel
      spc_doBranch(spc, spc_readOpcode(spc), !spc->z);
      break;
    }
    case 0xd4: { // movs dpx
      spc_movs(spc, spc_adrDpx(spc));
      break;
    }
    case 0xd5: { // movs abx
      spc_movs(spc, spc_adrAbx(spc));
      break;
    }
    case 0xd6: { // movs aby
      spc_movs(spc, spc_adrAby(spc));
      break;
    }
    case 0xd7: { // movs idy
      spc_movs(spc, spc_adrIdy(spc));
      break;
    }
    case 0xd8: { // movsx dp
      spc_movsx(spc, spc_adrDp(spc));
      break;
    }
    case 0xd9: { // movsx dpy
      spc_movsx(spc, spc_adrDpy(spc));
      break;
    }
    case 0xda: { // movws dp
      uint16_t low = 0;
      uint16_t high = spc_adrDpWord(spc, &low);
      spc_read(spc, low);
      spc_write(spc, low, spc->a);
      spc_write(spc, high, spc->y);
      break;
    }
    case 0xdb: { // movsy dpx
      spc_movsy(spc, spc_adrDpx(spc));
      break;
    }
    case 0xdc: { // decy imp
      spc->y--;
      spc_setZN(spc, spc->y);
      break;
    }
    case 0xdd: { // movay imp
      spc->a = spc->y;
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xde: { // cbne dpx, rel
      uint8_t val = spc_read(spc, spc_adrDpx(spc)) ^ 0xff;
      uint8_t result = spc->a + val + 1;
      spc_doBranch(spc, spc_readOpcode(spc), result != 0);
      break;
    }
    case 0xdf: { // daa imp
      if(spc->a > 0x99 || spc->c) {
        spc->a += 0x60;
        spc->c = true;
      }
      if((spc->a & 0xf) > 9 || spc->h) {
        spc->a += 6;
      }
      spc_setZN(spc, spc->a);
      break;
    }
    case 0xe0: { // clrv imp
      spc->v = false;
      spc->h = false;
      break;
    }
    case 0xe4: { // mov dp
      spc_mov(spc, spc_adrDp(spc));
      break;
    }
    case 0xe5: { // mov abs
      spc_mov(spc, spc_adrAbs(spc));
      break;
    }
    case 0xe6: { // mov ind
      spc_mov(spc, spc_adrInd(spc));
      break;
    }
    case 0xe7: { // mov idx
      spc_mov(spc, spc_adrIdx(spc));
      break;
    }
    case 0xe8: { // mov imm
      spc_mov(spc, spc_adrImm(spc));
      break;
    }
    case 0xe9: { // movx abs
      spc_movx(spc, spc_adrAbs(spc));
      break;
    }
    case 0xea: { // not1 abs.bit
      uint16_t adr = 0;
      uint8_t bit = spc_adrAbsBit(spc, &adr);
      uint8_t result = spc_read(spc, adr) ^ (1 << bit);
      spc_write(spc, adr, result);
      break;
    }
    case 0xeb: { // movy dp
      spc_movy(spc, spc_adrDp(spc));
      break;
    }
    case 0xec: { // movy abs
      spc_movy(spc, spc_adrAbs(spc));
      break;
    }
    case 0xed: { // notc imp
      spc->c = !spc->c;
      break;
    }
    case 0xee: { // popy imp
      spc->y = spc_pullByte(spc);
      break;
    }
    case 0xef: { // sleep imp
      spc->stopped = true; // no interrupts, so sleeping stops as well
      break;
    }
    case 0xf0: { // beq rel
      spc_doBranch(spc, spc_readOpcode(spc), spc->z);
      break;
    }
    case 0xf4: { // mov dpx
      spc_mov(spc, spc_adrDpx(spc));
      break;
    }
    case 0xf5: { // mov abx
      spc_mov(spc, spc_adrAbx(spc));
      break;
    }
    case 0xf6: { // mov aby
      spc_mov(spc, spc_adrAby(spc));
      break;
    }
    case 0xf7: { // mov idy
      spc_mov(spc, spc_adrIdy(spc));
      break;
    }
    case 0xf8: { // movx dp
      spc_movx(spc, spc_adrDp(spc));
      break;
    }
    case 0xf9: { // movx dpy
      spc_movx(spc, spc_adrDpy(spc));
      break;
    }
    case 0xfa: { // movm dp, dp
      uint16_t src = 0;
      uint16_t dst = spc_adrDpDp(spc, &src);
      uint8_t val = spc_read(spc, src);
      spc_write(spc, dst, val);
      break;
    }
    case 0xfb: { // movy dpx
      spc_movy(spc, spc_adrDpx(spc));
      break;
    }
    case 0xfc: { // incy imp
      spc->y++;
      spc_setZN(spc, spc->y);
      break;
    }
    case 0xfd: { // movya imp
      spc->y = spc->a;
      spc_setZN(spc, spc->y);
      break;
    }
    case 0xfe: { // dbnzy rel
      spc->y--;
      spc_doBranch(spc, spc_readOpcode(spc), spc->y != 0);
      break;
    }
    case 0xff: { // stop imp
      spc->stopped = true;
      break;
    }
  }
}
