/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 1998 BERO
 *  Copyright (C) 2002 Ben Parnell
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*  Code for emulating iNES mappers 4, 118,119 */

#include "mapinc.h"
#include "unif.h"

#define resetmode mapbyte1[0]
#define MMC3_cmd mapbyte1[1]
#define A000B mapbyte1[2]
#define A001B mapbyte1[3]
#define DRegBuf mapbyte4

#define TKSMIR mapbyte3
#define PPUCHRBus mapbyte2[0]

static int mmc3opts=0;

static INLINE void FixMMC3PRG(int V);
static INLINE void FixMMC3CHR(int V);
static void FixMMC3CHRTKS(int V);
static void FixMMC3CHRTQ(int V);

DECLFW(MMC3_IRQWrite)
{
        switch(A&0xE001)
        {
         case 0xc000:IRQLatch=V;
                     if(resetmode==1)
                      IRQCount=IRQLatch;
                     break;
         case 0xc001:resetmode=1;
                     IRQCount=IRQLatch;
                     break;
         case 0xE000:IRQa=0;X6502_IRQEnd(FCEU_IQEXT);
                     if(resetmode==1)
                      {IRQCount=IRQLatch;}
                     break;
         case 0xE001:IRQa=1;
                     if(resetmode==1)
                       {IRQCount=IRQLatch;}
                     break;
        }
}

static INLINE void FixMMC3PRG(int V)
{
          if(V&0x40)
           {
            setprg8(0xC000,DRegBuf[6]);
            setprg8(0x8000,~1);
           }
          else
           {
            setprg8(0x8000,DRegBuf[6]);
            setprg8(0xC000,~1);
           }
}

static INLINE void FixMMC3CHR(int V)
{
           int cbase=(V&0x80)<<5;
           setchr2((cbase^0x000),DRegBuf[0]>>1);
           setchr2((cbase^0x800),DRegBuf[1]>>1);
           setchr1(cbase^0x1000,DRegBuf[2]);
           setchr1(cbase^0x1400,DRegBuf[3]);
           setchr1(cbase^0x1800,DRegBuf[4]);
           setchr1(cbase^0x1c00,DRegBuf[5]);
}

static void MMC3RegReset(void)
{
 IRQCount=IRQLatch=IRQa=MMC3_cmd=0;

 DRegBuf[0]=0;
 DRegBuf[1]=2;
 DRegBuf[2]=4;
 DRegBuf[3]=5;
 DRegBuf[4]=6;
 DRegBuf[5]=7;
 DRegBuf[6]=0;
 DRegBuf[7]=1;

 FixMMC3PRG(0);
 if(mmc3opts&0x10)
  FixMMC3CHRTKS(0);
 else if(mmc3opts&0x20)
  FixMMC3CHRTQ(0);
 else
  FixMMC3CHR(0);
 setprg8(0xA000,DRegBuf[7]);
 setprg8(0xE000,~0);
}

DECLFW(Mapper4_write)
{
        switch(A&0xE001)
	{
         case 0x8000:
          if((V&0x40) != (MMC3_cmd&0x40))
	   FixMMC3PRG(V);
          if((V&0x80) != (MMC3_cmd&0x80))
           FixMMC3CHR(V);
          MMC3_cmd = V;
          break;

        case 0x8001:
                {
                 int cbase=(MMC3_cmd&0x80)<<5;
                 DRegBuf[MMC3_cmd&0x7]=V;
                 switch(MMC3_cmd&0x07)
                 {
                  case 0: V>>=1;setchr2((cbase^0x000),V);break;
                  case 1: V>>=1;setchr2((cbase^0x800),V);break;
                  case 2: setchr1(cbase^0x1000,V); break;
                  case 3: setchr1(cbase^0x1400,V); break;
                  case 4: setchr1(cbase^0x1800,V); break;
                  case 5: setchr1(cbase^0x1C00,V); break;
                  case 6: if (MMC3_cmd&0x40) setprg8(0xC000,V);
                          else setprg8(0x8000,V);
                          break;
                  case 7: setprg8(0xA000,V);
                          break;
                 }
                }
                break;

        case 0xA000:
		A000B=V;
		setmirror((V&1)^1);
                break;
 }
}

