// ###########################################################################
// #
// # OSCAVideo - Implementation of OSCA Video Module
// # Copyright (C) 2009-2011 Tony Friery [DialTone]
// #
// # ALL RIGHTS RESERVED
// #
// ###########################################################################

#pragma hdrstop

#include "OSCAVideo.h"
#include "OSCABoard.h"
#include "FormDiag.h"
#include "FormMain.h"
#include "FormDebug.h"

//---------------------------------------------------------------------------

// Helper macro for use when testing ;)
#define RANDOMCOLOR Color32(Random(255), Random(255), Random(255), 0xff)

//---------------------------------------------------------------------------

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::RunLineCop
//
//  PURPOSE:	Method called during the scanning phase to cause the
//					LineCop to run its program (if enabled)
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
inline void __fastcall TOSCAVideo::RunLineCop(void)
{
	// Check to see if the target linecop line has been reached
	if (this->fLineCopTarget != this->fScanLine)
	{
		// it hasn't, so bail
		return;
	}

	// Fetch the next linecop instruction
	bool _lineCopDone = false;
	do
	{
		word _lineCopInstr = m_pOwner->MainRAM[(this->fLineCopPC++) | 0x70000];
		_lineCopInstr |= m_pOwner->MainRAM[(this->fLineCopPC++) | 0x70000] << 8;
		if (this->fLineCopPC == 0)
		{
			// Bail as the linecop has looped
			_lineCopDone = true;
		}

		if (_lineCopInstr & 0x8000)
		{
			// Control Instruction
			if (_lineCopInstr & 0x4000)
			{
				// Wait for line
				this->fLineCopTarget = (_lineCopInstr & 0x1ff);
				_lineCopDone = true;
			}
			else
			{
				// Register Select
				this->fLineCopRegister = (_lineCopInstr & 0x7ff);
			}
		}
		else
		{
			// Write Instruction
			this->m_pOwner->LineCopWrite(this->fLineCopRegister, (byte)(_lineCopInstr & 0xff));

			if (_lineCopInstr & 0x4000)
			{
				// Increment Register
				this->fLineCopRegister++;
				this->fLineCopRegister &= 0x7ff;
			}

			if (_lineCopInstr & 0x2000)
			{
				// Increment wait line
				this->fLineCopTarget++;
				this->fLineCopTarget &= 0x1ff;
				_lineCopDone = true;
			}

			if (_lineCopInstr & 0x1000)
			{
				// Reload LineCop PC
				this->fLineCopPC = this->fLineCopStartAddr;
				_lineCopDone = true;	// TODO: I think this should be correct?
			}
		}
	} while (!_lineCopDone);
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::BuildBitmapLine
//
//  PURPOSE:	Method called during the scanning phase to build the
//					palette lookup entries for an entire bitmap row
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
inline void __fastcall TOSCAVideo::BuildBitmapLine(void)
{
	//unsigned int ramAddr;
	byte testBits[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
	byte setBits[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };

	//memset(&this->fVideoLine[0][this->fScanLine], 0x00, 512);

	// Check the plane modulo to see if the offsets need to be reset
	if (this->fPlaneModulo == 0xff)
	{
		this->fPlaneOffset = 0;
	}

	if (this->fScanLine < this->fWindowTop)
	{
		// Return with empty scanline - no processing required (top border)
		memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
		return;
	}

	if (this->CurrentVidMode == BITMAP_PLANAR)
	{
		if (this->fScanLine < this->fWindowBottom)
		{
			int startPlane = (this->fPlaneSet ? 8 : 0);
			int endPlane = startPlane + this->fPlaneCount;
			for (int i = 0; i < 512; i++)
			{
				byte paletteValue = 0x00;
				int k = (i & 7);

				if ((i >= this->fWindowLeft) && (i < this->fWindowRight))
				{
					// This is now inside the visible portion
					if (!fBGVideoInhibit)
					{
						int m = 0x80 >> k;
						for (int j = startPlane, bit = 1; j <= endPlane; j++, bit <<= 1)
						{
							if (this->VideoRAM[this->fPlaneBaseAddr[j] + this->fPlaneOffset] & m)//testBits[i & 7])
								paletteValue |= bit;
						}
					}

					if ((i & 7) == 7)
					{
						this->fPlaneOffset++;
					}
				}

				this->fVideoLine[this->fScanLine][i] = paletteValue;
			}
		}
		else
		{
			// Clear the line (bottom border)
			memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
		}
	}
	else
	{
		if (this->fScanLine < this->fWindowBottom)
		{
			// Handle a CHUNKY bitmap line
			byte paletteValue = 0;
			byte chunkyInterval = fExpandedChunky ? fChunkyWidth : 1;
			byte chunkyCount = 0;

			for (int i = 0, k = 0; i < 512; i++, k = i & 7)
			{
				if ((i >= this->fWindowLeft) && (i < this->fWindowRight))
				{
					// This is now inside the visible portion
					if (!fBGVideoInhibit)
					{
						paletteValue = this->VideoRAM[this->fPlaneBaseAddr[(this->fPlaneSet ? 8 : 0)] + (fPlaneOffset)];
						if (++chunkyCount == chunkyInterval)
						{
							fPlaneOffset++;
							chunkyCount = 0;
                  }
					}
				}
				else
				{
					paletteValue = 0;
				}

				this->fVideoLine[this->fScanLine + 1][i + 1] = paletteValue;
			}
		}
		else
		{
			memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);	// Clear the line
		}
	}

	if (this->fPlaneModulo == 0xff)
	{
		// Special case - reuse first line
		this->fPlaneOffset = 0;
	}
	else
	{
		this->fPlaneOffset += this->fPlaneModulo;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::BuildTileLine
//
//  PURPOSE:	Method called during the scanning phase to build the
//					palette lookup entries for an entire tilemap row
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
inline void __fastcall TOSCAVideo::BuildTileLine(void)
{
	int _playfieldCount = (this->fDualPFEnabled ? 2 : 1);
	int _priorityFlip = (this->fFlipPlayfields ? 1 : 0);

	unsigned int ramAddr;

	if (this->fScanLine < this->fWindowTop)
	{
		// Return with empty scanline - no processing required (top border)
		memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
		return;
	}

	switch (this->CurrentTileMode)
	{
		case TILE_LEGACY:
			if (this->fScanLine < this->fWindowBottom)
			{
				// Handle a Legacy Tilemap line
				// Base addr is (adj scanline num / 16) * 32
				int _baseMap = ((this->fScanLine - this->fWindowTop) & 0xff0) << 1;
				int _tileRow = (this->fScanLine - this->fWindowTop) & 0x0f;

				for (int i = 0, _tileBit = 0; i < 512; i++, _tileBit = i & 0x0f)
				{
					byte paletteValue = 0x00;

					if ((i >= this->fWindowLeft) && (i < this->fWindowRight))
					{
						for (int _pf = 0; _pf < _playfieldCount; _pf++)
						{
							int _actualPF = _pf ^ _priorityFlip;
							int _adjustMap = _baseMap + (0x400 * _actualPF);
							int _xOffset = 0;//(_actualPF ? (this->fXHWS >> 4) : (this->fXHWS & 0x0f));

							if (this->fAltMapBufferSelect[_actualPF])
							{
								_adjustMap += 0x200;
							}

							byte _tile = this->VideoRAM[_adjustMap];
							int _tileBase = this->fAltTileSetSelect[_actualPF] ? 0x10000 : 0x0;
							int _tileRAM = _tileBase + (_tile << 8) + (_tileRow << 4) + _tileBit;
							paletteValue = this->VideoRAM[_tileRAM];

							if ((paletteValue > 0) || (this->fBGVideoInhibit))
							{
								this->fVideoLine[this->fScanLine /*+ this->fPFScrollY[_actualPF]*/][i + _xOffset] = paletteValue;
							}
							else if (_pf == 0)
							{
								this->fVideoLine[this->fScanLine /*+ this->fPFScrollY[_actualPF]*/][i + _xOffset] = 0;
							}
						}

						if (_tileBit == 15)
						{
							_baseMap++;
						}
					}
					else
					{
						this->fVideoLine[this->fScanLine][i] = 0;
					}

				}
			}
			else
			{
				// Clear the line (bottom border)
				memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
			}
			break;
		case TILE_EXTENDED:
			if (this->fUse8x8Tiles)
			{
				if (this->fScanLine < this->fWindowBottom)
				{
					// Handle an Extended Tilemap line
					// Base addr is (adj scanline num / 8) * 64
					int _baseMap = 0x70000 | (((this->fScanLine - this->fWindowTop) & 0xff8) << 3);
					int _tileRow = (this->fScanLine - this->fWindowTop) & 0x07;

					for (int i = 0, _tileBit = 0; i < 512; i++, _tileBit = i & 0x07)
					{
						byte paletteValue = 0x00;

						if ((i >= this->fWindowLeft) && (i < this->fWindowRight))
						{
							for (int _pf = 0; _pf < _playfieldCount; _pf++)
							{
								int _actualPF = _pf ^ _priorityFlip;
								int _adjustMap = _baseMap + (0x2000 * _actualPF);
								int _xOffset = 0;//(_actualPF ? (this->fXHWS >> 4) : (this->fXHWS & 0x0f));

								if (this->fAltMapBufferSelect[_actualPF])
								{
									_adjustMap += 0x1000;
								}

								word _tile = this->VideoRAM[_adjustMap] | (this->VideoRAM[_adjustMap + 0x800]  << 8);
								int _tileBase = this->fAltTileSetSelect[_actualPF] ? 0x40000 : 0x0;
								int _adjRow = (_tile & 0x8000) ? (7 - _tileRow) : _tileRow;
								int _adjBit = (_tile & 0x4000) ? (7 - _tileBit) : _tileBit;
								_tile &= 0x1fff;
								int _tileRAM = _tileBase + (_tile << 6) + (_adjRow << 3) + _adjBit;
								paletteValue = this->VideoRAM[_tileRAM];

								if ((paletteValue > 0) || (this->fBGVideoInhibit))
								{
									this->fVideoLine[this->fScanLine][i + _xOffset] = paletteValue;
								}
								else if (_pf == 0)
								{
									this->fVideoLine[this->fScanLine][i + _xOffset] = 0;
								}
							}

							if (_tileBit == 7)
							{
								_baseMap++;
							}
						}
						else
						{
							this->fVideoLine[this->fScanLine][i] = 0;
						}
					}
				}
				else
				{
					// Clear the line (bottom border)
					memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
				}
			}
			else
			{
				if (this->fScanLine < this->fWindowBottom)
				{
					// Handle an Extended Tilemap line
					// Base addr is (adj scanline num / 16) * 32
					int _baseMap = 0x70000 | (((this->fScanLine - this->fWindowTop) & 0xff0) << 1);
					int _tileRow = (this->fScanLine - this->fWindowTop) & 0x0f;

					for (int i = 0, _tileBit = 0; i < 512; i++, _tileBit = i & 0x0f)
					{
						byte paletteValue = 0x00;

						if ((i >= this->fWindowLeft) && (i < this->fWindowRight))
						{
							for (int _pf = 0; _pf < _playfieldCount; _pf++)
							{
								int _actualPF = _pf ^ _priorityFlip;
								int _adjustMap = _baseMap + (0x400 * _actualPF);
								int _xOffset = 0;//(_actualPF ? (this->fXHWS >> 4) : (this->fXHWS & 0x0f));

								if (this->fAltMapBufferSelect[_actualPF])
								{
									_adjustMap += 0x200;
								}

								word _tile = this->VideoRAM[_adjustMap] | (this->VideoRAM[_adjustMap + 0x800]  << 8);
								int _tileBase = this->fAltTileSetSelect[_actualPF] ? 0x40000 : 0x0;
								int _adjRow = (_tile & 0x8000) ? (15 - _tileRow) : _tileRow;
								int _adjBit = (_tile & 0x4000) ? (15 - _tileBit) : _tileBit;
								_tile &= 0x7fff;
								int _tileRAM = _tileBase + (_tile << 8) + (_adjRow << 4) + _adjBit;
								paletteValue = this->VideoRAM[_tileRAM];

								if ((paletteValue > 0) || (this->fBGVideoInhibit))
								{
									this->fVideoLine[this->fScanLine][i + _xOffset] = paletteValue;
								}
								else if (_pf == 0)
								{
									this->fVideoLine[this->fScanLine][i + _xOffset] = 0;
								}
							}

							if (_tileBit == 15)
							{
								_baseMap++;
							}
						}
						else
						{
							this->fVideoLine[this->fScanLine][i] = 0;
						}
					}
				}
				else
				{
					// Clear the line (bottom border)
					memset(&this->fVideoLine[this->fScanLine][96], 0x00, 400);
				}
			}
			break;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::BuildSpriteLine
//
//  PURPOSE:	Method called during the scanning phase to build the
//					palette lookup entries for an entire sprite row
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
inline void __fastcall TOSCAVideo::BuildSpriteLine(void)
{
	// TODO: Implement remaining sprite options
	unsigned int ramAddr;
	int _spriteStart = 0;
	int _spriteEnd = 127;

	if (!this->fSpritesEnabled)
	{
		return;
	}

	if (!this->fSpritesDirty)
	{
		return;
	}

	this->fSpritesDirty--;

	memset(&this->fSpriteLine[this->fScanLine][80], 0x00, 416 * 2);

	if (this->fSpriteDoubleBuf)
	{
		if (this->fSpriteBank == 0)
		{
			_spriteEnd = 62;
		}
		else
		{
			_spriteStart = 64;
		}
	}
	else
	{
		_spriteEnd = 127;
	}

	for (int j = _spriteStart; j <= _spriteEnd; j++)
	{
		if (((this->Sprites[j].Height + this->Sprites[j].Y) > this->fScanLine) && (this->Sprites[j].Y <= this->fScanLine))
		{
			ramAddr = (this->Sprites[j].DefNum * 256) + ((this->fScanLine - this->Sprites[j].Y) * 16);
			for (int i = 0; i < 16; i++)
			{
				byte _palette = this->SpriteRAM[ramAddr++];
				if (_palette > 0)
					this->fSpriteLine[this->fScanLine][this->Sprites[j].X + i + 1] = _palette;
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::Tick
//
//  PURPOSE:	Method called periodically to enable synching the
//					device clock against the CPU clock
//
//  INPUTS:		unsigned int		Number of cycles elapsed since last
//											time this method was called
//
//  OUTPUT:		bool					IRQ Status following tick being
//											processed
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAVideo::Tick(unsigned int CycleCount)
{
	// TODO: Rewrite the Video TICK method to make it more correct and cycle accurate
	this->fScanPrescaler += CycleCount;

	while (this->fScanPrescaler >= 1024)
	{
		this->fScanPrescaler -= 1024;

		// Run LineCop if necessary
		if (this->fLineCopRunning)
		{
		  this->RunLineCop();
		}

		if (this->fScanLine < 256)
		{
			// Store palette
			if (this->fScanLine >= 16)
			{
				memcpy(&this->fDrawPalette[this->fScanLine - 16], &this->fVideoPalette[this->fLivePalette], sizeof(TPalette32));
			}

			// Build video elements
			this->BuildSpriteLine();

			switch (this->CurrentVidMode)
			{
				case BITMAP_CHUNKY:
				case BITMAP_PLANAR:
					this->BuildBitmapLine();
					break;
				case TILE_MAP:
					this->BuildTileLine();
					break;
			}

			// Handle truncating scrolled line
			memset(&this->fVideoLine[this->fScanLine][this->fWindowRight], 0x00, 16);

			// Handle wide border
			if (this->fWideBorder)
			{
				memset(&this->fVideoLine[this->fScanLine][this->fWindowLeft], 0x00, 16);
			}
		}

		this->fScanLine++;
		this->fInternalScanLine++;

		// Check if raster IRQ needs to be triggered
		if (this->fScanLine == this->fRasterIRQTarget)
		{
			fRasterIRQ = true;
		}

		// Handle last scanline
		if (this->fScanLine == 312)
		{
			this->fScanLine = 0;
			this->fInternalScanLine = -8;
			this->fPlaneOffset = 0;

			if (this->fLineCopRunning)
			{
				this->fLineCopPC = this->fLineCopStartAddr;
				this->fLineCopTarget = 0;
			}
		}
	}

	return ((this->fRasterIRQ) && (this->fRasterIRQEnable));
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WritePalette
//
//  PURPOSE:	Write to one of the palette entries in the table
//
//  INPUTS:		word					Address within palette to write
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WritePalette(word Address, byte Value)
{
	// Write new value to the palette storage
	this->fSpritesDirty = 312;
	this->PaletteRAM[this->fTargetPalette][Address & 0x1ff] = Value;

	// Extract new RGB value
	int g = (PaletteRAM[this->fTargetPalette][Address & 0x1fe] & 0xf0);
	int b = (PaletteRAM[this->fTargetPalette][Address & 0x1fe] & 0x0f) << 4;
	int r = (PaletteRAM[this->fTargetPalette][(Address & 0x1fe) | 1] & 0x0f) << 4;

	// Update actual palette used for UI rendering
	this->fVideoPalette[this->fTargetPalette][(Address >> 1)] = Color32(r, g, b, 0xff);

	if ((DiagForm) && (DiagForm->IsActive(DIAG_PALETTE)))
	{
		DiagForm->AddMessage(DIAG_PALETTE, UnicodeString::Format(
			L"Palette entry 0x%3.3x set to 0x%2.2x",
			OPENARRAY(TVarRec, ((Address & 0x1ff), Value))
		));
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteBlitter
//
//  PURPOSE:	Write to one of the blitter registers
//
//  INPUTS:		word					Address of the blitter register to write
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteBlitter(byte Address, byte Value)
{
	switch (Address & 0x0f)
	{
		case 0x00:	// blit_src_loc (LSB)
			this->fBlitSrcLoc &= 0xff00;
			this->fBlitSrcLoc |= Value;
			break;
		case 0x01:	// blit_src_loc (MSB)
			this->fBlitSrcLoc &= 0xff;
			this->fBlitSrcLoc |= (Value << 8);
			break;
		case 0x02:	// blit_dst_loc (LSB)
			this->fBlitDestLoc &= 0xff00;
			this->fBlitDestLoc |= Value;
			break;
		case 0x03:	// blit_dst_loc (MSB)
			this->fBlitDestLoc &= 0xff;
			this->fBlitDestLoc |= (Value << 8);
			break;
		case 0x04:	// blit_src_mod
			this->fBlitSrcModulo &= 0x300;
			this->fBlitSrcModulo |= Value;
			break;
		case 0x05:	// blit_dst_mod
			this->fBlitDestModulo &= 0x300;
			this->fBlitDestModulo |= Value;
			break;
		case 0x06:	// blit_height
			this->fBlitHeight = Value;
			break;
		case 0x07:
			this->fBlitWidth = Value;
			//this->fSpritesDirty = 312;

			if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
			{
				DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
					L"Begin Blit from 0x%6.6x to 0x%6.6x",
					OPENARRAY(TVarRec, (
						((this->fBlitSrcMSB << 16) | ((this->fBlitMisc & 0x10) << 12)) + this->fBlitSrcLoc,
						((this->fBlitDestMSB << 16) | ((this->fBlitMisc & 0x20) << 11)) + this->fBlitDestLoc)
					)
				));

				DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
					L"Blit W: %d, H: %d, Mod (s/d): 0x%3.3x, 0x%3.3x",
					OPENARRAY(TVarRec, (this->fBlitWidth, this->fBlitHeight,
					this->fBlitSrcModulo, this->fBlitDestModulo))
				));
			}

			// TODO: Move this blittery code to within the TICK method so it happens cycle accurately
			if (this->fBlitMisc & 0x40)
			{
				// Ascending Blit
				unsigned int blitFrom = ((this->fBlitSrcMSB << 16) | ((this->fBlitMisc & 0x10) << 12)) + this->fBlitSrcLoc;
				unsigned int blitTo = ((this->fBlitDestMSB << 16) | ((this->fBlitMisc & 0x20) << 11)) + this->fBlitDestLoc;

				for (int blitLines = 0; blitLines <= this->fBlitHeight; blitLines++)
				{

					for (int blitCols = 0; blitCols <= this->fBlitWidth; blitCols++)
					{
						if (((this->fBlitMisc & 0x80) == 0x00) || (this->VideoRAM[blitFrom] != 0x00))
						{
							this->VideoRAM[blitTo] = this->VideoRAM[blitFrom];
						}

						blitFrom++;
						blitTo++;
					}

					blitFrom += this->fBlitSrcModulo;
					blitTo += this->fBlitDestModulo;
				}
			}
			else
			{
				// Descending Blit
				unsigned int blitFrom = ((this->fBlitSrcMSB << 16) | ((this->fBlitMisc & 0x10) << 12)) + this->fBlitSrcLoc;
				unsigned int blitTo = ((this->fBlitDestMSB << 16) | ((this->fBlitMisc & 0x20) << 11)) + this->fBlitDestLoc;

				for (int blitLines = 0; blitLines <= this->fBlitHeight; blitLines++)
				{

					for (int blitCols = 0; blitCols <= this->fBlitWidth; blitCols++)
					{
						if (((this->fBlitMisc & 0x80) == 0x00) || (this->VideoRAM[blitFrom] != 0x00))
						{
							this->VideoRAM[blitTo] = this->VideoRAM[blitFrom];
						}

						blitFrom--;
						blitTo--;
					}

					blitFrom -= this->fBlitSrcModulo;
					blitTo -= this->fBlitDestModulo;
				}
			}
			break;
		case 0x08:	// blit_misc
			this->fBlitMisc = Value;

			// Source Modulo Additional Bits
			this->fBlitSrcModulo &= 0xff;
			this->fBlitSrcModulo |= (this->fBlitMisc & 0x03) << 8;
			this->fBlitSrcModulo &= 0x3ff;
			if (this->fBlitSrcModulo & 0x200)
			{
				// Sign-Extend Source Modulo Value
				this->fBlitSrcModulo |= 0xfc00;
			}

			// Destination Modulo Additional Bits
			this->fBlitDestModulo &= 0xff;
			this->fBlitDestModulo |= (this->fBlitMisc & 0x0c) << 6;
			this->fBlitDestModulo &= 0x3ff;
			if (this->fBlitDestModulo & 0x200)
			{
				// Sign-Extend Destination Modulo Value
				this->fBlitDestModulo |= 0xfc00;
			}
			break;
		case 0x09:	// blit_src_msb
			this->fBlitSrcMSB = (Value & 0x07);
			break;
		case 0x0a:	// blit_dst_msb
			this->fBlitDestMSB = (Value & 0x07);
			break;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteSpriteRegister
//
//  PURPOSE:	Write to one of the sprite registers
//
//  INPUTS:		word					Address of the register to be written
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteSpriteRegister(word Address, byte Value)
{
	int _spriteNum = (Address >> 2) & 0x7f;
	int _spriteReg = (Address & 3);

	if (this->fSpriteDoubleBuf)
	{
		if ((_spriteNum == 63) || (_spriteNum == 127))
		{
			// These registers aren't valid in double-buffer mode
			return;
		}
	}

	this->fSpritesDirty = 312;

	switch(_spriteReg)
	{
		case 0x0:	// X Position
			this->Sprites[_spriteNum].X &= 0x100;
			this->Sprites[_spriteNum].X |= Value;
			break;
		case 0x1:	// Control bits
			// MSB bits
			this->Sprites[_spriteNum].X &= 0xff;
			this->Sprites[_spriteNum].X |= ((Value & 0x01) << 8);
			this->Sprites[_spriteNum].Y &= 0xff;
			this->Sprites[_spriteNum].Y |= ((Value & 0x02) << 7);
			this->Sprites[_spriteNum].DefNum &= 0xff;
			this->Sprites[_spriteNum].DefNum |= ((Value & 0x04) << 6);
			this->Sprites[_spriteNum].Mirror = (Value & 0x08) ? true : false;

			// Handle special bits - depends on vreg_sprctrl
			switch(this->fSpriteControl & 0x30)
			{
				case 0x00:
					this->Sprites[_spriteNum].Height = (Value & 0xf0);
					if (this->Sprites[_spriteNum].Height == 0)
					{
						this->Sprites[_spriteNum].Height = 240;
					}
					this->Sprites[_spriteNum].InFront = false;
					this->Sprites[_spriteNum].Matte = false;
					break;
				case 0x10:
					this->Sprites[_spriteNum].Height = (Value & 0x70);
					if (this->Sprites[_spriteNum].Height == 0)
					{
						this->Sprites[_spriteNum].Height = 240;
					}
					this->Sprites[_spriteNum].InFront = (Value & 0x80) ? true : false;
					this->Sprites[_spriteNum].Matte = false;
					break;
				case 0x20:
					this->Sprites[_spriteNum].Height = (Value & 0xb0);
					if (this->Sprites[_spriteNum].Height == 0)
					{
						this->Sprites[_spriteNum].Height = 64;
					}
					this->Sprites[_spriteNum].InFront = false;
					this->Sprites[_spriteNum].Matte = (Value & 0x40) ? true : false;
					break;
				case 0x30:
					this->Sprites[_spriteNum].Height = (Value & 0x30);
					if (this->Sprites[_spriteNum].Height == 0)
					{
						this->Sprites[_spriteNum].Height = 64;
					}
					this->Sprites[_spriteNum].InFront = (Value & 0x80) ? true : false;
					this->Sprites[_spriteNum].Matte = (Value & 0x40) ? true : false;
					break;
			}
			break;
		case 0x2:	// Y Position
			this->Sprites[_spriteNum].Y &= 0x100;
			this->Sprites[_spriteNum].Y |= Value;
			break;
		case 0x3:	// Sprite definition number
			this->Sprites[_spriteNum].DefNum &= 0x100;
			this->Sprites[_spriteNum].DefNum |= Value;
			break;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteRegister
//
//  PURPOSE:	Write to one of the video registers
//
//  INPUTS:		word					Address of the register to be written
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteRegister(word Address, byte Value)
{
	switch(Address & 0xf0)
	{
		case 0x00:
			// Main Video Regs
			switch (Address & 0x0f)
			{
				case 0x00:	// vreg_xhws
					this->fXHWS = Value;
					break;
				case 0x01:	// vreg_vidctrl
					// ??? means that the behaviour varies depending on video mode
					// ??? | ??? | ??? | ??? | ??? | BG_VID_INHIBIT | WIDE_LEFT_BORDER | TILE_MAP_EN
					this->CurrentVidMode = (Value & 0x01) ? TILE_MAP :
						(Value & 0x80) ? BITMAP_CHUNKY : BITMAP_PLANAR;
					this->fWideBorder = (Value & 0x02) ? true : false;
					this->fBGVideoInhibit = (Value & 0x04) ? true : false;
					MainForm->StatusBar->Panels->Items[9]->Text = (this->fBGVideoInhibit ? L"INH" : L"ACT");

					switch (this->CurrentVidMode)
					{
						case TILE_MAP:
							this->fDualPFEnabled = (Value & 0x80) ? true : false;
							this->fAltMapBufferSelect[1] = (Value & 0x40) ? true : false;	// Map buffer B
							this->fAltMapBufferSelect[0] = (Value & 0x20) ? true : false;	// Map buffer A

							if (this->CurrentTileMode == TILE_LEGACY)
							{
								this->fAltTileSetSelect[1] = (Value & 0x10) ? true : false;	// Tile set B
								this->fAltTileSetSelect[0] = (Value & 0x08) ? true : false;	// Tile set A
								MainForm->StatusBar->Panels->Items[12]->Text = (this->fDualPFEnabled ? L"LEG DP" : L"LEGACY");
							}
							else
							{
								this->fAltTileSetSelect[0] = (Value & 0x10) ? true : false;	// Tile set
								this->fAltTileSetSelect[1] = (Value & 0x10) ? true : false;	// Tile set
								this->fUse8x8Tiles = (Value & 0x08) ? true : false; 		// tile sizes are 8x8 or 16x16 (false)
								MainForm->StatusBar->Panels->Items[12]->Text = (this->fUse8x8Tiles ?
									(this->fDualPFEnabled ? L"8x8 DP" : L"8x8") : (this->fDualPFEnabled ? L"16x16 DP" : L"16x16"));
							}
							break;
						case BITMAP_CHUNKY:
							this->fBitmapFloodMode = (Value & 0x40) ? true : false;
							this->fPlaneSet = (Value & 0x20) ? true : false;
							this->fExpandedChunky = (Value & 0x08) ? true : false;
							MainForm->StatusBar->Panels->Items[12]->Text = (this->fExpandedChunky ? L"EX CHNK" : L"CHUNKY");
							break;
						case BITMAP_PLANAR:
							this->fPlaneSet = (Value & 0x20) ? true : false;
							MainForm->StatusBar->Panels->Items[12]->Text = L"PLANAR";
							break;
					}

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"vreg_vidctrl Set to 0x%2.2x",
							OPENARRAY(TVarRec, (Value))
						));
					}
					break;
				case 0x02:	// vreg_window
					if (this->fWindowXYSwitch)
					{
						// X Positions
						this->fWindowLeft = (Value & 0xf0);
						this->fWindowRight = 256 + ((Value & 0x0f) << 4);
						this->fWindowWidth = this->fWindowRight - this->fWindowLeft;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
						{
							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Display Width from %d to %d",
								OPENARRAY(TVarRec, (this->fWindowLeft, this->fWindowRight))
							));
						}
					}
					else
					{
						// Y Positions
						this->fWindowTop = ((Value & 0xf0) >> 1);
						this->fWindowBottom = 160 + ((Value & 0x0f) << 3);
						this->fWindowHeight = this->fWindowBottom - this->fWindowTop;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
						{
							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Display Height from %d to %d",
								OPENARRAY(TVarRec, (this->fWindowTop - 16, this->fWindowBottom - 16))
							));
						}
					}
					break;
				case 0x03:	// vreg_yhws_bplcount
					switch (this->CurrentVidMode)
					{
						case TILE_MAP:
							this->fPFScrollY[(Value & 0x80) >> 7] = Value & 0x0f;

							if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
							{
								DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
									L"Y Scroll for Playfield %d set to 0x%2.2x",
									OPENARRAY(TVarRec, ((Value & 0x80) >> 7, Value & 0x0f))
								));
							}
							break;
						case BITMAP_PLANAR:
							if ((Value & 0x80) == 0x00)
							{
								this->fPlaneCount = Value & 0x07;

								if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
								{
									DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
										L"Plane count set to %d",
										OPENARRAY(TVarRec, (this->fPlaneCount))
									));
								}
							}
							break;
						case BITMAP_CHUNKY:
							if ((Value & 0x80) == 0x00)
							{
								this->fChunkyWidth = (Value & 0x07) + 1;

								if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
								{
									DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
										L"Chunky pixel width set to %d",
										OPENARRAY(TVarRec, (this->fChunkyWidth))
									));
								}
							}
							break;
					}
					break;
				case 0x04:	// vreg_rasthi
					if (Value & 0x80)
					{
						if (this->fRasterIRQ)
						{
							this->ClearIRQ();

							if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
							{
								DiagForm->AddMessage(DIAG_VIDEO, L"Raster IRQ Cleared");
							}
						}
					}
					else
					{
						this->fWindowXYSwitch = ((Value & 0x04) > 0);
						this->fRasterIRQEnable = (Value & 0x02) ? true : false;
						this->fRasterIRQTarget &= 0xff;
						this->fRasterIRQTarget |= ((Value & 0x01) << 8);

						if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
						{
							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Raster IRQ Target Set to 0x%3.3x (%s)",
								OPENARRAY(TVarRec, (this->fRasterIRQTarget, (this->fRasterIRQEnable ? L"enable" : L"disable")))
							));
						}
					}
					break;
				case 0x05:	// vreg_rastlo
					this->fRasterIRQTarget &= 0x100;
					this->fRasterIRQTarget |= Value;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Raster IRQ Target Set to 0x%3.3x (%s)",
								OPENARRAY(TVarRec, (this->fRasterIRQTarget, (this->fRasterIRQEnable ? L"enable" : L"disable")))
						));
					}
					break;
				case 0x06:	// vreg_vidpage
					if (Value & 0x80)
					{
						// Sprite Page
						this->fSpritePage = (Value & 0x1f);

						if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
						{
							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Sprite Page set to 0x%2.2x",
								OPENARRAY(TVarRec, (this->fSpritePage))
							));
						}
					}
					else
					{
						// Video Page
						this->fVideoPage = (Value & 0x3f);

						if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
						{
							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Video Page set to 0x%2.2x",
								OPENARRAY(TVarRec, (this->fVideoPage))
							));
						}
					}
					break;
				case 0x07:	// vreg_sprctrl
					this->fSpriteControl = Value;
					this->fSpritesEnabled = (Value & 0x01) ? true : false;
					this->fSpriteInterleave = (Value & 0x02) ? true : false;
					this->fSpriteBank = (Value & 0x04) >> 2;
					this->fSpriteDoubleBuf = (Value & 0x08) ? true : false;
					this->fSpriteForceHigh = (Value & 0x10) ? true : false;
					this->fSpritesMatte = (Value & 0x20) ? true : false;
					this->fSpriteLowColours = (Value & 0x40) ? true : false;
					this->fSpritesDirty = 312;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"vreg_sprctrl set to 0x%2.2x",
							OPENARRAY(TVarRec, (this->fSpriteControl))
						));
					}
					break;
				// CASES 0x08, 0x09 and 0x0A refer to the multiplier so they are handled elsewhere
				case 0x0b:	// linedraw_colour
					this->fLineColour = Value;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"Linedraw Colour Index set to 0x%2.2x",
							OPENARRAY(TVarRec, (this->fLineColour))
						));
					}
					break;
				case 0x0c:	// vreg_ext_vidctrl
					// xxx | xxx | xxx | xxx | hires mode | interlace | flip_dualpf | extend_tiles
					// TODO: Implement Interlace and HiRes modes
					this->CurrentTileMode = (Value & 0x01) ? TILE_EXTENDED : TILE_LEGACY;
					this->fFlipPlayfields = (Value & 0x02) ? true : false;	// When true, B is behind A

					if (this->CurrentTileMode == TILE_LEGACY)
					{
						MainForm->StatusBar->Panels->Items[12]->Text = (this->fDualPFEnabled ? L"LEG DP" : L"LEGACY");
					}
					else
					{
						MainForm->StatusBar->Panels->Items[12]->Text = (this->fUse8x8Tiles ?
							(this->fDualPFEnabled ? L"8x8 DP" : L"8x8") : (this->fDualPFEnabled ? L"16x16 DP" : L"16x16"));
					}

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"vreg_ext_vidctrl set to 0x%2.2x",
							OPENARRAY(TVarRec, (Value))
						));
					}
					break;
				case 0x0d:	// vreg_linecop_lo
					this->fLineCopStartAddr &= 0xff00;
					this->fLineCopStartAddr |= (Value & 0xfe);
					this->fLineCopRunning = (Value & 0x01) ? true : false;
					MainForm->StatusBar->Panels->Items[15]->Text = (this->fLineCopRunning ? L"RUN" : L"IDLE");
					this->fLineCopPC = fLineCopStartAddr;

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"LineCop Start Address set to 0x%5.5x (%s)",
							OPENARRAY(TVarRec, (this->fLineCopStartAddr | 0x70000, (this->fLineCopRunning ? L"running" : L"stopped")))
						));
					}
					break;
				case 0x0e:	// vreg_linecop_hi
					this->fLineCopStartAddr &= 0xff;
					this->fLineCopStartAddr |= (Value << 8);

					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"LineCop Start Address set to 0x%5.5x (%s)",
							OPENARRAY(TVarRec, (this->fLineCopStartAddr | 0x70000, (this->fLineCopRunning ? L"running" : L"stopped")))
						));
					}
					break;
				case 0x0f:	// vreg_palette_ctrl
					if (Value & 0x02)
					{
						// Change target palette
						this->fTargetPalette = Value & 1;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_PALETTE)))
						{
							DiagForm->AddMessage(DIAG_PALETTE, UnicodeString::Format(
								L"Switched to target palette %d",
								OPENARRAY(TVarRec, (this->fTargetPalette))
							));
						}
					}
					else
					{
						// Change live palette
						this->fLivePalette = Value & 1;

						if ((DiagForm) && (DiagForm->IsActive(DIAG_PALETTE)))
						{
							DiagForm->AddMessage(DIAG_PALETTE, UnicodeString::Format(
								L"Switched to live palette %d",
								OPENARRAY(TVarRec, (this->fLivePalette))
							));
						}
					}
					break;
				default:
					if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"Register 0x%3.3x set to 0x%2.2x",
							OPENARRAY(TVarRec, (Address, Value))
						));
					}
					break;
			}
			break;
		case 0x10:
			// Blitter
			if ((Address & 0x0f) < 0x0b)
			{
				this->WriteBlitter(Address & 0x0f, Value);
			}
			else
			{
				if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
				{
					DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
						L"Unknown Blitter Register 0x%3.3x set to 0x%2.2x",
						OPENARRAY(TVarRec, (Address, Value))
					));
				}
			}
			break;
		case 0x20:
		case 0x30:
			// Line Draw
			// TODO: Implement Line Drawing Hardware
			if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
			{
				DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
					L"(Line-Draw) Register 0x%3.3x set to 0x%2.2x",
					OPENARRAY(TVarRec, (Address, Value))
				));
			}
			break;
		case 0x40:
		case 0x50:
		case 0x60:
		case 0x70:
			{
				// Bitplane Locations
				byte _planeNumber = (Address & 0x3c) >> 2;

				switch (Address & 0x03)
				{
					case 0x00:
						this->fPlaneBaseAddr[_planeNumber] &= 0x7ff00;
						this->fPlaneBaseAddr[_planeNumber] |= Value;
						break;
					case 0x01:
						this->fPlaneBaseAddr[_planeNumber] &= 0x700ff;
						this->fPlaneBaseAddr[_planeNumber] |= Value << 8;
						break;
					case 0x02:
						this->fPlaneBaseAddr[_planeNumber] &= 0x0ffff;
						this->fPlaneBaseAddr[_planeNumber] |= (Value & 0x07) << 16;
						break;
					case 0x03:
						// Special case - reset or modulo
						if (_planeNumber & 1)
						{
							this->fPlaneModulo = Value;

							DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
								L"Plane Modulo set to 0x%2.2x",
								OPENARRAY(TVarRec, (Value))
							));
						}
						else
						{
							this->fPlaneOffset = 0;

							DiagForm->AddMessage(DIAG_VIDEO, L"Plane Counters Reset");
						}
						break;
				}

				if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
				{
					if ((Address & 0x03) < 0x03)
					{
						DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
							L"Plane Address %d set to 0x%8.8x",
							OPENARRAY(TVarRec, (_planeNumber, this->fPlaneBaseAddr[_planeNumber]))
						));
					}
				}
			}
			break;
		case 0x80:
			// New Sprite Priority Registers
			if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
			{
				DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
					L"(SpritePri) Register 0x%3.3x set to 0x%2.2x",
					OPENARRAY(TVarRec, (Address, Value))
				));
			}
			break;
		default:
			if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
			{
				DiagForm->AddMessage(DIAG_VIDEO, UnicodeString::Format(
					L"Unknown Video Register 0x%3.3x set to 0x%2.2x",
					OPENARRAY(TVarRec, (Address, Value))
				));
			}
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::ReadVREG
//
//  PURPOSE:	Read the value of the VREG status register
//
//  INPUTS:		void
//
//  OUTPUT:		byte					Value of VREG register
//
///////////////////////////////////////////////////////////////////////
byte __fastcall TOSCAVideo::ReadVREG(void)
{
	byte retv = 0x00;

	retv |= 0x00;																					// Long Interlaced Field
	retv |= (this->fScanLine & 0x01) << 6;													// LSB of Scanline
	retv |= ((this->CurrentVidSystem == VIDEO_PAL_I)   								//	60Hz when set
		|| (this->CurrentVidSystem == VIDEO_PAL_P)) ? 0 : 0x20;
	retv |= this->fScanLine & 0x10;//0x00; 												// Busy																				// Blitter Busy
	retv |= this->GetRasterIRQ() ? 0x08 : 0x00;											// Raster IRQ
	retv |= ((this->fScanLine < this->fWindowTop) ||
		(this->fScanLine > this->fWindowBottom)) ? 0 : 0x04;							// Y Window (1 = Display)
	retv |= ((this->fScanPrescaler < (512 + this->fWindowLeft)) || 				// X Window (1 = Display)
		(this->fScanPrescaler > (512 + this->fWindowRight))) ? 0 : 0x02;
	retv |= ((this->fScanLine > 279) || (this->fScanLine < 8)) ? 0x01 : 0x00;	// Last Line VRT

	return retv;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::ReadMappedVRAM
//
//  PURPOSE:	Read from Video RAM (mapped as an 8k block)
//
//  INPUTS:		word					Address within Video RAM to read
//
//  OUTPUT:		byte					Value read from Video RAM
//
///////////////////////////////////////////////////////////////////////
byte __fastcall TOSCAVideo::ReadMappedVRAM(word Address)
{
	return this->VideoRAM[(Address & 0x1fff) + (this->fVideoPage * 8 * 1024)];
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::ReadSpriteRAM
//
//  PURPOSE:	Read from Sprite Memory
//
//  INPUTS:		word					Address within Sprite RAM to read
//
//  OUTPUT:		byte					Byte read from Sprite Memory
//
///////////////////////////////////////////////////////////////////////
byte __fastcall TOSCAVideo::ReadSpriteRAM(word Address)
{
	return this->SpriteRAM[(Address & 0xfff) + (this->fSpritePage * 4 * 1024)];
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteDirectVRAM
//
//  PURPOSE:	Write to Video RAM (paged in as a full 64k block)
//
//  INPUTS:		word					Address within Video RAM to write
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteDirectVRAM(word Address, byte Value)
{

	this->VideoRAM[Address + ((this->fVideoPage & 0x38) << 13)] = Value;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteMappedVRAM
//
//  PURPOSE:	Write to Video RAM (paged in as an 8K block)
//
//  INPUTS:		word					Address within Video RAM to write
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteMappedVRAM(word Address, byte Value)
{
	this->VideoRAM[(Address & 0x1fff) + (this->fVideoPage * 8 * 1024)] = Value;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::WriteSpriteRAM
//
//  PURPOSE:	Write to Sprite Memory
//
//  INPUTS:		word					Address within Sprite RAM to write
//					byte					Value to be written
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::WriteSpriteRAM(word Address, byte Value)
{
	this->SpriteRAM[(Address & 0xfff) + (this->fSpritePage * 4 * 1024)] = Value;
	this->fSpritesDirty = 312;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::GetRasterIRQ
//
//  PURPOSE:	Retrieve the current value of the Raster IRQ Flag
//
//  INPUTS:		void
//
//  OUTPUT:		bool					IRQ Flag Status (true = Flag set,
//											ie. Raster IRQ has Occurred)
//
///////////////////////////////////////////////////////////////////////
bool __fastcall TOSCAVideo::GetRasterIRQ(void)
{
	return this->fRasterIRQ;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::ClearIRQ
//
//  PURPOSE:	Clear down the Raster IRQ Flag
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::ClearIRQ(void)
{
	if ((DiagForm) && (DiagForm->IsActive(DIAG_VIDEO)))
	{
		DiagForm->AddMessage(DIAG_VIDEO, L"Raster IRQ Cleared");
	}

	this->fRasterIRQ = false;
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:	TOSCAVideo::Reset
//
//  PURPOSE:	Clear down the Raster IRQ Flag
//
//  INPUTS:		void
//
//  OUTPUT:		void
//
///////////////////////////////////////////////////////////////////////
void __fastcall TOSCAVideo::Reset(void)
{
	memset(&this->VideoRAM, 0, (512 * 1024));						// Clear Video RAM
	memset(&this->SpriteRAM, 0, (128 * 1024));					// Clear Sprite RAM
	memset(&this->PaletteRAM, 0, 256 * 2 * 2);					// Clear Palette RAM
	memset(&this->fVideoLine, 0, 512 * 312);						// Clear Video Render
	memset(&this->fSpriteLine, 0, 512 * 312 * sizeof(word));	// Clear Sprite Render
	memset(&this->Sprites, 0, sizeof(SpriteRegister) * 128);	// Clear Sprite Regs
	this->fScanPrescaler 	= 0;
	this->fScanLine 			= 0;
	this->fInternalScanLine = -8;						// Guarantees an 8-pixel border
	this->fRasterIRQ			= false;
	this->fWindowXYSwitch	= false;
	this->fRasterIRQTarget	= 0x0000;
	this->fWindowTop			= 0;						// Actually -16
	this->fWindowBottom		= 160;					// Actually 144
	this->fWindowLeft			= 0;
	this->fWindowRight		= 256;
	this->fVideoPage			= 0;
	this->fSpritePage			= 0;
	this->CurrentVidSystem	= VIDEO_VGA;
	this->CurrentVidMode		= BITMAP_PLANAR;
	this->CurrentTileMode	= TILE_LEGACY;
	this->fPlaneCount			= 0;
	this->fPlaneOffset		= 0;
	this->fBlitDestMSB		= 0;
	this->fBlitSrcMSB			= 0;
	this->fBlitWidth			= 0;
	this->fBlitHeight			= 0;
	this->fBlitMisc			= 0;
	this->fBlitSrcModulo		= 0;
	this->fBlitDestModulo	= 0;
	this->fBlitSrcLoc			= 0;
	this->fBlitDestLoc		= 0;
	this->fBGVideoInhibit	= false;					// Enable Video by default
	this->fWideBorder			= false;
	this->fBitmapFloodMode	= false;
	this->fFlipPlayfields	= false;
	this->fExpandedChunky	= false;
	this->fUse8x8Tiles		= false;
	this->fRasterIRQEnable	= false;
	this->fTargetPalette		= 0;
	this->fLivePalette		= 0;
	this->fXHWS					= 0x00;
	this->fChunkyWidth		= 1;
	this->fLineCopPC			= 0;
	this->fLineCopRegister	= 0;
	this->fLineCopRunning	= false;
	this->fLineCopStartAddr = 0;
	this->fLineCopTarget		= 0;
	this->fSpriteDoubleBuf	= false;
	this->fSpriteBank			= 0;
	this->fSpriteInterleave	= false;
	this->fSpritesEnabled	= false;
	this->fSpritesMatte		= false;
	this->fSpriteForceHigh	= false;
	this->fSpriteLowColours	= false;
	this->fSpritesDirty 		= 312;

	for (int i = 0; i < 2; i++)
	{
		this->fAltMapBufferSelect[i]	= false;					// Primary Map buffer
		this->fAltTileSetSelect[i]		= false;					// Primary Tile set
		this->fPFScrollY[i]				= 0;						// No scroll
	}

	for (int i = 0; i < 16; i++)
	{
		this->fPlaneBaseAddr[i] = 0x000000;
	}
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAVideo::TOSCAVideo [CONSTRUCTOR]
//
//  PURPOSE:	Constructor for the Video Module Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAVideo::TOSCAVideo(TOSCABoard *owner) :
	m_pOwner(owner)
{
	// Perform Initial Power-On-Reset
	this->Reset();
}

///////////////////////////////////////////////////////////////////////
//
//  FUNCTION:  TOSCAVideo::~TOSCAVideo [DESTRUCTOR]
//
//  PURPOSE:	Destructor for the Video Module Class
//
//  INPUTS:		none
//
//  OUTPUT:		none
//
///////////////////////////////////////////////////////////////////////
__fastcall TOSCAVideo::~TOSCAVideo()
{
}

//---------------------------------------------------------------------------

#pragma package(smart_init)

