using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

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

namespace BizHawk.Emulation.Cores.Nintendo.NES
{
	// describes Sachen8259A/B/C.  D is in a different class
	// behavior from fceumm

	public class Sachen8259ABC : NES.NESBoardBase
	{
		// config
		int prg_bank_mask_32k;
		int chr_bank_mask_2k;

		int shiftout; // reg lines are shifted on the PCB to increase capacity
		int shiftmask;

		// state
		int port; // register that gets written next
		int prg; // 32K prg swap
		int[] chr = new int[4]; // 6 bits of chr, 3 from an outer bank
		bool simple; // when true, we're in some sort of "simplified" mode

		public override bool Configure(NES.EDetectionOrigin origin)
		{
			switch (Cart.board_type)
			{
				// quite a few crappy games on these boards, shouldn't be hard to find examples?
				case "MAPPER141":
				case "UNIF_UNL-Sachen-8259A":
				case "SACHEN-8259A":
					shiftout = 1; // 256KiB chr max
					break;
				case "MAPPER138":
				case "UNIF_UNL-Sachen-8259B":
				case "SACHEN-8259B":
					shiftout = 0; // 128KiB chr max
					break;
				case "MAPPER139":
				case "UNIF_UNL-Sachen-8259C":
				case "SACHEN-8259C":
					shiftout = 2; // 512KiB chr max
					break;
				default:
					return false;
			}
			Cart.wram_size = 0; // cart responds to regs in 6000:7fff

			//zero 13-dec-2014 - Q-boy is example of game with vram, apparently.
			//lets only clear vram if theres a chr rom
			if(Cart.chr_size != 0)
				Cart.vram_size = 0;

			shiftmask = (1 << shiftout) - 1;
			prg_bank_mask_32k = Cart.prg_size / 32 - 1;
			chr_bank_mask_2k = Cart.chr_size / 2 - 1;

			SetMirrorType(EMirrorType.Vertical);

			return true;
		}

		public override void WriteEXP(int addr, byte value)
		{
			Write(addr, value);
		}
		public override void WriteWRAM(int addr, byte value)
		{
			Write(addr, value);
		}
		void Write(int addr, byte value)
		{
			addr &= 0x0101;
			if (addr == 0x100)
			{
				port = value & 7;
			}
			else
			{
				switch (port)
				{
					case 0:
					case 1:
					case 2:
					case 3:
						// low 3 bits
						chr[port] &= 0x38;
						chr[port] |= value & 7;
						break;
					case 4:
						// outer bank high 3 bits
						for (int i = 0; i < 4; i++)
						{
							chr[i] &= 0x07;
							chr[i] |= (value & 7) << 3;
						}
						break;
					case 5:
						prg = value & 7;
						break;
					case 6: // unused?
						break;
					case 7:
						simple = value.Bit(0);
						if (simple)
						{
							SetMirrorType(EMirrorType.Vertical);
						}
						else
						{
							switch (value & 6)
							{
								case 0: SetMirrorType(EMirrorType.Vertical); break;
								case 2: SetMirrorType(EMirrorType.Horizontal); break;
								case 4: SetMirroring(0, 1, 1, 1); break;
								case 6: SetMirrorType(EMirrorType.OneScreenA); break;
							}
						}
						break;
				}
			}
		}

		public override byte ReadPRG(int addr)
		{
			int bank = prg & prg_bank_mask_32k;
			return ROM[addr | bank << 15];
		}
		public override byte ReadPPU(int addr)
		{
			if (addr < 0x2000)
			{
				if(VROM == null)
					return base.ReadPPU(addr);

				int idx = addr >> 11;
				// in addition to fixing V-mirroring, simple fixes us to 1 bank
				// this means for type C, simple has 1 8KiB chr bank
				int bank = chr[simple ? 0 : idx];
				// on the PCB, all of the CHR addr lines are shifted up,
				// while the low lines that were lost are connected directly to PPU A lines
				bank <<= shiftout;
				bank |= idx & shiftmask;
				bank &= chr_bank_mask_2k;
				return VROM[addr & 0x7ff | bank << 11];
			}
			else
			{
				return base.ReadPPU(addr);
			}
		}