void MMC3_hb(void)
{
      resetmode=0;
      if(IRQCount>=0)
      {
        IRQCount--;
        if(IRQCount<0)
        {
         if(IRQa)
         {
            resetmode = 1;
	    X6502_IRQBegin(FCEU_IQEXT);
         }
        }
      }
}
static int isines;

static void genmmc3restore(int version)
{
 if(version>=56)
 {
  setmirror((A000B&1)^1);
  setprg8(0xA000,DRegBuf[7]);
  FixMMC3PRG(MMC3_cmd);
  if(mmc3opts&0x10)
   FixMMC3CHRTKS(MMC3_cmd);
  else if(mmc3opts&0x20)
   FixMMC3CHRTQ(MMC3_cmd);
  else
   FixMMC3CHR(MMC3_cmd);
 }
 else if(isines)
  iNESStateRestore(version);
}

void Mapper4_init(void)
{
 A000B=(Mirroring&1)^1; // For hard-wired mirroring on some MMC3 games.
                        // iNES format needs to die or be extended...
 mmc3opts=0;
 SetWriteHandler(0x8000,0xBFFF,Mapper4_write);
 SetWriteHandler(0xC000,0xFFFF,MMC3_IRQWrite);

 GameHBIRQHook=MMC3_hb;
 GameStateRestore=genmmc3restore;
 if(!VROM_size)
  SetupCartCHRMapping(0, CHRRAM, 8192, 1);
 isines=1;
}

static DECLFW(Mapper250_write)
{
        V=A&0xFF;
        A=(A&0xF800)&((0xF000)|((A&0x400)<<1));
        A=(A&0xF000)|((A&0x800)>>11);
	Mapper4_write(A,V);
}

static DECLFW(M250_IRQWrite)
{
        V=A&0xFF;
        A=(A&0xF800)&((0xF000)|((A&0x400)<<1));
        A=(A&0xF000)|((A&0x800)>>11);
	MMC3_IRQWrite(A,V);
}

void Mapper250_init(void)
{
 A000B=(Mirroring&1)^1; // For hard-wired mirroring on some MMC3 games.
                        // iNES format needs to die or be extended...
 mmc3opts=0;
 SetWriteHandler(0x8000,0xBFFF,Mapper250_write);
 SetWriteHandler(0xC000,0xFFFF,M250_IRQWrite);

 GameHBIRQHook=MMC3_hb;
 GameStateRestore=genmmc3restore;
 if(!VROM_size)
  SetupCartCHRMapping(0, CHRRAM, 8192, 1);
 isines=1;
}

static void FP_FASTAPASS(1) TKSPPU(uint32 A)
{
 static int last=-1;
 static uint8 z;

 A>>=13;
 PPUCHRBus=A;
 z=TKSMIR[A];

 if(z!=last)
 {
  setmirror(MI_0+z);
  last=z;
 }
}

static void tksmir(void)
{
 setmirror(MI_0+TKSMIR[PPUCHRBus]);
}

static void FixMMC3CHRTKS(int V)
{
           int cbase=(V&0x80)<<5;
	   int x;

           setchr2((cbase^0x000),DRegBuf[0]>>1);
           setchr2((cbase^0x800),DRegBuf[1]>>1);
	   TKSMIR[(cbase>>10)^0]=TKSMIR[(cbase>>10)^1]=(DRegBuf[0]&0x80)>>7;
	   TKSMIR[(cbase>>10)^2]=TKSMIR[(cbase>>10)^3]=(DRegBuf[1]&0x80)>>7;
	   for(x=0;x<4;x++)
	   {
	    setchr1(cbase^(x*0x400),DRegBuf[2+x]);
	    TKSMIR[(cbase>>10)^(0x4+x)]=(DRegBuf[2+x]&0x80)>>7;
	   }
	   tksmir();
}

