using BizHawk.Common;
using BizHawk.Common.NumberExtensions;

namespace BizHawk.Emulation.Cores.Nintendo.NES
{
	public sealed class MLT_MAX15 : NES.NESBoardBase
	{
		//http://wiki.nesdev.com/w/index.php/INES_Mapper_234

		bool mode = false;
		int block_high = 0;
		int block_low = 0;
		byte prg_bank = 0;
		byte chr_bank_high = 0;
		byte chr_bank_low = 0;
		int prg_bank_mask_32k = 0;
		int chr_bank_mask_8k = 0;
		bool reg_0_locked = false;
		public override bool Configure(NES.EDetectionOrigin origin)
		{
			switch (Cart.board_type)
			{
				case "MAPPER234":
				case "MLT-MAXI15":
					break;
				default:
					return false;
			}

			prg_bank_mask_32k = Cart.prg_size / 32 - 1;
			chr_bank_mask_8k = Cart.chr_size / 8 - 1;

			return true;
		}

		public override void NESSoftReset()
		{
			mode = false;
			block_high = 0;
			block_low = 0;
			prg_bank = 0;
			chr_bank_high = 0;
			reg_0_locked = false;
			base.NESSoftReset();
			SetMirrorType(EMirrorType.Vertical);
		}

		public override void SyncState(Serializer ser)
		{
			ser.Sync("mode", ref mode);
			ser.Sync("block_high", ref block_high);
			ser.Sync("block_low", ref block_low);
			ser.Sync("prg_bank", ref prg_bank);
			ser.Sync("chr_bank", ref chr_bank_high);
			ser.Sync("reg_0_locked", ref reg_0_locked);
			base.SyncState(ser);
		}

		public override void WritePRG(int addr, byte value)
		{
			if (addr < 0x7F80)
			{
				base.WritePRG(addr, value);
			}
			else
			{
				switch (addr & 0x7FF8)
				{
					case 0x7F80:
					case 0x7F88:
					case 0x7F90:
					case 0x7F98:
						if (!reg_0_locked)
						{
							if (value > 0)
							{
								reg_0_locked = true;
							}
							block_high = (value >> 1) & 0x07;
							block_low = value & 0x01;
							mode = value.Bit(6);
							if (value.Bit(7))
							{
								SetMirrorType(EMirrorType.Horizontal);
							}
							else
							{
								SetMirrorType(EMirrorType.Vertical);
							}
						}
						break;
					case 0x7FC0:
					case 0x7FC8:
					case 0x7FD0:
					case 0x7FD8:
						//Console.WriteLine("This mapper is doing undocumented register writes!");
						break;
					case 0x7FE8:
					case 0x7FF0:
						prg_bank = (byte)(value & 0x01);
						chr_bank_high = (byte)((value >> 4) & 0x03);
						chr_bank_low = (byte)((value >> 6) & 0x01);
						//Console.WriteLine("chr_lw: {0}, chr_hg: {1}, value: {2}", chr_bank_low, chr_bank_high, value);
						break;
				}
			}
		}

		public override byte ReadPRG(int addr)
		{
			int bank;
			if (mode)
			{
				bank = (block_high << 1) | prg_bank;
			}
			else
			{
				bank = (block_high << 1) | block_low;
			}
			
			byte value = ROM[((bank & prg_bank_mask_32k) * 0x8000) + (addr & 0x7FFF)];

			if (addr >= 0x7F80)
			{
				WritePRG(addr, value);
			}
			
			return value;
			
		}

		public override byte ReadPPU(int addr)
		{
			if (addr < 0x2000)
			{
				int bank;
				if (mode)
				{
					bank = (block_high << 3) | (chr_bank_low << 2) | chr_bank_high;
				}
				else
				{
					bank = (block_high << 3) | (block_low << 2) | chr_bank_high;
				}

				return VROM[((bank & chr_bank_mask_8k) * 0x2000) + (addr & 0x1FFF)];
			}
			return base.ReadPPU(addr);
		}
	}
}
