using System.IO;

namespace MyNes.Core
{
    [BoardInfo("Pirate MMC5-style", 90)]
    [HassIssues]
    internal class Mapper090 : Board
    {
    	protected bool MAPPER90MODE;

    	private int[] prg_reg;

    	private int[] chr_reg;

    	private int[] nt_reg;

    	private int prg_mode;

    	private int chr_mode;

    	private bool chr_block_mode;

    	private int chr_block;

    	private bool chr_m;

    	private bool flag_s;

    	private int irqCounter;

    	private bool IrqEnable;

    	private bool irqCountDownMode;

    	private bool irqCountUpMode;

    	private bool irqFunkyMode;

    	private bool irqPrescalerSize;

    	private int irqSource;

    	private int irqPrescaler;

    	private int irqPrescalerXOR;

    	private byte irqFunkyModeReg;

    	private byte Dipswitch;

    	private byte multiplication_a;

    	private byte multiplication_b;

    	private ushort multiplication;

    	private byte RAM5803;

    	private bool nt_advanced_enable;

    	private bool nt_rom_only;

    	private int nt_ram_select;

    	internal override string Issues => MNInterfaceLanguage.IssueMapper90;

    	internal override void HardReset()
    	{
    		base.HardReset();
    		MAPPER90MODE = true;
    		prg_reg = new int[4];
    		chr_reg = new int[8];
    		nt_reg = new int[4];
    		prg_mode = (chr_mode = 0);
    		for (int i = 0; i < 4; i++)
    		{
    			prg_reg[i] = i;
    			nt_reg[i] = i;
    		}
    		for (int j = 0; j < 8; j++)
    		{
    			chr_reg[j] = j;
    		}
    		SetupPRG();
    		SetupCHR();
    		Dipswitch = 0;
    		irqCounter = 0;
    		IrqEnable = false;
    		irqCountDownMode = false;
    		irqCountUpMode = false;
    		irqFunkyMode = false;
    		irqPrescalerSize = false;
    		irqSource = 0;
    		irqPrescaler = 0;
    		irqPrescalerXOR = 0;
    		irqFunkyModeReg = 0;
    		RAM5803 = 0;
    		flag_s = false;
    		multiplication_a = 0;
    		multiplication_b = 0;
    		multiplication = 0;
    	}

    	internal override void SoftReset()
    	{
    		base.SoftReset();
    		if (Dipswitch == 0)
    		{
    			Dipswitch = byte.MaxValue;
    		}
    		else
    		{
    			Dipswitch = 0;
    		}
    	}