DECLFW(Mapper118_write)
{
        switch(A&0xE001)
	{
        case 0x8000:
	if((V&0x40) != (MMC3_cmd&0x40))
	 FixMMC3PRG(V);
        if((V&0x80) != (MMC3_cmd&0x80))
	 FixMMC3CHRTKS(V);	 
        MMC3_cmd = V;
        break;

        case 0x8001:
                {
                int cbase=(MMC3_cmd&0x80)<<5;
                DRegBuf[MMC3_cmd&0x7]=V;
                switch(MMC3_cmd&0x7)
                {
                case 0:TKSMIR[(cbase>>10)^0]=TKSMIR[(cbase>>10)^1]=(V&0x80)>>7;
		       tksmir();
		       setchr2(cbase^0x000,V>>1);
		       break;
                case 1:TKSMIR[(cbase>>10)^2]=TKSMIR[(cbase>>10)^3]=(V&0x80)>>7;
		       tksmir();
		       setchr2(cbase^0x800,V>>1);
		       break;
                case 2:TKSMIR[(cbase>>10)^0x4]=(V&0x80)>>7;
		       tksmir();
		       setchr1(cbase^0x1000,V);
		       break;
                case 3:TKSMIR[(cbase>>10)^0x5]=(V&0x80)>>7;
		       tksmir();
		       setchr1(cbase^0x1400,V);
		       break;
                case 4:TKSMIR[(cbase>>10)^0x6]=(V&0x80)>>7;
		       tksmir();
		       setchr1(cbase^0x1800,V);
		       break;
                case 5:TKSMIR[(cbase>>10)^0x7]=(V&0x80)>>7;
		       tksmir();
		       setchr1(cbase^0x1C00,V);
		       break;
                case 6:
                        if (MMC3_cmd&0x40) setprg8(0xC000,V);
                        else setprg8(0x8000,V);
                        break;
                case 7: setprg8(0xA000,V);
                        break;
               }
               }
               break;
 }
}

void Mapper118_init(void)
{
  mmc3opts=0x10;
  SetWriteHandler(0x8000,0xbfff,Mapper118_write);
  SetWriteHandler(0xC000,0xFFFF,MMC3_IRQWrite);
  GameHBIRQHook=MMC3_hb;
  GameStateRestore=genmmc3restore;
  PPU_hook=TKSPPU;
  isines=1;
}

static void FixMMC3CHRTQ(int V)
{
          int cbase=(V&0x80)<<5;
          int x;

          for(x=0;x<2;x++)
           setchr2r((DRegBuf[x]&0x40)>>2,(x*0x800)^cbase,(DRegBuf[x]&0x3f)>>1);

          for(x=2;x<6;x++)
           setchr1r((DRegBuf[x]&0x40)>>2,((x-2)*0x400+0x1000)^cbase,DRegBuf[x]&0x3F);
}

DECLFW(Mapper119_write)
{
        switch(A&0xE001)
	{
        case 0x8000:
        if((V&0x40) != (MMC3_cmd&0x40))
         FixMMC3PRG(V);
        if((V&0x80) != (MMC3_cmd&0x80))
	 FixMMC3CHRTQ(V);
        MMC3_cmd = V;        
        break;

        case 0x8001:
                {
                 int cbase=(MMC3_cmd&0x80)<<5;
                 DRegBuf[MMC3_cmd&0x7]=V;
                 switch(MMC3_cmd&0x07)
		 {
                 case 0: 
			setchr2r((V&0x40)>>2,cbase,(V&0x3F)>>1);
                        break;
                 case 1: 
			setchr2r((V&0x40)>>2,cbase^0x800,(V&0x3F)>>1);
                        break;
                 case 2:
			setchr1r((V&0x40)>>2,cbase^0x1000,V&0x3F);
                        break;
                 case 3:
			setchr1r((V&0x40)>>2,cbase^0x1400,V&0x3F);
                        break;
                 case 4:
			setchr1r((V&0x40)>>2,cbase^0x1800,V&0x3F);
                        break;
                 case 5:
			setchr1r((V&0x40)>>2,cbase^0x1c00,V&0x3F);
                        break;
                 case 6:
                        if (MMC3_cmd&0x40) setprg8(0xC000,V);
                        else setprg8(0x8000,V);
                        break;
                 case 7: setprg8(0xA000,V);
                        break;
                }
               }
               break;
        case 0xA000:
		A000B=V;
                setmirror((V&1)^1);
                break;
 }
}

void Mapper119_init(void)
{
  mmc3opts=0x20;
  SetWriteHandler(0x8000,0xbfff,Mapper119_write);
  SetWriteHandler(0xC000,0xFFFF,MMC3_IRQWrite);
  GameHBIRQHook=MMC3_hb;
  SetupCartCHRMapping(0x10, CHRRAM, 8192, 1);
  GameStateRestore=genmmc3restore;
  isines=1;
}

static int wrams;

static void GenMMC3Close(void)
{
 UNIFOpenWRAM(UOW_WR,0,1);
 UNIFWriteWRAM(WRAM,wrams);
 UNIFCloseWRAM();
}

