using System;
using BizHawk.Emulation.Common;

namespace BizHawk.Emulation.Cores.Intellivision
{
	public sealed class STIC : IVideoProvider
	{
		private bool Sr1, Sr2, Sst, Fgbg = false;
		private ushort[] Register = new ushort[64];
		private ushort ColorSP = 0x0028;

		public int TotalExecutedCycles;
		public int PendingCycles;

		public Func<ushort, ushort> ReadMemory;
		public Func<ushort, ushort, bool> WriteMemory;

		public int[] FrameBuffer = new int[159 * 96];

		public int[] GetVideoBuffer()
		{
			Background();
			Mobs();
			return FrameBuffer; 
		}

		public int VirtualWidth { get { return 159; } }
		public int BufferWidth { get { return 159; } }
		public int VirtualHeight { get { return 192; } }
		public int BufferHeight { get { return 96; } }
		public int BackgroundColor { get { return 0; } }
		
		public void Reset()
		{
			Sr1 = true;
			Sr2 = true;
		}

		public bool GetSr1()
		{
			return Sr1;
		}

		public bool GetSr2()
		{
			return Sr2;
		}

		public void SetSst(bool value)
		{
			Sst = value;
		}

		public ushort? ReadSTIC(ushort addr)
		{
			switch (addr & 0xF000)
			{
				case 0x0000:
					if (addr <= 0x003F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x0021)
						{
							Fgbg = false;
						}
						return Register[addr];
					}
					else if (addr <= 0x007F)
					{
						// TODO: OK only during VBlank Period 2.
						return Register[addr - 0x0040];
					}
					break;
				case 0x4000:
					if (addr <= 0x403F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x4021)
						{
							Fgbg = false;
						}
					}
					break;
				case 0x8000:
					if (addr <= 0x803F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x8021)
						{
							Fgbg = false;
						}
					}
					break;
				case 0xC000:
					if (addr <= 0xC03F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0xC021)
						{
							Fgbg = false;
						}
					}
					break;
			}
			return null;
		}

		public bool WriteSTIC(ushort addr, ushort value)
		{
			switch (addr & 0xF000)
			{
				case 0x0000:
					if (addr <= 0x003F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x0021)
						{
							Fgbg = true;
						}
						Register[addr] = value;
						return true;
					}
					else if (addr <= 0x007F)
					{
						// Read-only STIC.
						break;
					}
					break;
				case 0x4000:
					if (addr <= 0x403F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x4021)
						{
							Fgbg = true;
						}
						Register[addr - 0x4000] = value;
						return true;
					}
					break;
				case 0x8000:
					if (addr <= 0x803F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0x8021)
						{
							Fgbg = true;
						}
						Register[addr & 0x003F] = value;
						return true;
					}
					break;
				case 0xC000:
					if (addr <= 0xC03F)
					{
						// TODO: OK only during VBlank Period 1.
						if (addr == 0xC021)
						{
							Fgbg = true;
						}
						Register[addr - 0xC000] = value;
						return true;
					}
					break;
			}
			return false;
		}

		public void Execute(int cycles)
		{
			PendingCycles -= cycles;
			TotalExecutedCycles += cycles;
			if (PendingCycles <= 0)
			{
				Sr1 = !Sr1;
				if (Sr1)
				{
					PendingCycles = 14934 - 3791;
				}
				else
				{
					PendingCycles += 3791;
				}
			}
		}

		public int ColorToRGBA(int color)
		{
			switch (color)
			{
				case 0:
					return 0x000000;
				case 1:
					return 0x002DFF;
				case 2:
					return 0xFF3D10;
				case 3:
					return 0xC9CFAB;
				case 4:
					return 0x386B3F;
				case 5:
					return 0x00A756;
				case 6:
					return 0xFAEA50;
				case 7:
					return 0xFFFCFF;
				case 8:
					return 0xBDACC8;
				case 9:
					return 0x24B8FF;
				case 10:
					return 0xFFB41F;
				case 11:
					return 0x546E00;
				case 12:
					return 0xFF4E57;
				case 13:
					return 0xA496FF;
				case 14:
					return 0x75CC80;
				case 15:
					return 0xB51A58;
			}
			throw new ArgumentException("Specified color does not exist.");
		}

		public void Background()
		{
			// The background is a 20x12 grid of "cards".
			for (int card_row = 0; card_row < 12; card_row++)
			{
				for (int card_col = 0; card_col < 20; card_col++)
				{
					int buffer_offset = (card_row * 159 * 8) + (card_col * 8);
					// The cards are stored sequentially in the System RAM.
					ushort card = ReadMemory((ushort)(0x0200 + (card_row * 20) + card_col));
					// Parse data from the card.
					bool gram = ((card & 0x0800) != 0);
					int card_num = card >> 3;
					int fg = card & 0x0007;
					int bg;
					if (Fgbg)
					{
						bg = ((card >> 9) & 0x0008) | ((card >> 11) & 0x0004) | ((card >> 9) & 0x0003);
						// Only 64 of the GROM's cards can be used in FGBG Mode.
						card_num &= 0x003F;
					}
					else
					{
						bool advance = ((card & 0x2000) != 0);
						bool squares = ((card & 0x1000) != 0);
						if (gram)
						{
							// GRAM only has 64 cards.
							card_num &= 0x003F;
							// The foreground color has an additional bit when not in Colored Squares mode.
							if (squares)
								fg |= 0x0008;
						}
						else
						{
							// All of the GROM's 256 cards can be used in Color Stack Mode.
							card_num &= 0x00FF;
						}
						if (!gram && squares)
						{
							// Colored Squares Mode.
							int[] colors = new int[4];
							colors[0] = fg;
							colors[1] = (card >> 3) & 0x0007;
							colors[2] = (card >> 6) & 0x0007;
							colors[3] = ((card >> 11) & 0x0004) | ((card >> 9) & 0x0003);
							for (int squares_row = 0; squares_row < 8; squares_row++)
							{
								for (int squares_col = 0; squares_col < 8; squares_col++)
								{
									// The rightmost column does not get displayed.
									if (card_col == 19 && squares_col == 7)
									{
										continue;
									}
									int color;
									int pixel = buffer_offset + (squares_row * 159) + squares_col;
									// Determine the color of the quadrant the pixel is in.
									if (squares_col < 4)
									{
										if (squares_row < 4)
										{
											color = 0;
										}
										else
										{
											color = 2;
										}
									}
									else
									{
										if (squares_row < 4)
										{
											color = 1;
										}
										else
										{
											color = 3;
										}
									}
									FrameBuffer[pixel] = ColorToRGBA(colors[color]);
								}
							}
							continue;
						}
						else
						{
							if (advance)
							{
								// Cycle through the Color Stack registers.
								ColorSP++;
								if (ColorSP > 0x002B)
								{
									ColorSP = 0x0028;
								}
							}
							bg = ReadMemory(ColorSP) & 0x000F;
						}
					}
					for (int pict_row = 0; pict_row < 8; pict_row++)
					{
						// Each picture is stored sequentially in the GROM / GRAM, and so are their rows.
						int row_mem = (card_num * 8) + pict_row;
						byte row;
						if (gram)
						{
							row = (byte)ReadMemory((ushort)(0x3800 + row_mem));
						}
						else
						{
							row = (byte)ReadMemory((ushort)(0x3000 + row_mem));
						}
						for (int pict_col = 0; pict_col < 8; pict_col++)
						{
							// The rightmost column does not get displayed.
							if (card_col == 19 && pict_col == 0)
							{
								continue;
							}
							int pixel = buffer_offset + (pict_row * 159) + (7 - pict_col);
							// If the pixel is on, give it the FG color.
							if ((row & 0x1) != 0)
							{
								// The pixels go right as the bits get less significant.
								FrameBuffer[pixel] = ColorToRGBA(fg);
							}
							else
							{
								FrameBuffer[pixel] = ColorToRGBA(bg);
							}
							row >>= 1;
						}
					}
				}
			}
		}

		public void Mobs()
		{
			// TODO
		}
	}
}