    	internal override void WritePRG(ref ushort address, ref byte data)
    	{
    		switch (address & 0xF007)
    		{
    		case 32768:
    		case 32769:
    		case 32770:
    		case 32771:
    		case 32772:
    		case 32773:
    		case 32774:
    		case 32775:
    			prg_reg[address & 3] = data & 0x7F;
    			SetupPRG();
    			break;
    		case 36864:
    		case 36865:
    		case 36866:
    		case 36867:
    		case 36868:
    		case 36869:
    		case 36870:
    		case 36871:
    			chr_reg[address & 7] = (chr_reg[address & 7] & 0xFF00) | data;
    			SetupCHR();
    			break;
    		case 40960:
    		case 40961:
    		case 40962:
    		case 40963:
    		case 40964:
    		case 40965:
    		case 40966:
    		case 40967:
    			chr_reg[address & 7] = (chr_reg[address & 7] & 0xFF) | (data << 8);
    			SetupCHR();
    			break;
    		case 45056:
    		case 45057:
    		case 45058:
    		case 45059:
    			nt_reg[address & 3] = (nt_reg[address & 3] & 0xFF00) | data;
    			break;
    		case 45060:
    		case 45061:
    		case 45062:
    		case 45063:
    			nt_reg[address & 3] = (nt_reg[address & 3] & 0xFF) | (data << 8);
    			break;
    		case 49152:
    			IrqEnable = (data & 1) == 1;
    			if (!IrqEnable)
    			{
    				NesEmu.IRQFlags &= -9;
    			}
    			break;
    		case 49153:
    			irqCountDownMode = (data & 0x80) == 128;
    			irqCountUpMode = (data & 0x40) == 64;
    			irqFunkyMode = (data & 8) == 8;
    			irqPrescalerSize = (data & 4) == 4;
    			irqSource = data & 3;
    			break;
    		case 49154:
    			IrqEnable = false;
    			NesEmu.IRQFlags &= -9;
    			break;
    		case 49155:
    			IrqEnable = true;
    			break;
    		case 49156:
    			irqPrescaler = data ^ irqPrescalerXOR;
    			break;
    		case 49157:
    			irqCounter = data ^ irqPrescalerXOR;
    			break;
    		case 49158:
    			irqPrescalerXOR = data;
    			break;
    		case 49159:
    			irqFunkyModeReg = data;
    			break;
    		case 53248:
    			flag_s = (data & 0x80) == 128;
    			prg_mode = data & 7;
    			chr_mode = (data >> 3) & 3;
    			nt_advanced_enable = (data & 0x20) == 32;
    			nt_rom_only = (data & 0x40) == 64;
    			SetupPRG();
    			SetupCHR();
    			break;
    		case 53249:
    			switch (data & 3)
    			{
    			case 0:
    				Switch01KNMTFromMirroring(Mirroring.Vert);
    				break;
    			case 1:
    				Switch01KNMTFromMirroring(Mirroring.Horz);
    				break;
    			case 2:
    				Switch01KNMTFromMirroring(Mirroring.OneScA);
    				break;
    			case 3:
    				Switch01KNMTFromMirroring(Mirroring.OneScB);
    				break;
    			}
    			break;
    		case 53250:
    			nt_ram_select = data & 0x80;
    			break;
    		case 53251:
    			chr_m = (data & 0x80) == 128;
    			chr_block_mode = (data & 0x20) == 32;
    			chr_block = (data & 0x1F) << 8;
    			SetupCHR();
    			break;
    		}
    	}

    	internal override void WriteSRM(ref ushort address, ref byte data)
    	{
    	}

    	internal override void ReadSRM(ref ushort address, out byte data)
    	{
    		if (flag_s)
    		{
    			base.ReadSRM(ref address, out data);
    		}
    		else
    		{
    			data = 0;
    		}
    	}

    	internal override void ReadEX(ref ushort address, out byte data)
    	{
    		switch (address)
    		{
    		case 20480:
    			data = Dipswitch;
    			break;
    		case 22528:
    			data = (byte)(multiplication & 0xFFu);
    			break;
    		case 22529:
    			data = (byte)((multiplication & 0xFF00) >> 8);
    			break;
    		case 22531:
    			data = RAM5803;
    			break;
    		default:
    			data = 0;
    			break;
    		}
    	}

    	internal override void WriteEX(ref ushort address, ref byte data)
    	{
    		switch (address)
    		{
    		case 22528:
    			multiplication_a = data;
    			multiplication = (ushort)(multiplication_a * multiplication_b);
    			break;
    		case 22529:
    			multiplication_b = data;
    			multiplication = (ushort)(multiplication_a * multiplication_b);
    			break;
    		case 22531:
    			RAM5803 = data;
    			break;
    		case 22530:
    			break;
    		}
    	}

    	internal override void ReadNMT(ref ushort address, out byte data)
    	{
    		if (MAPPER90MODE)
    		{
    			data = NMT_RAM[NMT_AREA_BLK_INDEX[(address >> 10) & 3]][address & 0x3FF];
    		}
    		if (!nt_advanced_enable)
    		{
    			data = NMT_RAM[NMT_AREA_BLK_INDEX[(address >> 10) & 3]][address & 0x3FF];
    		}
    		else if (nt_rom_only)
    		{
    			data = CHR_ROM[nt_reg[(address >> 10) & 3]][address & 0x3FF];
    		}
    		else if ((nt_reg[(address >> 10) & 3] & 0x80) != nt_ram_select)
    		{
    			data = CHR_ROM[nt_reg[(address >> 10) & 3]][address & 0x3FF];
    		}
    		else
    		{
    			data = NMT_RAM[nt_reg[(address >> 10) & 3] & 1][address & 0x3FF];
    		}
    	}

