//////////////////////////////////////////////////////////////////////////
// Mapper001  Nintendo MMC1                                             //
//////////////////////////////////////////////////////////////////////////
using static VirtualNes.MMU;
using BYTE = System.Byte;
using INT = System.Int32;


namespace VirtualNes.Core
{
    public class Mapper001 : Mapper
    {
        ushort last_addr;

        BYTE patch;
        BYTE wram_patch;
        BYTE wram_bank;
        BYTE wram_count;

        BYTE[] reg = new byte[4];
        BYTE shift, regbuf;

        public Mapper001(NES parent) : base(parent) { }

        public override void Reset()
        {
            reg[0] = 0x0C;      // D3=1,D2=1
            reg[1] = reg[2] = reg[3] = 0;
            shift = regbuf = 0;

            patch = 0;
            wram_patch = 0;

            if (PROM_16K_SIZE < 32)
            {
                SetPROM_32K_Bank(0, 1, PROM_8K_SIZE - 2, PROM_8K_SIZE - 1);
            }
            else
            {
                // For 512K/1M byte Cartridge
                SetPROM_16K_Bank(4, 0);
                SetPROM_16K_Bank(6, 16 - 1);

                patch = 1;
            }

            if (VROM_8K_SIZE != 0)
            {
                //		SetVROM_8K_Bank( 0 );
            }

            uint crc = nes.rom.GetPROM_CRC();

            if (crc == 0xb8e16bd0)
            {   // Snow Bros.(J)
                patch = 2;
            }
            //	if( crc == 0x9b565541 ) {	// Triathron, The(J)
            //		nes.SetFrameIRQmode( FALSE );
            //	}
            if (crc == 0xc96c6f04)
            {   // Venus Senki(J)
                nes.SetRenderMethod(EnumRenderMethod.POST_ALL_RENDER);
            }
            //	if( crc == 0x5e3f7004 ) {	// Softball Tengoku(J)
            //	}

            if (crc == 0x4d2edf70)
            {   // Night Rider(J)
                nes.SetRenderMethod(EnumRenderMethod.TILE_RENDER);
            }
            if (crc == 0xcd2a73f0)
            {   // Pirates!(U)
                nes.SetRenderMethod(EnumRenderMethod.TILE_RENDER);
                patch = 2;
            }

            //	if( crc == 0x09efe54b ) {	// Majaventure - Mahjong Senki(J)
            //		nes.SetFrameIRQmode( FALSE );
            //	}

            if (crc == 0x11469ce3)
            {   // Viva! Las Vegas(J)
            }
            if (crc == 0xd878ebf5)
            {   // Ninja Ryukenden(J)
                nes.SetRenderMethod(EnumRenderMethod.POST_ALL_RENDER);
            }

            //	if( crc == 0x7bd7b849 ) {	// Nekketsu Koukou - Dodgeball Bu(J)
            //	}

            if (crc == 0x466efdc2)
            {   // Final Fantasy(J)
                nes.SetRenderMethod(EnumRenderMethod.TILE_RENDER);
                nes.ppu.SetExtMonoMode(true);
            }
            if (crc == 0xc9556b36)
            {   // Final Fantasy I&II(J)
                nes.SetRenderMethod(EnumRenderMethod.TILE_RENDER);
                nes.ppu.SetExtMonoMode(true);
                nes.SetSAVERAM_SIZE(16 * 1024);
                wram_patch = 2;
            }

            if (crc == 0x717e1169)
            {   // Cosmic Wars(J)
                nes.SetRenderMethod(EnumRenderMethod.PRE_ALL_RENDER);
            }

            if (crc == 0xC05D2034)
            {   // Snake's Revenge(U)
                nes.SetRenderMethod(EnumRenderMethod.PRE_ALL_RENDER);
            }

            if (crc == 0xb8747abf       // Best Play - Pro Yakyuu Special(J)
             || crc == 0x29449ba9       // Nobunaga no Yabou - Zenkoku Ban(J)
             || crc == 0x2b11e0b0       // Nobunaga no Yabou - Zenkoku Ban(J)(alt)
             || crc == 0x4642dda6       // Nobunaga's Ambition(U)
             || crc == 0xfb69743a       // Aoki Ookami to Shiroki Mejika - Genghis Khan(J)
             || crc == 0x2225c20f       // Genghis Khan(U)
             || crc == 0xabbf7217       // Sangokushi(J)
            )
            {

                nes.SetSAVERAM_SIZE(16 * 1024);
                wram_patch = 1;
                wram_bank = 0;
                wram_count = 0;
            }
        }