static DECLFW(MBWRAM)
{
  (WRAM-0x6000)[A]=V;
}

static DECLFR(MAWRAM)
{
 return((WRAM-0x6000)[A]);
}

static DECLFW(MBWRAMMMC6)
{
 WRAM[A&0x3ff]=V;
}

static DECLFR(MAWRAMMMC6)
{
 return(WRAM[A&0x3ff]);
}

static void GenMMC3Reset(void)
{
 if(mmc3opts&0x10)
  SetWriteHandler(0x8000,0xBFFF,Mapper118_write);
 else if(mmc3opts&0x20)
  SetWriteHandler(0x8000,0xBFFF,Mapper119_write);
 else
  SetWriteHandler(0x8000,0xBFFF,Mapper4_write);
 SetReadHandler(0x8000,0xFFFF,CartBR);
 SetWriteHandler(0xC000,0xFFFF,MMC3_IRQWrite);
 if(mmc3opts&1)
 {
  if(wrams==1024)
  {
   SetReadHandler(0x7000,0x7FFF,MAWRAMMMC6);
   SetWriteHandler(0x7000,0x7FFF,MBWRAMMMC6);
  }
  else
  {
   SetReadHandler(0x6000,0x6000+wrams-1,MAWRAM);
   SetWriteHandler(0x6000,0x6000+wrams-1,MBWRAM);
  }
 }
 MMC3RegReset();
}

static void GenMMC3Power(void)
{
 if(mmc3opts&1)
 {
  if(wrams==1024)
   FCEU_CheatAddRAM(1,0x7000,WRAM);
  else
   FCEU_CheatAddRAM(wrams/1024,0x6000,WRAM);
  if(!(mmc3opts&2))
   FCEU_dwmemset(WRAM,0,wrams);
 }
 GenMMC3Reset();
}

void GenMMC3_Init(int prg, int chr, int attrib, int wram, int battery)
{
 wrams=wram*1024;
 mmc3opts=attrib; // &0x10 == tks/tls, &0x20 == tq

 PRGmask8[0]&=(prg>>13)-1;
 CHRmask1[0]&=(chr>>10)-1;
 CHRmask2[0]&=(chr>>11)-1;

 if(wram)
 {
  mmc3opts|=1;
  AddExState(WRAM, wram*1024, 0, "WRAM");
 }

 if(battery)
 {
  mmc3opts|=2;
  BoardClose=GenMMC3Close;

  UNIFOpenWRAM(UOW_RD,0,1);
  UNIFReadWRAM(WRAM,wram*1024);
  UNIFCloseWRAM();
 }

 if(!chr)
 {
  CHRmask1[0]=7;
  CHRmask2[0]=3;
  SetupCartCHRMapping(0, CHRRAM, 8192, 1);
  AddExState(CHRRAM, 8192, 0, "CHRR");
 }
 AddExState(mapbyte1, 32, 0, "MPBY");
 AddExState(&IRQa, 1, 0, "IRQA");
 AddExState(&IRQCount, 4, 1, "IRQC");
 AddExState(&IRQLatch, 4, 1, "IQL1");

 BoardPower=GenMMC3Power;

 GameHBIRQHook=MMC3_hb;
 GameStateRestore=genmmc3restore;
 isines=0;
}

// void GenMMC3_Init(int prg, int chr, int attrib, int wram, int battery)

void TFROM_Init(void)
{
 GenMMC3_Init(512, 64, 0, 0, 0);
}

void TGROM_Init(void)
{
 GenMMC3_Init(512, 0, 0, 0, 0);
}

void TKROM_Init(void)
{
 GenMMC3_Init(512, 256, 0, 8, 1);
}

void TLROM_Init(void)
{
 GenMMC3_Init(512, 256, 0, 0, 0);
}

void TSROM_Init(void)
{
 GenMMC3_Init(512, 256, 0, 8, 0);
}

void TLSROM_Init(void)
{
 GenMMC3_Init(512, 256, 0x10, 8, 0);
}

void TKSROM_Init(void)
{
 GenMMC3_Init(512, 256, 0x10, 8, 1);
}

void TQROM_Init(void)
{
 GenMMC3_Init(512, 64, 0x20, 0, 0);
}

/* MMC6 board */
void HKROM_Init(void)
{
 GenMMC3_Init(512, 512, 0, 1, 1);
}
