using System;

// Contains rendering functions for TMS9918 Mode 4.

namespace BizHawk.Emulation.Cores.Sega.MasterSystem
{
	public partial class VDP
	{
		internal void RenderBackgroundCurrentLine(bool show)
		{
			if (ScanLine >= FrameHeight)
				return;
			
			if (DisplayOn == false)
			{
				for (int x = 0; x < 256; x++)
					FrameBuffer[(ScanLine * 256) + x] = Palette[BackdropColor];
				return;
			}

			// Clear the priority buffer for this scanline
			Array.Clear(ScanlinePriorityBuffer, 0, 256);

			int mapBase = NameTableBase;

			int vertOffset = ScanLine + Registers[9];
			if (FrameHeight == 192)
			{
				if (vertOffset >= 224)
					vertOffset -= 224;
			}
			else
			{
				if (vertOffset >= 256)
					vertOffset -= 256;
			}
			byte horzOffset = (HorizScrollLock && ScanLine < 16) ? (byte)0 : Registers[8];

			int yTile = vertOffset / 8;

			for (int xTile = 0; xTile < 32; xTile++)
			{
				if (xTile == 24 && VerticalScrollLock)
				{
					vertOffset = ScanLine;
					yTile = vertOffset / 8;
				}

				byte PaletteBase = 0;
				int tileInfo = VRAM[mapBase + ((yTile * 32) + xTile) * 2] | (VRAM[mapBase + (((yTile * 32) + xTile) * 2) + 1] << 8);
				int tileNo = tileInfo & 0x01FF;
				if ((tileInfo & 0x800) != 0)
					PaletteBase = 16;
				bool Priority = (tileInfo & 0x1000) != 0;
				bool VFlip = (tileInfo & 0x400) != 0;
				bool HFlip = (tileInfo & 0x200) != 0;

				int yOfs = vertOffset & 7;
				if (VFlip)
					yOfs = 7 - yOfs;

				if (HFlip == false)
				{
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 0] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 1] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 2] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 3] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 4] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 5] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 6] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 7] + PaletteBase] : Palette[BackdropColor];

					if (Priority)
					{
						horzOffset -= 8;
						for (int k = 0; k < 8; k++)
						{
							if (PatternBuffer[(tileNo * 64) + (yOfs * 8) + k] != 0)
								ScanlinePriorityBuffer[horzOffset] = 1;
							horzOffset++;
						}
					}
				}
				else // Flipped Horizontally
				{
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 7] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 6] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 5] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 4] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 3] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 2] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 1] + PaletteBase] : Palette[BackdropColor];
					FrameBuffer[(ScanLine * 256) + horzOffset++] = show ? Palette[PatternBuffer[(tileNo * 64) + (yOfs * 8) + 0] + PaletteBase] : Palette[BackdropColor];

					if (Priority)
					{
						horzOffset -= 8;
						for (int k = 7; k >= 0; k--)
						{
							if (PatternBuffer[(tileNo * 64) + (yOfs * 8) + k] != 0)
								ScanlinePriorityBuffer[horzOffset] = 1;
							horzOffset++;
						}
					}
				}
			}
		}

		internal void RenderSpritesCurrentLine(bool show)
		{
			bool overflowHappens = true;
			bool collisionHappens = true;
			bool renderHappens = show;

			if (!DisplayOn)
			{
				renderHappens = false;
				collisionHappens = false;
			}
			if (ScanLine >= FrameHeight)
			{
				renderHappens = false;
				overflowHappens = false;
			}

			int SpriteBase = SpriteAttributeTableBase;
			int SpriteHeight = EnableLargeSprites ? 16 : 8;

			// Clear the sprite collision buffer for this scanline
			Array.Clear(SpriteCollisionBuffer, 0, 256);

			// Loop through these sprites and render the current scanline
			int SpritesDrawnThisScanline = 0;
			for (int i = 0; i < 64; i++)
			{
				int x = VRAM[SpriteBase + 0x80 + (i * 2)];
				if (ShiftSpritesLeft8Pixels)
					x -= 8;

				int y = VRAM[SpriteBase + i] + 1;
				if (y == 209 && FrameHeight == 192) 
					break; // 208 is special terminator sprite (in 192-line mode)
				if (y >= (EnableLargeSprites ? 240 : 248)) 
					y -= 256;

				if (y + SpriteHeight <= ScanLine || y > ScanLine)
					continue;

				if (SpritesDrawnThisScanline >= 8)
				{
					collisionHappens = false; // technically the VDP stops processing sprite past this so we would never set the collision bit for sprites past this
					if (overflowHappens)
						StatusByte |= 0x40; // Set Overflow bit
					if (SpriteLimit)
						renderHappens = false; // should be able to break/return, but to ensure this has no effect on sync we keep processing and disable rendering
				}

				int tileNo = VRAM[SpriteBase + 0x80 + (i * 2) + 1];
				if (EnableLargeSprites)
					tileNo &= 0xFE;
				tileNo += SpriteTileBase;

				int ys = ScanLine - y;

				for (int xs = 0; xs < 8 && x + xs < 256; xs++)
				{
					byte color = PatternBuffer[(tileNo * 64) + (ys * 8) + xs];
					if (color != 0 && x + xs >= 0)
					{
						if (SpriteCollisionBuffer[x + xs] != 0)
						{
							if (collisionHappens) 
								StatusByte |= 0x20; // Set Collision bit
						}
						else if (renderHappens && ScanlinePriorityBuffer[x + xs] == 0)
						{
							FrameBuffer[(ys + y) * 256 + x + xs] = Palette[(color + 16)];
						}
						SpriteCollisionBuffer[x + xs] = 1;
					}
				}
				SpritesDrawnThisScanline++;
			}
		}

		internal void RenderSpritesCurrentLineDoubleSize(bool show)
		{
			bool overflowHappens = true;
			bool collisionHappens = true;
			bool renderHappens = show;

			if (!DisplayOn)
			{
				renderHappens = false;
				collisionHappens = false;
			}
			if (ScanLine >= FrameHeight)
			{
				renderHappens = false;
				overflowHappens = false;
			}

			int SpriteBase = SpriteAttributeTableBase;
			int SpriteHeight = EnableLargeSprites ? 16 : 8;

			// Clear the sprite collision buffer for this scanline
			Array.Clear(SpriteCollisionBuffer, 0, 256);

			// Loop through these sprites and render the current scanline
			int SpritesDrawnThisScanline = 0;
			for (int i = 0; i < 64; i++)
			{
				int x = VRAM[SpriteBase + 0x80 + (i * 2)];
				if (ShiftSpritesLeft8Pixels)
					x -= 8;

				int y = VRAM[SpriteBase + i] + 1;
				if (y == 209 && FrameHeight == 192) 
					break; // terminator sprite
				if (y >= (EnableLargeSprites ? 240 : 248)) 
					y -= 256;

				if (y + (SpriteHeight * 2) <= ScanLine || y > ScanLine)
					continue;

				if (SpritesDrawnThisScanline >= 8)
				{
					collisionHappens = false; // technically the VDP stops processing sprite past this so we would never set the collision bit for sprites past this
					if (overflowHappens)
						StatusByte |= 0x40; // Set Overflow bit
					if (SpriteLimit)
						renderHappens = false; // should be able to break/return, but to ensure this has no effect on sync we keep processing and disable rendering
				}

				int tileNo = VRAM[SpriteBase + 0x80 + (i * 2) + 1];
				if (EnableLargeSprites)
					tileNo &= 0xFE;
				tileNo += SpriteTileBase;

				int ys = ScanLine - y;

				for (int xs = 0; xs < 16 && x + xs < 256; xs++)
				{
					byte color = PatternBuffer[(tileNo * 64) + ((ys / 2) * 8) + (xs / 2)];
					if (color != 0 && x + xs >= 0)
					{
						if (SpriteCollisionBuffer[x + xs] != 0)
						{
							if (collisionHappens)
								StatusByte |= 0x20; // Set Collision bit
						}
						else if (renderHappens && ScanlinePriorityBuffer[x + xs] == 0)
						{
							FrameBuffer[(ys + y) * 256 + x + xs] = Palette[(color + 16)];
						}
						SpriteCollisionBuffer[x + xs] = 1;
					}
				}
				SpritesDrawnThisScanline++;
			}
		}

		// Renders left-blanking. Should be done per scanline, not per-frame.
		internal void RenderLineBlanking(bool render)
		{
			if (!LeftBlanking || ScanLine >= FrameHeight || !render) 
				return;

			int ofs = ScanLine * 256;
			for (int x = 0; x < 8; x++)
				FrameBuffer[ofs++] = Palette[BackdropColor];
		}

		// Handles GG clipping or highlighting
		internal void ProcessGGScreen()
		{
			if (mode != VdpMode.GameGear)
				return;

			if (Sms.Settings.ShowClippedRegions == false)
			{
				int yStart = (FrameHeight - 144) / 2;
				for (int y = 0; y < 144; y++)
					for (int x = 0; x < 160; x++)
						GameGearFrameBuffer[(y * 160) + x] = FrameBuffer[((y + yStart) * 256) + x + 48];
			}

			if (Sms.Settings.HighlightActiveDisplayRegion && Sms.Settings.ShowClippedRegions)
			{
				// Top 24 scanlines
				for (int y = 0; y < 24; y++)
				{
					for (int x = 0; x < 256; x++)
					{
						int frameOffset = (y * 256) + x;
						int p = (FrameBuffer[frameOffset] >> 1) & 0x7F7F7F7F;
						FrameBuffer[frameOffset] = (int)((uint)p | 0x80000000);
					}
				}

				// Bottom 24 scanlines
				for (int y = 168; y < 192; y++)
				{
					for (int x = 0; x < 256; x++)
					{
						int frameOffset = (y * 256) + x;
						int p = (FrameBuffer[frameOffset] >> 1) & 0x7F7F7F7F;
						FrameBuffer[frameOffset] = (int)((uint)p | 0x80000000);
					}
				}

				// Left 48 pixels
				for (int y = 24; y < 168; y++)
				{
					for (int x = 0; x < 48; x++)
					{
						int frameOffset = (y * 256) + x;
						int p = (FrameBuffer[frameOffset] >> 1) & 0x7F7F7F7F;
						FrameBuffer[frameOffset] = (int)((uint)p | 0x80000000);
					}
				}

				// Right 48 pixels
				for (int y = 24; y < 168; y++)
				{
					for (int x = 208; x < 256; x++)
					{
						int frameOffset = (y * 256) + x;
						int p = (FrameBuffer[frameOffset] >> 1) & 0x7F7F7F7F;
						FrameBuffer[frameOffset] = (int)((uint)p | 0x80000000);
					}
				}
			}
		}
	}
}