    	internal override void WriteNMT(ref ushort address, ref byte data)
    	{
    		if (MAPPER90MODE)
    		{
    			NMT_RAM[NMT_AREA_BLK_INDEX[(address >> 10) & 3]][address & 0x3FF] = data;
    		}
    		else if (!nt_advanced_enable)
    		{
    			NMT_RAM[NMT_AREA_BLK_INDEX[(address >> 10) & 3]][address & 0x3FF] = data;
    		}
    		else if (!nt_rom_only && (nt_reg[(address >> 10) & 3] & 0x80) == nt_ram_select)
    		{
    			NMT_RAM[nt_reg[(address >> 10) & 3] & 1][address & 0x3FF] = data;
    		}
    	}

    	private void SetupPRG()
    	{
    		switch (prg_mode)
    		{
    		case 0:
    			Switch08KPRG(prg_reg[3] * 4 + 3, PRGArea.Area6000);
    			Switch32KPRG(PRG_ROM_32KB_Mask, PRGArea.Area8000);
    			break;
    		case 1:
    			Switch08KPRG(prg_reg[3] * 2 + 1, PRGArea.Area6000);
    			Switch16KPRG(prg_reg[1], PRGArea.Area8000);
    			Switch16KPRG(PRG_ROM_16KB_Mask, PRGArea.AreaC000);
    			break;
    		case 2:
    			Switch08KPRG(prg_reg[3], PRGArea.Area6000);
    			Switch08KPRG(prg_reg[0], PRGArea.Area8000);
    			Switch08KPRG(prg_reg[1], PRGArea.AreaA000);
    			Switch08KPRG(prg_reg[2], PRGArea.AreaC000);
    			Switch08KPRG(PRG_ROM_08KB_Mask, PRGArea.AreaE000);
    			break;
    		case 3:
    			Switch08KPRG(ReverseByte(prg_reg[3]), PRGArea.Area6000);
    			Switch08KPRG(ReverseByte(prg_reg[0]), PRGArea.Area8000);
    			Switch08KPRG(ReverseByte(prg_reg[1]), PRGArea.AreaA000);
    			Switch08KPRG(ReverseByte(prg_reg[2]), PRGArea.AreaC000);
    			Switch08KPRG(PRG_ROM_16KB_Mask, PRGArea.AreaE000);
    			break;
    		case 4:
    			Switch08KPRG(prg_reg[3] * 4 + 3, PRGArea.Area6000);
    			Switch32KPRG(prg_reg[3], PRGArea.Area8000);
    			break;
    		case 5:
    			Switch08KPRG(prg_reg[3] * 2 + 1, PRGArea.Area6000);
    			Switch16KPRG(prg_reg[1], PRGArea.Area8000);
    			Switch16KPRG(prg_reg[3], PRGArea.AreaC000);
    			break;
    		case 6:
    			Switch08KPRG(prg_reg[3], PRGArea.Area6000);
    			Switch08KPRG(prg_reg[0], PRGArea.Area8000);
    			Switch08KPRG(prg_reg[1], PRGArea.AreaA000);
    			Switch08KPRG(prg_reg[2], PRGArea.AreaC000);
    			Switch08KPRG(prg_reg[3], PRGArea.AreaE000);
    			break;
    		case 7:
    			Switch08KPRG(ReverseByte(prg_reg[3]), PRGArea.Area6000);
    			Switch08KPRG(ReverseByte(prg_reg[0]), PRGArea.Area8000);
    			Switch08KPRG(ReverseByte(prg_reg[1]), PRGArea.AreaA000);
    			Switch08KPRG(ReverseByte(prg_reg[2]), PRGArea.AreaC000);
    			Switch08KPRG(ReverseByte(prg_reg[3]), PRGArea.AreaE000);
    			break;
    		}
    	}

