content: /*
   * HalfNES by Andrew Hoffman
   * Licensed under the GNU GPL Version 3. See LICENSE file
   */
  package com.grapeshot.halfnes.mappers;
  
  import com.grapeshot.halfnes.*;
  
  /**
   *
   * @author Andrew
   */
  public class TengenRamboMapper extends Mapper {
  
      private int whichbank = 0;
      private boolean prgconfig = false, chrconfig = false,
              chrmode1k = false, irqmode = false;
      private int irqctrreload = 0;
      private int irqctr = 0;
      private boolean irqenable = false;
      private boolean irqreload = false;
      private int prgreg0 = 0, prgreg1 = 0, prgreg2 = 0;
      private int[] chrreg = new int[8];
      private boolean interrupted = false;
  
      @Override
      public void loadrom() throws BadMapperException {
          //needs to be in every mapper. Fill with initial cfg
          super.loadrom();
          //on startup:
          for (int i = 0; i < 8; ++i) {
              prg_map[i] = (1024 * i);
              prg_map[i + 8] = (1024 * i);
              //yes this actually matters; MMC3 does NOT start up in a random state
              //(at least Smash TV and TMNT3 expect certain banks w/o even setting up mapper)
          }
          for (int i = 1; i <= 32; ++i) {
              prg_map[32 - i] = prgsize - (1024 * i);
          }
  
          for (int i = 0; i < 8; ++i) {
              chr_map[i] = 0;
          }
          setprgregs();
          //cpuram.setPrgRAMEnable(false);
      }
  
      @Override
      public final void cartWrite(int addr, int data) {
          if (addr < 0x8000 || addr > 0xffff) {
              super.cartWrite(addr, data);
              return;
          }
          //bankswitches here
          //different register for even/odd writes
          if (((addr & (utils.BIT0)) != 0)) {
              //odd registers
              if ((addr >= 0x8000) && (addr <= 0x9fff)) {
                  //bank change
                  //System.err.println("setting " + whichbank + " " + data + " " + prgconfig);
                  switch (whichbank) {
                      case 0:
                      case 1:
                      case 2:
                      case 3:
                      case 4:
                      case 5:
                          chrreg[whichbank] = data;
                          setupchr();
                          break;
                      case 6:
                          prgreg0 = data;
                          setprgregs();
                          break;
                      case 7:
                          //bank 7 always swappable, always in same place
                          prgreg1 = data;
                          setprgregs();
                          break;
                      case 8:
                      case 9:
                          //setup extra chr banks;
                          chrreg[whichbank - 2] = data;
                          break;
                      case 0xf:
                          prgreg2 = data;
                          setprgregs();
                          break;
                  }
              } else if ((addr >= 0xA000) && (addr <= 0xbfff)) {
                  //prg ram write protect
                  //cpuram.setPrgRAMEnable(!utils.getbit(data, 7));
              } else if ((addr >= 0xc000) && (addr <= 0xdfff)) {
                  //any value here reloads irq counter
                  irqreload = true;
                  irqmode = ((data & (utils.BIT0)) != 0);
              } else if ((addr >= 0xe000) && (addr <= 0xffff)) {
                  //iany value here enables interrupts
                  irqenable = true;
              }
          } else {
              //even registers
              if ((addr >= 0x8000) && (addr <= 0x9fff)) {
                  //bank select
                  whichbank = data & 0xf;
                  chrmode1k = ((data & (utils.BIT5)) != 0);
                  prgconfig = ((data & (utils.BIT6)) != 0);
                  //if bit is false, 8000-9fff swappable and c000-dfff fixed to 2nd to last bank
                  //if bit is true, c000-dfff swappable and 8000-9fff fixed to 2nd to last bank
                  chrconfig = ((data & (utils.BIT7)) != 0);
                  //if false: 2 2k banks @ 0000-0fff, 4 1k banks in 1000-1fff
                  //if true: 4 1k banks @ 0000-0fff, 2 2k banks @ 1000-1fff
                  setupchr();
                  setprgregs();
              } else if ((addr >= 0xA000) && (addr <= 0xbfff)) {
                  //mirroring setup
                  if (scrolltype != MirrorType.FOUR_SCREEN_MIRROR) {
                      setmirroring(((data & (utils.BIT0)) != 0) ? MirrorType.H_MIRROR : MirrorType.V_MIRROR);
                  }
              } else if ((addr >= 0xc000) && (addr <= 0xdfff)) {
                  //value written here used to reload irq counter _@ end of scanline_
                  irqctrreload = data;
                  irqreload = true;
              } else if ((addr >= 0xe000) && (addr <= 0xffff)) {
                  //any value here disables IRQ and acknowledges
                  if (interrupted) {
                      --cpu.interrupt;
                  }
                  interrupted = false;
                  irqenable = false;
                  irqctr = irqctrreload;
              }
          }
      }
  
      private void setupchr() {
          if (chrconfig) {
              if (chrmode1k) {
                  setppubank(1, 0, chrreg[2]);
                  setppubank(1, 1, chrreg[3]);
                  setppubank(1, 2, chrreg[4]);
                  setppubank(1, 3, chrreg[5]);
                  setppubank(1, 4, chrreg[0]);
                  setppubank(1, 5, chrreg[6]);
                  setppubank(1, 6, chrreg[1]);
                  setppubank(1, 7, chrreg[7]);
              } else {
                  setppubank(1, 0, chrreg[2]);
                  setppubank(1, 1, chrreg[3]);
                  setppubank(1, 2, chrreg[4]);
                  setppubank(1, 3, chrreg[5]);
                  //Lowest bit of bank number IS IGNORED for the 2k banks
                  setppubank(2, 4, (chrreg[0] >> 1) << 1);
                  setppubank(2, 6, (chrreg[1] >> 1) << 1);
              }
          } else {
              if (chrmode1k) {
                  setppubank(1, 0, chrreg[0]);
                  setppubank(1, 1, chrreg[6]);
                  setppubank(1, 2, chrreg[1]);
                  setppubank(1, 3, chrreg[7]);
                  setppubank(1, 4, chrreg[2]);
                  setppubank(1, 5, chrreg[3]);
                  setppubank(1, 6, chrreg[4]);
                  setppubank(1, 7, chrreg[5]);
              } else {
                  setppubank(1, 4, chrreg[2]);
                  setppubank(1, 5, chrreg[3]);
                  setppubank(1, 6, chrreg[4]);
                  setppubank(1, 7, chrreg[5]);
  
                  setppubank(2, 0, (chrreg[0] >> 1) << 1);
                  setppubank(2, 2, (chrreg[1] >> 1) << 1);
              }
          }
      }
  
      private void setprgregs() {
          //no matter what, c000-dfff is last bank
          if (!prgconfig) {
              //map r6 to first 8k, r7 to 2nd, rf to 3rd
              for (int i = 0; i < 8; ++i) {
                  prg_map[i] = (1024 * (i + (prgreg0 * 8))) % prgsize;
                  prg_map[i + 8] = (1024 * (i + (prgreg1 * 8))) % prgsize;
                  prg_map[i + 16] = (1024 * (i + (prgreg2 * 8))) % prgsize;
              }
          } else {
              //map rf to 1st 8k, r6 to 2nd, r7 to 3rd
              for (int i = 0; i < 8; ++i) {
                  prg_map[i] = (1024 * (i + (prgreg2 * 8))) % prgsize;
                  prg_map[i + 8] = (1024 * (i + (prgreg0 * 8))) % prgsize;
                  prg_map[i + 16] = (1024 * (i + (prgreg1 * 8))) % prgsize;
              }
          }
      }
  
      @Override
      public void notifyscanline(int scanline) {
          if (irqmode) {
              return;
          }
          //Scanline counter
          if (scanline > 239 && scanline != 261) {
              //clocked on LAST line of vblank and all lines of frame. Not on 240.
              return;
          }
          if (!ppu.mmc3CounterClocking()) {
              return;
          }
          clockscanlinecounter();
      }
      int remainder;
      boolean intnextcycle = false;
  
      @Override
      public void cpucycle(int cycles) {
          if (intnextcycle) {
              intnextcycle = false;
              if (!interrupted) {
                  ++cpu.interrupt;
                  interrupted = true;
              }
          }
          if (!irqmode) {
              return;
          }
          remainder += cycles;
          for (int i = 0; i < remainder; ++i) {
              if ((i & 3) == 0) {
                  clockscanlinecounter();
              }
          }
          remainder %= 4;
      }
  
      public void clockscanlinecounter() {
          if (irqreload) {
              irqreload = false;
              irqctr = irqctrreload + 1;
          } else if (irqctr == 0) {
              irqctr = irqctrreload;
          } else {
              if (--irqctr == 0 && irqenable) {
                  intnextcycle = true;
              }
          }
      }
  
      private void setppubank(int banksize, int bankpos, int banknum) {
  //        System.err.println(banksize + ", " + bankpos + ", "+ banknum);
          for (int i = 0; i < banksize; ++i) {
              chr_map[i + bankpos] = (1024 * ((banknum) + i)) % chrsize;
          }
      }
  }
  