        //void Mapper001::Write(WORD addr, BYTE data)
        public override void Write(ushort addr, byte data)
        {
            //	DEBUGOUT( "MMC1 %04X=%02X\n", addr&0xFFFF,data&0xFF );

            if (wram_patch == 1 && addr == 0xBFFF)
            {
                wram_count++;
                wram_bank += (byte)(data & 0x01);
                if (wram_count == 5)
                {
                    if (wram_bank != 0)
                    {
                        SetPROM_Bank(3, new ArrayRef<byte>(WRAM, 0x2000), BANKTYPE_RAM);
                    }
                    else
                    {
                        SetPROM_Bank(3, new ArrayRef<byte>(WRAM, 0x0000), BANKTYPE_RAM);
                    }
                    wram_bank = wram_count = 0;
                }
            }

            if (patch != 1)
            {
                if ((addr & 0x6000) != (last_addr & 0x6000))
                {
                    shift = regbuf = 0;
                }
                last_addr = addr;
            }

            if ((data & 0x80) != 0)
            {
                shift = regbuf = 0;
                //		reg[0] = 0x0C;		// D3=1,D2=1
                reg[0] |= 0x0C;     // D3=1,D2=1 残りはリセットされない
                return;
            }

            if ((data & 0x01) != 0) regbuf |= (byte)(1 << shift);
            if (++shift < 5)
                return;
            addr = (ushort)((addr & 0x7FFF) >> 13);
            reg[addr] = regbuf;

            //	DEBUGOUT( "MMC1 %d=%02X\n", addr&0xFFFF,regbuf&0xFF );

            regbuf = 0;
            shift = 0;

            if (patch != 1)
            {
                // For Normal Cartridge
                switch (addr)
                {
                    case 0:
                        if ((reg[0] & 0x02) != 0)
                        {
                            if ((reg[0] & 0x01) != 0) SetVRAM_Mirror(VRAM_HMIRROR);
                            else SetVRAM_Mirror(VRAM_VMIRROR);
                        }
                        else
                        {
                            if ((reg[0] & 0x01) != 0) SetVRAM_Mirror(VRAM_MIRROR4H);
                            else SetVRAM_Mirror(VRAM_MIRROR4L);
                        }
                        break;
                    case 1:
                        // Register #1
                        if (VROM_1K_SIZE != 0)
                        {
                            if ((reg[0] & 0x10) != 0)
                            {
                                // CHR 4K bank lower($0000-$0FFF)
                                SetVROM_4K_Bank(0, reg[1]);
                                // CHR 4K bank higher($1000-$1FFF)
                                SetVROM_4K_Bank(4, reg[2]);
                            }
                            else
                            {
                                // CHR 8K bank($0000-$1FFF)
                                SetVROM_8K_Bank(reg[1] >> 1);
                            }
                        }
                        else
                        {
                            // For Romancia
                            if ((reg[0] & 0x10) != 0)
                            {
                                SetCRAM_4K_Bank(0, reg[1]);
                            }
                        }
                        break;
                    case 2:
                        // Register #2
                        if (VROM_1K_SIZE != 0)
                        {
                            if ((reg[0] & 0x10) != 0)
                            {
                                // CHR 4K bank lower($0000-$0FFF)
                                SetVROM_4K_Bank(0, reg[1]);
                                // CHR 4K bank higher($1000-$1FFF)
                                SetVROM_4K_Bank(4, reg[2]);
                            }
                            else
                            {
                                // CHR 8K bank($0000-$1FFF)
                                SetVROM_8K_Bank(reg[1] >> 1);
                            }
                        }
                        else
                        {
                            // For Romancia
                            if ((reg[0] & 0x10) != 0)
                            {
                                SetCRAM_4K_Bank(4, reg[2]);
                            }
                        }
                        break;
                    case 3:
                        if (!((reg[0] & 0x08) != 0))
                        {
                            // PRG 32K bank ($8000-$FFFF)
                            SetPROM_32K_Bank(reg[3] >> 1);
                        }
                        else
                        {
                            if ((reg[0] & 0x04) != 0)
                            {
                                // PRG 16K bank ($8000-$BFFF)
                                SetPROM_16K_Bank(4, reg[3]);
                                SetPROM_16K_Bank(6, PROM_16K_SIZE - 1);
                            }
                            else
                            {
                                // PRG 16K bank ($C000-$FFFF)
                                SetPROM_16K_Bank(6, reg[3]);
                                SetPROM_16K_Bank(4, 0);
                            }
                        }
                        break;
                }
            }
            else
            {
                // For 512K/1M byte Cartridge
                INT PROM_BASE = 0;
                if (PROM_16K_SIZE >= 32)
                {
                    PROM_BASE = reg[1] & 0x10;
                }

                // For FinalFantasy I&II
                if (wram_patch == 2)
                {
                    if (((reg[1] & 0x18) == 0))
                    {
                        SetPROM_Bank(3, new ArrayRef<byte>(WRAM, 0x0000), BANKTYPE_RAM);
                    }
                    else
                    {
                        SetPROM_Bank(3, new ArrayRef<byte>(WRAM, 0x2000), BANKTYPE_RAM);
                    }
                }

                // Register #0
                if (addr == 0)
                {
                    if ((reg[0] & 0x02) != 0)
                    {
                        if ((reg[0] & 0x01) != 0) SetVRAM_Mirror(VRAM_HMIRROR);
                        else SetVRAM_Mirror(VRAM_VMIRROR);
                    }
                    else
                    {
                        if ((reg[0] & 0x01) != 0) SetVRAM_Mirror(VRAM_MIRROR4H);
                        else SetVRAM_Mirror(VRAM_MIRROR4L);
                    }
                }
                // Register #1
                if (VROM_1K_SIZE != 0)
                {
                    if ((reg[0] & 0x10) != 0)
                    {
                        // CHR 4K bank lower($0000-$0FFF)
                        SetVROM_4K_Bank(0, reg[1]);
                    }
                    else
                    {
                        // CHR 8K bank($0000-$1FFF)
                        SetVROM_8K_Bank(reg[1] >> 1);
                    }
                }
                else
                {
                    // For Romancia
                    if ((reg[0] & 0x10) != 0)
                    {
                        SetCRAM_4K_Bank(0, reg[1]);
                    }
                }
                // Register #2
                if (VROM_1K_SIZE != 0)
                {
                    if ((reg[0] & 0x10) != 0)
                    {
                        // CHR 4K bank higher($1000-$1FFF)
                        SetVROM_4K_Bank(4, reg[2]);
                    }
                }
                else
                {
                    // For Romancia
                    if ((reg[0] & 0x10) != 0)
                    {
                        SetCRAM_4K_Bank(4, reg[2]);
                    }
                }
                // Register #3
                if (((reg[0] & 0x08) == 0))
                {
                    // PRG 32K bank ($8000-$FFFF)
                    SetPROM_32K_Bank((reg[3] & (0xF + PROM_BASE)) >> 1);
                }
                else
                {
                    if ((reg[0] & 0x04) != 0)
                    {
                        // PRG 16K bank ($8000-$BFFF)
                        SetPROM_16K_Bank(4, PROM_BASE + (reg[3] & 0x0F));
                        if (PROM_16K_SIZE >= 32) SetPROM_16K_Bank(6, PROM_BASE + 16 - 1);
                    }
                    else
                    {
                        // PRG 16K bank ($C000-$FFFF)
                        SetPROM_16K_Bank(6, PROM_BASE + (reg[3] & 0x0F));
                        if (PROM_16K_SIZE >= 32) SetPROM_16K_Bank(4, PROM_BASE);
                    }
                }
            }
        }

        //void Mapper001::SaveState(LPBYTE p)
        public override void SaveState(byte[] p)
        {
            p[0] = reg[0];
            p[1] = reg[1];
            p[2] = reg[2];
            p[3] = reg[3];
            p[4] = shift;
            p[5] = regbuf;

            p[6] = wram_bank;
            p[7] = wram_count;
        }

        //void Mapper001::LoadState(LPBYTE p)
        public override void LoadState(byte[] p)
        {
            reg[0] = p[0];
            reg[1] = p[1];
            reg[2] = p[2];
            reg[3] = p[3];
            shift = p[4];
            regbuf = p[5];

            wram_bank = p[6];
            wram_count = p[7];
        }

        public override bool IsStateSave()
        {
            return true;
        }
    }
}