// Contains rendering functions for legacy TMS9918 modes.

using System;

namespace BizHawk.Emulation.Cores.Sega.MasterSystem
{
	public partial class VDP
	{
		int[] PaletteTMS9918 = new int[] 
		{
			unchecked((int)0xFF000000),
			unchecked((int)0xFF000000),
			unchecked((int)0xFF47B73B),
			unchecked((int)0xFF7CCF6F),
			unchecked((int)0xFF5D4EFF),
			unchecked((int)0xFF8072FF),
			unchecked((int)0xFFB66247),
			unchecked((int)0xFF5DC8ED),
			unchecked((int)0xFFD76B48),
			unchecked((int)0xFFFB8F6C),
			unchecked((int)0xFFC3CD41),
			unchecked((int)0xFFD3DA76),
			unchecked((int)0xFF3E9F2F),
			unchecked((int)0xFFB664C7),
			unchecked((int)0xFFCCCCCC),
			unchecked((int)0xFFFFFFFF)
		};

		void RenderBackgroundM0(bool show)
		{
			if (ScanLine >= FrameHeight)
				return;

			if (DisplayOn == false)
			{
				Array.Clear(FrameBuffer, ScanLine * 256, 256);
				return;
			}

			int yc = ScanLine / 8;
			int yofs = ScanLine % 8;
			int FrameBufferOffset = ScanLine * 256;
			int PatternNameOffset = TmsPatternNameTableBase + (yc * 32);
			int ScreenBGColor = PaletteTMS9918[Registers[7] & 0x0F];

			for (int xc = 0; xc < 32; xc++)
			{
				int pn = VRAM[PatternNameOffset++];
				int pv = VRAM[PatternGeneratorBase + (pn * 8) + yofs];
				int colorEntry = VRAM[ColorTableBase + (pn / 8)];
				int fgIndex = (colorEntry >> 4) & 0x0F;
				int bgIndex = colorEntry & 0x0F;
				int fgColor = fgIndex == 0 ? ScreenBGColor : PaletteTMS9918[fgIndex];
				int bgColor = bgIndex == 0 ? ScreenBGColor : PaletteTMS9918[bgIndex];

				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x80) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x40) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x20) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x10) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x08) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x04) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x02) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x01) > 0) ? fgColor : bgColor) : 0;
			}
		}

		void RenderBackgroundM2(bool show)
		{
			if (ScanLine >= FrameHeight)
				return;

			if (DisplayOn == false)
			{
				Array.Clear(FrameBuffer, ScanLine * 256, 256);
				return;
			}

			int yrow = ScanLine / 8;
			int yofs = ScanLine % 8;
			int FrameBufferOffset = ScanLine * 256;
			int PatternNameOffset = TmsPatternNameTableBase + (yrow * 32);
			int PatternGeneratorOffset = (((Registers[4] & 4) << 11) & 0x2000);// +((yrow / 8) * 0x100);
			int ColorOffset = (ColorTableBase & 0x2000);// +((yrow / 8) * 0x100);
			int ScreenBGColor = PaletteTMS9918[Registers[7] & 0x0F];

			for (int xc = 0; xc < 32; xc++)
			{
				int pn = VRAM[PatternNameOffset++] + ((yrow / 8) * 0x100);
				int pv = VRAM[PatternGeneratorOffset + (pn * 8) + yofs];
				int colorEntry = VRAM[ColorOffset + (pn * 8) + yofs];
				int fgIndex = (colorEntry >> 4) & 0x0F;
				int bgIndex = colorEntry & 0x0F;
				int fgColor = fgIndex == 0 ? ScreenBGColor : PaletteTMS9918[fgIndex];
				int bgColor = bgIndex == 0 ? ScreenBGColor : PaletteTMS9918[bgIndex];

				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x80) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x40) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x20) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x10) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x08) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x04) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x02) > 0) ? fgColor : bgColor) : 0;
				FrameBuffer[FrameBufferOffset++] = show ? (((pv & 0x01) > 0) ? fgColor : bgColor) : 0;
			}
		}
		
		void RenderTmsSprites(bool show)
		{
			if (ScanLine >= FrameHeight || DisplayOn == false)
				return;

			if (EnableDoubledSprites == false)
				RenderTmsSpritesStandard(show);
			else
				RenderTmsSpritesDouble(show);
		}

		void RenderTmsSpritesStandard(bool show)
		{
			Array.Clear(ScanlinePriorityBuffer, 0, 256);
			Array.Clear(SpriteCollisionBuffer, 0, 256);

			bool LargeSprites = EnableLargeSprites;

			int SpriteSize = 8;
			if (LargeSprites) SpriteSize *= 2;
			const int OneCellSize = 8;

			int NumSpritesOnScanline = 0;
			for (int i = 0; i < 32; i++)
			{
				int SpriteBase = TmsSpriteAttributeBase + (i * 4);
				int y = VRAM[SpriteBase++];
				int x = VRAM[SpriteBase++];
				int Pattern = VRAM[SpriteBase++];
				int Color = VRAM[SpriteBase];

				if (y == 208) break; // terminator sprite
				if (y > 224) y -= 256; // sprite Y wrap
				y++; // inexplicably, sprites start on Y+1
				if (y > ScanLine || y + SpriteSize <= ScanLine) continue; // sprite is not on this scanline
				if ((Color & 0x80) > 0) x -= 32; // Early Clock adjustment

				if (++NumSpritesOnScanline == 5)
				{
					StatusByte &= 0xE0;    // Clear FS0-FS4 bits
					StatusByte |= (byte)i; // set 5th sprite index
					StatusByte |= 0x40;    // set overflow bit
					break;
				}

				if (LargeSprites) Pattern &= 0xFC; // 16x16 sprites forced to 4-byte alignment
				int SpriteLine = ScanLine - y;

				// pv contains the VRAM byte holding the pattern data for this character at this scanline.
				// each byte contains the pattern data for each the 8 pixels on this line.
				// the bit-shift further down on PV pulls out the relevant horizontal pixel.

				byte pv = VRAM[SpritePatternGeneratorBase + (Pattern * 8) + SpriteLine];

				for (int xp = 0; xp < SpriteSize && x + xp < 256; xp++)
				{
					if (x + xp < 0) continue;
					if (LargeSprites && xp == OneCellSize)
						pv = VRAM[SpritePatternGeneratorBase + (Pattern * 8) + SpriteLine + 16];

					if (Color != 0 && (pv & (1 << (7 - (xp & 7)))) > 0)
					{
						if (SpriteCollisionBuffer[x + xp] != 0)
							StatusByte |= 0x20; // Set sprite collision flag

						if (ScanlinePriorityBuffer[x + xp] == 0)
						{
							ScanlinePriorityBuffer[x + xp] = 1;
							SpriteCollisionBuffer[x + xp] = 1;
							if (show)
								FrameBuffer[(ScanLine * 256) + x + xp] = PaletteTMS9918[Color & 0x0F];
						}
					}
				}
			}
		}

		void RenderTmsSpritesDouble(bool show)
		{
			Array.Clear(ScanlinePriorityBuffer, 0, 256);
			Array.Clear(SpriteCollisionBuffer, 0, 256);

			bool LargeSprites = EnableLargeSprites;

			int SpriteSize = 8;
			if (LargeSprites) SpriteSize *= 2;
			SpriteSize *= 2;  // because sprite magnification
			const int OneCellSize = 16; // once 8-pixel cell, doubled, will take 16 pixels

			int NumSpritesOnScanline = 0;
			for (int i = 0; i < 32; i++)
			{
				int SpriteBase = TmsSpriteAttributeBase + (i * 4);
				int y = VRAM[SpriteBase++];
				int x = VRAM[SpriteBase++];
				int Pattern = VRAM[SpriteBase++];
				int Color = VRAM[SpriteBase];

				if (y == 208) break; // terminator sprite
				if (y > 224) y -= 256; // sprite Y wrap
				y++; // inexplicably, sprites start on Y+1
				if (y > ScanLine || y + SpriteSize <= ScanLine) continue; // sprite is not on this scanline
				if ((Color & 0x80) > 0) x -= 32; // Early Clock adjustment

				if (++NumSpritesOnScanline == 5)
				{
					StatusByte &= 0xE0;    // Clear FS0-FS4 bits
					StatusByte |= (byte)i; // set 5th sprite index
					StatusByte |= 0x40;    // set overflow bit
					break;
				}

				if (LargeSprites) Pattern &= 0xFC; // 16x16 sprites forced to 4-byte alignment
				int SpriteLine = ScanLine - y;
				SpriteLine /= 2; // because of sprite magnification

				byte pv = VRAM[SpritePatternGeneratorBase + (Pattern * 8) + SpriteLine];

				for (int xp = 0; xp < SpriteSize && x + xp < 256; xp++)
				{
					if (x + xp < 0) continue;
					if (LargeSprites && xp == OneCellSize)
						pv = VRAM[SpritePatternGeneratorBase + (Pattern * 8) + SpriteLine + 16];

					if (Color != 0 && (pv & (1 << (7 - ((xp / 2) & 7)))) > 0)  // xp/2 is due to sprite magnification
					{
						if (SpriteCollisionBuffer[x + xp] != 0)
							StatusByte |= 0x20; // Set sprite collision flag

						if (ScanlinePriorityBuffer[x + xp] == 0)
						{
							ScanlinePriorityBuffer[x + xp] = 1;
							SpriteCollisionBuffer[x + xp] = 1;
							if (show)
								FrameBuffer[(ScanLine * 256) + x + xp] = PaletteTMS9918[Color & 0x0F];
						}
					}
				}
			}
		}
	}
}