		public override void SyncState(Serializer ser)
		{
			base.SyncState(ser);
			ser.Sync("port", ref port);
			ser.Sync("prg", ref prg);
			ser.Sync("chr", ref chr, false);
			ser.Sync("simple", ref simple);
		}
	}





	// similar in some ways to 8259ABC, but different
	// fceumm combines the code to implement them; i think that's too messy
	
	// this mapper is stupid and was most certainly made for 1 game.  it has an awkward
	// chr mapping that can only support up to 32KiB chr rom, and it uses it to show
	// a few famous persons portraits while playing a stupid block game

	// i think there's something wrong with the mapper implementation; but the game
	// sucks so hard, it's hard to tell
	public class Sachen8259D : NES.NESBoardBase
	{
		// config
		int prg_bank_mask_32k;
		int chr_bank_mask_1k;

		// state
		int port;
		int prg;
		int[] chr = new int[8];

		public override bool Configure(NES.EDetectionOrigin origin)
		{
			switch (Cart.board_type)
			{
				// only game i'm aware of is "The Great Wall"
				case "MAPPER137":
				case "UNIF_UNL-Sachen-8259D":
				case "SACHEN-8259D":
					break;
				default:
					return false;
			}
			Cart.wram_size = 0; // cart responds to regs in 6000:7fff
			Cart.vram_size = 0;
			prg_bank_mask_32k = Cart.prg_size / 32 - 1;
			chr_bank_mask_1k = Cart.chr_size / 1 - 1;

			// last 4k of chr is fixed
			chr[4] = 0x1c;
			chr[5] = 0x1d;
			chr[6] = 0x1e;
			chr[7] = 0x1f;

			SetMirrorType(EMirrorType.Vertical);
			return true;
		}

		public override void WriteEXP(int addr, byte value)
		{
			Write(addr, value);
		}
		public override void WriteWRAM(int addr, byte value)
		{
			Write(addr, value);
		}
		void Write(int addr, byte value)
		{
			addr &= 0x0101;
			if (addr == 0x100)
			{
				port = value & 7;
			}
			else
			{
				switch (port)
				{
					case 0:
					case 1:
					case 2:
					case 3:
						// low 3 bits
						chr[port] &= 0x18;
						chr[port] |= value & 7;
						break;
					case 4:
						// a single high bit for chr regs 1:3
						// note that this bit goes in bit #4, not #3
						chr[1] &= 0x0f;
						chr[1] |= (value & 1) << 4;
						chr[2] &= 0x0f;
						chr[2] |= (value & 2) << 3;
						chr[3] &= 0x0f;
						chr[3] |= (value & 4) << 2;
						break;
					case 5:
						prg = value & 7;
						break;
					case 6:
						// one more single bit of chr for 1 reg
						// so only chr[3] is 5 full bits
						chr[3] &= 0x17;
						chr[3] |= (value & 1) << 3;
						break;
					case 7:
						// as in A\B\C, but we don't need to store "simple"
						if (value.Bit(0))
						{
							SetMirrorType(EMirrorType.Vertical);
						}
						else
						{
							switch (value & 6)
							{
								case 0: SetMirrorType(EMirrorType.Vertical); break;
								case 2: SetMirrorType(EMirrorType.Horizontal); break;
								case 4: SetMirroring(0, 1, 1, 1); break;
								case 6: SetMirrorType(EMirrorType.OneScreenA); break;
							}
						}
						break;
				}
			}
		}

		public override byte ReadPRG(int addr)
		{
			int bank = prg & prg_bank_mask_32k;
			return ROM[addr | bank << 15];
		}
		public override byte ReadPPU(int addr)
		{
			if (addr < 0x2000)
			{
				int bank = chr[addr >> 10] & chr_bank_mask_1k;
				return VROM[addr & 0x3ff | bank << 10];
			}
			else
			{
				return base.ReadPPU(addr);
			}
		}

		public override void SyncState(Serializer ser)
		{
			base.SyncState(ser);
			ser.Sync("port", ref port);
			ser.Sync("prg", ref prg);
			ser.Sync("chr", ref chr, false);
		}

	}
}