    	private void SetupCHR()
    	{
    		switch (chr_mode)
    		{
    		case 0:
    			if (chr_block_mode)
    			{
    				Switch08KCHR(chr_reg[0]);
    			}
    			else
    			{
    				Switch08KCHR((chr_reg[0] & 0xFF) | chr_block);
    			}
    			break;
    		case 1:
    			if (chr_block_mode)
    			{
    				Switch04KCHR(chr_reg[0], CHRArea.Area0000);
    				Switch04KCHR(chr_reg[4], CHRArea.Area1000);
    			}
    			else
    			{
    				Switch04KCHR((chr_reg[0] & 0xFF) | chr_block, CHRArea.Area0000);
    				Switch04KCHR((chr_reg[4] & 0xFF) | chr_block, CHRArea.Area1000);
    			}
    			break;
    		case 2:
    			if (chr_block_mode)
    			{
    				Switch02KCHR(chr_reg[0], CHRArea.Area0000);
    				Switch02KCHR(chr_m ? chr_reg[0] : chr_reg[2], CHRArea.Area0800);
    				Switch02KCHR(chr_reg[4], CHRArea.Area1000);
    				Switch02KCHR(chr_reg[6], CHRArea.Area1800);
    			}
    			else
    			{
    				Switch02KCHR((chr_reg[0] & 0xFF) | chr_block, CHRArea.Area0000);
    				Switch02KCHR(((chr_m ? chr_reg[0] : chr_reg[2]) & 0xFF) | chr_block, CHRArea.Area0800);
    				Switch02KCHR((chr_reg[4] & 0xFF) | chr_block, CHRArea.Area1000);
    				Switch02KCHR((chr_reg[6] & 0xFF) | chr_block, CHRArea.Area1800);
    			}
    			break;
    		case 3:
    			if (chr_block_mode)
    			{
    				Switch01KCHR(chr_reg[0], CHRArea.Area0000);
    				Switch01KCHR(chr_reg[1], CHRArea.Area0400);
    				Switch01KCHR(chr_m ? chr_reg[0] : chr_reg[2], CHRArea.Area0800);
    				Switch01KCHR(chr_m ? chr_reg[1] : chr_reg[3], CHRArea.Area0C00);
    				Switch01KCHR(chr_reg[4], CHRArea.Area1000);
    				Switch01KCHR(chr_reg[5], CHRArea.Area1400);
    				Switch01KCHR(chr_reg[6], CHRArea.Area1800);
    				Switch01KCHR(chr_reg[7], CHRArea.Area1C00);
    			}
    			else
    			{
    				Switch01KCHR((chr_reg[0] & 0xFF) | chr_block, CHRArea.Area0000);
    				Switch01KCHR((chr_reg[1] & 0xFF) | chr_block, CHRArea.Area0400);
    				Switch01KCHR(((chr_m ? chr_reg[0] : chr_reg[2]) & 0xFF) | chr_block, CHRArea.Area0800);
    				Switch01KCHR(((chr_m ? chr_reg[1] : chr_reg[3]) & 0xFF) | chr_block, CHRArea.Area0C00);
    				Switch01KCHR((chr_reg[4] & 0xFF) | chr_block, CHRArea.Area1000);
    				Switch01KCHR((chr_reg[5] & 0xFF) | chr_block, CHRArea.Area1400);
    				Switch01KCHR((chr_reg[6] & 0xFF) | chr_block, CHRArea.Area1800);
    				Switch01KCHR((chr_reg[7] & 0xFF) | chr_block, CHRArea.Area1C00);
    			}
    			break;
    		}
    	}

    	private byte ReverseByte(int value)
    	{
    		return (byte)((uint)(((value & 0x40) >> 6) | ((value & 0x20) >> 4) | ((value & 0x10) >> 2)) | ((uint)value & 8u) | (uint)((value & 4) << 2) | (uint)((value & 2) << 4) | (uint)((value & 1) << 6));
    	}

    	internal override void OnCPUClock()
    	{
    		if (irqSource != 0)
    		{
    			return;
    		}
    		if (irqPrescalerSize)
    		{
    			irqPrescaler = (irqPrescaler & 0xF8) | (((irqPrescaler & 7) + 1) & 7);
    			if ((irqPrescaler & 7) == 7)
    			{
    				ClockIRQCounter();
    			}
    		}
    		else
    		{
    			irqPrescaler++;
    			if (irqPrescaler == 255)
    			{
    				ClockIRQCounter();
    			}
    		}
    	}

    	internal override void OnPPUAddressUpdate(ref ushort address)
    	{
    		if (irqSource != 1)
    		{
    			return;
    		}
    		old_vram_address = new_vram_address;
    		new_vram_address = address & 0x1000;
    		if (old_vram_address >= new_vram_address)
    		{
    			return;
    		}
    		if (irqPrescalerSize)
    		{
    			irqPrescaler = (irqPrescaler & 0xF8) | (((irqPrescaler & 7) + 1) & 7);
    			if ((irqPrescaler & 7) == 7)
    			{
    				ClockIRQCounter();
    			}
    		}
    		else
    		{
    			irqPrescaler++;
    			if (irqPrescaler == 255)
    			{
    				ClockIRQCounter();
    			}
    		}
    	}

