struct MMC3 : Chip {
  MMC3(Board& board) : Chip(board) {
  }

  auto main() -> void {
    if(irqDelay) irqDelay--;
    cpu.irqLine(irqLine);
    tick();
  }

  auto irqTest(uint addr) -> void {
    if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
      if(irqDelay == 0) {
        if(irqCounter == 0) {
          irqCounter = irqLatch;
        } else if(--irqCounter == 0) {
          if(irqEnable) irqLine = 1;
        }
      }
      irqDelay = 6;
    }
    chrAbus = addr;
  }

  auto addrPRG(uint addr) const -> uint {
    switch((addr >> 13) & 3) {
    case 0:
      if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
      return (prgBank[0] << 13) | (addr & 0x1fff);
    case 1:
      return (prgBank[1] << 13) | (addr & 0x1fff);
    case 2:
      if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
      return (prgBank[0] << 13) | (addr & 0x1fff);
    case 3:
      return (0x3f << 13) | (addr & 0x1fff);
    }
  }

  auto addrCHR(uint addr) const -> uint {
    if(chrMode == 0) {
      if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
      if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
      if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
      if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
      if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
      if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
    } else {
      if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
      if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
      if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
      if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
      if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
      if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
    }
  }

  auto addrCIRAM(uint addr) const -> uint {
    if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
    if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
  }

  auto readRAM(uint addr) -> uint8 {
    if(ramEnable) return board.prgram.data[addr & 0x1fff];
    return 0x00;
  }

  auto writeRAM(uint addr, uint8 data) -> void {
    if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
  }

  auto writeIO(uint addr, uint8 data) -> void {
    switch(addr & 0xe001) {
    case 0x8000:
      chrMode = data & 0x80;
      prgMode = data & 0x40;
      bankSelect = data & 0x07;
      break;

    case 0x8001:
      switch(bankSelect) {
      case 0: chrBank[0] = data & ~1; break;
      case 1: chrBank[1] = data & ~1; break;
      case 2: chrBank[2] = data; break;
      case 3: chrBank[3] = data; break;
      case 4: chrBank[4] = data; break;
      case 5: chrBank[5] = data; break;
      case 6: prgBank[0] = data & 0x3f; break;
      case 7: prgBank[1] = data & 0x3f; break;
      }
      break;

    case 0xa000:
      mirror = data & 0x01;
      break;

    case 0xa001:
      ramEnable = data & 0x80;
      ramWriteProtect = data & 0x40;
      break;

    case 0xc000:
      irqLatch = data;
      break;

    case 0xc001:
      irqCounter = 0;
      break;

    case 0xe000:
      irqEnable = false;
      irqLine = 0;
      break;

    case 0xe001:
      irqEnable = true;
      break;
    }
  }

  auto power() -> void {
  }

  auto reset() -> void {
    chrMode = 0;
    prgMode = 0;
    bankSelect = 0;
    prgBank[0] = 0;
    prgBank[1] = 0;
    chrBank[0] = 0;
    chrBank[1] = 0;
    chrBank[2] = 0;
    chrBank[3] = 0;
    chrBank[4] = 0;
    chrBank[5] = 0;
    mirror = 0;
    ramEnable = 1;
    ramWriteProtect = 0;
    irqLatch = 0;
    irqCounter = 0;
    irqEnable = false;
    irqDelay = 0;
    irqLine = 0;

    chrAbus = 0;
  }

  auto serialize(serializer& s) -> void {
    s.integer(chrMode);
    s.integer(prgMode);
    s.integer(bankSelect);
    s.array(prgBank);
    s.array(chrBank);
    s.integer(mirror);
    s.integer(ramEnable);
    s.integer(ramWriteProtect);
    s.integer(irqLatch);
    s.integer(irqCounter);
    s.integer(irqEnable);
    s.integer(irqDelay);
    s.integer(irqLine);

    s.integer(chrAbus);
  }

  bool chrMode;
  bool prgMode;
  uint3 bankSelect;
  uint8 prgBank[2];
  uint8 chrBank[6];
  bool mirror;
  bool ramEnable;
  bool ramWriteProtect;
  uint8 irqLatch;
  uint8 irqCounter;
  bool irqEnable;
  uint irqDelay;
  bool irqLine;

  uint16 chrAbus;
};