    	private void ClockIRQCounter()
    	{
    		if (irqCountDownMode && irqCountUpMode)
    		{
    			return;
    		}
    		if (irqCountDownMode)
    		{
    			irqCounter--;
    			if (irqCounter == 0)
    			{
    				irqCounter = 255;
    				if (IrqEnable)
    				{
    					NesEmu.IRQFlags |= 8;
    				}
    			}
    		}
    		else
    		{
    			if (!irqCountUpMode)
    			{
    				return;
    			}
    			irqCounter++;
    			if (irqCounter == 255)
    			{
    				irqCounter = 0;
    				if (IrqEnable)
    				{
    					NesEmu.IRQFlags |= 8;
    				}
    			}
    		}
    	}

    	internal override void WriteStateData(ref BinaryWriter stream)
    	{
    		base.WriteStateData(ref stream);
    		for (int i = 0; i < prg_reg.Length; i++)
    		{
    			stream.Write(prg_reg[i]);
    		}
    		for (int j = 0; j < chr_reg.Length; j++)
    		{
    			stream.Write(chr_reg[j]);
    		}
    		for (int k = 0; k < nt_reg.Length; k++)
    		{
    			stream.Write(nt_reg[k]);
    		}
    		stream.Write(prg_mode);
    		stream.Write(chr_mode);
    		stream.Write(chr_block_mode);
    		stream.Write(chr_block);
    		stream.Write(chr_m);
    		stream.Write(flag_s);
    		stream.Write(irqCounter);
    		stream.Write(IrqEnable);
    		stream.Write(irqCountDownMode);
    		stream.Write(irqCountUpMode);
    		stream.Write(irqFunkyMode);
    		stream.Write(irqPrescalerSize);
    		stream.Write(irqSource);
    		stream.Write(irqPrescaler);
    		stream.Write(irqPrescalerXOR);
    		stream.Write(irqFunkyModeReg);
    		stream.Write(Dipswitch);
    		stream.Write(multiplication_a);
    		stream.Write(multiplication_b);
    		stream.Write(multiplication);
    		stream.Write(RAM5803);
    		stream.Write(nt_advanced_enable);
    		stream.Write(nt_rom_only);
    		stream.Write(nt_ram_select);
    	}

    	internal override void ReadStateData(ref BinaryReader stream)
    	{
    		base.ReadStateData(ref stream);
    		for (int i = 0; i < prg_reg.Length; i++)
    		{
    			prg_reg[i] = stream.ReadInt32();
    		}
    		for (int j = 0; j < chr_reg.Length; j++)
    		{
    			chr_reg[j] = stream.ReadInt32();
    		}
    		for (int k = 0; k < nt_reg.Length; k++)
    		{
    			nt_reg[k] = stream.ReadInt32();
    		}
    		prg_mode = stream.ReadInt32();
    		chr_mode = stream.ReadInt32();
    		chr_block_mode = stream.ReadBoolean();
    		chr_block = stream.ReadInt32();
    		chr_m = stream.ReadBoolean();
    		flag_s = stream.ReadBoolean();
    		irqCounter = stream.ReadInt32();
    		IrqEnable = stream.ReadBoolean();
    		irqCountDownMode = stream.ReadBoolean();
    		irqCountUpMode = stream.ReadBoolean();
    		irqFunkyMode = stream.ReadBoolean();
    		irqPrescalerSize = stream.ReadBoolean();
    		irqSource = stream.ReadInt32();
    		irqPrescaler = stream.ReadInt32();
    		irqPrescalerXOR = stream.ReadInt32();
    		irqFunkyModeReg = stream.ReadByte();
    		Dipswitch = stream.ReadByte();
    		multiplication_a = stream.ReadByte();
    		multiplication_b = stream.ReadByte();
    		multiplication = stream.ReadUInt16();
    		RAM5803 = stream.ReadByte();
    		nt_advanced_enable = stream.ReadBoolean();
    		nt_rom_only = stream.ReadBoolean();
    		nt_ram_select = stream.ReadInt32();
    	}
    }
}