/*	class A2Computer (category Video)

	Routines and tables for rendering frames of Apple II video.
*/
#import "LibAppleII-Priv.h"

@implementation A2Computer (Video)
//---------------------------------------------------------------------------

static unsigned DoubleBits(uint8_t b)
{/*
	Returns a 16-bit vector formed by replicating the bits of an 8-bit
	vector.  (Each 0 becomes 00, and each 1 becomes 11.)
*/
	static unsigned dbl[16] =
	{
		0x00,0x03,0x0C,0x0F, 0x30,0x33,0x3C,0x3F,
		0xC0,0xC3,0xCC,0xCF, 0xF0,0xF3,0xFC,0xFF,
	};

	return dbl[b>>4]<<8 | dbl[b&15];
}


static unsigned SpreadBits(uint8_t b)
	{ return DoubleBits(b) & 0x5555; }
	// Expands an 8-bit vector to 16, interleaving it with zeros.


static unsigned ShiftColor(unsigned c, int s)
	{ return c & 0xF0  |  0x0F & (((c&0x0F)*0x11)>>(s&3)); }
//	{ return ((c&0xF)*0x11) >> (s&3) & 0xF; }


static inline unsigned RandomByte(long* r)
	{ *r = *r * 157 % 32363;  return 0xFF & (*r>>8 ^ *r); }

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

static void InitHPixelTables(void)
{
	uint8_t  colorOfBits[1 << 6] =
	{
		//       0    1    2    3    4    5    6    7
		/* 0 */ 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x3, 0x3,
		/* 1 */ 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF,
		/* 2 */ 0x0, 0x8, 0x9, 0x9, 0xA, 0xA, 0xB, 0xB,
		/* 3 */ 0xC, 0xC, 0xD, 0xD, 0xE, 0xE, 0xF, 0xF,
		/* 4 */ 0x0, 0x0, 0x1, 0x1, 0x3, 0x3, 0x3, 0x3,
		/* 5 */ 0x4, 0xC, 0x5, 0xD, 0x7, 0xF, 0x7, 0xF, // 55??
		/* 6 */ 0x0, 0x0, 0x9, 0x9, 0xB, 0xB, 0xB, 0xB,
		/* 7 */ 0xC, 0xC, 0xD, 0xD, 0xF, 0xF, 0xF, 0xF,
	};

	for (int i = 64;  --i >= 0;)
		colorOfBits[i] |= (i << 2 & 16); // monochrome bit

	for (int i = LENGTH(A2T.vidPix.Hd);  --i >= 0;)
		for (int j = 4;  --j >= 0;)
			((uint8_t*) (A2T.vidPix.Hd + i))[j] =
				ShiftColor(colorOfBits[i>>j & 0x3F], 2-j);

	for (int i = LENGTH(A2T.vidPix.Hs);  --i >= 0;)
	{
		unsigned	mono = 0;
			// the pattern of monochrome pixels _i_ represents

		for (int j = 0;  j <= 6;  j += 2)
			mono |= ("\0\3\0\2\4\7\4\6\0\7\0\6\0\7\0\6"[i>>j & 15]) << j;
		mono &= 0x1FF;

		for (int j = 4;  --j >= 0;)
			((uint8_t*) (A2T.vidPix.Hs + i))[j] =
				ShiftColor(((uint8_t*)(A2T.vidPix.Hd + mono))[j], 1);
	}
}

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

static void InitPixelTables(void)
{/*
	Initializes the 'A2T.vpix' tables, which map video bit patterns to
	quartets of pixel values.

	The eight-bit pixel values used:
	  0x00-0F  HGR & DHGR colors, monochrome off
	    10-1F  HGR & DHGR colors, monochrome on
	    20-2F  GR & DGR colors (shades from 0 to 5 when in monochrome)
	    30-33  text: steady black, white; flashing black, white
*/
	InitHPixelTables();

	for (int i = 16;  --i >= 0;)
		A2T.vidPix.G[i] = 0x01010101 * (0x20 | i);

	for (int i = LENGTH(A2T.vidPix.Ts);  --i >= 0;)
	{
		uint8_t		*pix = (uint8_t*)(A2T.vidPix.Ts + i);

		pix[0] = pix[1] = 0x30 | (i    & 3);
		pix[2] = pix[3] = 0x30 | (i>>2 & 3);
	}

	for (int i = LENGTH(A2T.vidPix.Td);  --i >= 0;)
	{
		uint8_t		*pix = (uint8_t*)(A2T.vidPix.Td + i);

		pix[0] = 0x30 | (i    & 3);
		pix[1] = 0x30 | (i>>2 & 3);
		pix[2] = 0x30 | (i>>4 & 3);
		pix[3] = 0x30 | (i>>6 & 3);
	}
}

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

static void InitBitPatterns(void)
{
	#include "glyphs.xbm"
		// defines: static char glyphs_xbm_bits[10*9*16]

	char	fix = ~glyphs_xbm_bits[0x20 * 9]; // bits from space character

	for (int ch = 128;  --ch >= 0;)
	{
		char*	xbm = glyphs_xbm_bits + (ch&0x70)*9 + (ch&15);

		for (int row = 0;  row < 8;  ++row, xbm += 16)
		{
			A2T.vidBits.T[1][row][128+ch] = 0x1555 ^ (
				A2T.vidBits.T[1][row][ch] =
					SpreadBits((*xbm ^ fix) & 0x7F) );
		}
	}

	for (int row = 8;  --row >= 0;)
	{
		uint16_t	*p0 = A2T.vidBits.T[0][row],
					*p1 = A2T.vidBits.T[1][row],
					*p2 = A2T.vidBits.T[2][row];

		memcpy(p1+0x40, p1+0xC0, 0x20*2); // Mouse Text
		memcpy(p1+0xC0, p1+0x80, 0x20*2); // capitals

		memcpy(p0, p1, 0x100*2);
		memcpy(p0+0x40, p0, 0x40*2);
		for (int i = 0x40;  i <= 0x7F;  ++i)
			p0[i] |= 0x2AAA;

		memcpy(p2, p0, 0x100*2);
		memcpy(p2+0xE0, p0+0xC0, 0x20*2);
	}

	for (long i = sizeof(A2T.vidBits.T)/2;  --i >= 0;)
		(A2T.vidBits.T[0][0])[i] <<= 2;

	for (int i = 0x80;  --i >= 0;)
	{
		uint32_t	sb = SpreadBits(i);

		A2T.vidBits.Hs[     i] =  sb           << 6;
		A2T.vidBits.Hs[0x80+i] = (sb | 0x2AAA) << 6;
	}
}

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

+ (void)_InitVideo
{
	InitBitPatterns();
	InitPixelTables();
}

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

- (void)RenderScreen:(void*)pixBase:(int32_t)rowBytes
{/*
	Renders a frame of Apple II video into the given 8-bit deep image.

	Argument 'pixBase' points to the upper-left or lower-left pixel;
	'rowBytes' is the stride in memory from one row to the next -- the
	delta between pixels that are vertically adjacent.
*/
	PixelGroup*		pout = (PixelGroup*) pixBase;

	rowBytes /= sizeof(*pout);

	if (mHalts & kfHaltNoPower) // then power is off; render a screen of snow
	{
		for (int v = 192;  --v >= 0;  pout += rowBytes)
		{
			for (int h = 141;  (h -= 4) >= 0;)
			{
				enum {		kWhite4 = 0x2F * 0x01010101 };
				unsigned	r = A2Random16();

				r &= r >> 4 & r >> 8;
				(pout+h)[0] = kWhite4 & -(r&1);  r >>= 1;
				(pout+h)[1] = kWhite4 & -(r&1);  r >>= 1;
				(pout+h)[2] = kWhite4 & -(r&1);  r >>= 1;
				(pout+h)[3] = kWhite4 & -(r&1);
			}
		}
		return;
	}

	//------------------------------------------------------------
	// Otherwise the power is on, so render a real screen of
	// Apple II video.

	#define SETUP_FOR(TYPE) \
		enum {	kMask  = (LENGTH(A2T.vidPix.TYPE) - 1) << 2, \
				kShift = kShift##TYPE }; \
		char*	tpix = (char*)(A2T.vidPix.TYPE);

	#define BAM_(I)		pout[I] = *(PixelGroup*)(tpix + (bits & kMask))
	#define BAM(I)		BAM_(I);  bits >>= kShift

	#define HLOOP \
		for (v -= 21*192;  (v+=192) < 0;  pout += 7, pin += 2)

	#define GCASE(N) \
		case kfUpRow + kfMIXED + (N): \
		case kfUpRow + (N): \
		case kfLoRow + (N)

	#define TCASE(N) \
		case kfLoRow          + kfMIXED + (N): \
		case kfUpRow + kfTEXT           + (N): \
		case kfLoRow + kfTEXT           + (N): \
		case kfUpRow + kfTEXT + kfMIXED + (N): \
		case kfLoRow + kfTEXT + kfMIXED + (N)

	enum
	{
		kAux    = 0x2000,
		kfUpRow = 1 << 5,
		kfLoRow = 0,

		kShiftTs = 4,   kShiftHs = 4,
		kShiftTd = 8,   kShiftHd = 4,
	};

	rowBytes -= 20 * 7;

	for (int v = -1;  ++v < 192;  pout += rowBytes)
	{
		uint32_t	bits = mFlags; //!! mVideoFlags[v];
		uint8_t*	pin = (mMemory->RAM[0][0]) +
						((v<<4 & 0x380) + 40*(v>>6));
		int			dispPage =
						0x400 << (2 >> (bits>>ksPAGE2v & 3) & 1);
						// = either 0x400 or 0x800

		switch ( bits & 0x1F  |  ((v>>5)-5) & 0x20 )
		{
		//------------------------------------- 40-column text

			default:
			TCASE( 0                              ):
			TCASE(                     + kfSINGRES):
			TCASE(          + kfHIRESv            ):
			TCASE(          + kfHIRESv + kfSINGRES):
			{
				SETUP_FOR(Ts)
				uint16_t*		tbits;

				tbits = A2T.vidBits.T[bits>>ksALTCHAR & 1][v&7];
				pin += dispPage;
				*pout++ = 0;

				HLOOP
				{
					bits = tbits[pin[0]];
					BAM(0);  BAM(1);  BAM(2);
					bits |= tbits[pin[1]] << 2;
					BAM(3);  BAM(4);  BAM(5);  BAM_(6);
				}
				*pout-- = 0;
			}	break;

		//------------------------------------- 80-column text

			TCASE(+ kf80COL                       ):
			TCASE(+ kf80COL            + kfSINGRES):
			TCASE(+ kf80COL + kfHIRESv            ):
			TCASE(+ kf80COL + kfHIRESv + kfSINGRES):
			{
				SETUP_FOR(Td)
				uint16_t*		tbits;

				tbits = A2T.vidBits.T[bits>>ksALTCHAR & 1][v&7];
				pin += dispPage;
				*pout++ = 0;

				HLOOP
				{
					bits = tbits[pin[kAux+0]];
					BAM(0);
					bits |= tbits[pin[0]] << 6;
					BAM(1);  BAM(2);
					bits |= tbits[pin[kAux+1]] << 4;
					BAM(3);  BAM(4);
					bits |= tbits[pin[1]] << 2;
					BAM(5);  BAM_(6);
				}
				*pout-- = 0;

			}	break;

		//------------------------------------- 40-column GR

			GCASE( 0                              ):
			GCASE(                     + kfSINGRES):
			GCASE(+ kf80COL            + kfSINGRES):
			{
				pin += dispPage;
				*pout++ = 0;

				HLOOP
				{
					pout[0] = pout[1] = pout[2] = pout[3] =
						A2T.vidPix.G[pin[0] >> (v&4) & 15];

					((uint16_t*)pout)[7] = pout[4] = pout[5] = pout[6] =
						A2T.vidPix.G[pin[1] >> (v&4) & 15];
				}
				*pout-- = 0;

			}	break;

		//------------------------------------- 80-column GR

			GCASE(+ kf80COL                       ):
			{
				pin += dispPage;
				*pout++ = 0;

				HLOOP
				{
					pout[0] = pout[1] =
						A2T.vidPix.G[pin[kAux+0] >> (v&4) & 15];

					((uint8_t*)pout)[7] = pout[2] = pout[3] =
						A2T.vidPix.G[pin[0] >> (v&4) & 15];

					((uint16_t*)pout)[7] = pout[4] = pout[5] =
						A2T.vidPix.G[pin[kAux+1] >> (v&4) & 15];

					((uint8_t*)pout)[21] = ((uint16_t*)pout)[11] = pout[6] =
						A2T.vidPix.G[pin[1] >> (v&4) & 15];
				}
				*pout-- = 0;

			}	break;

		//------------------------------------- HGR

			GCASE(          + kfHIRESv            ):
			GCASE(          + kfHIRESv + kfSINGRES):
			GCASE(+ kf80COL + kfHIRESv + kfSINGRES):
			{
				SETUP_FOR(Hs)

				pin += dispPage<<4 | (v&7)<<10;
				bits = 0;

				HLOOP
				{
					bits |= A2T.vidBits.Hs[pin[0]];
					BAM(0);  BAM(1);  BAM(2);
					bits |= A2T.vidBits.Hs[pin[1]] << 2;
					BAM(3);  BAM(4);  BAM(5);  BAM(6);
				}
				BAM(0);  BAM_(1);

			}	break;

		//------------------------------------- DHGR

			GCASE(+ kf80COL + kfHIRESv            ):
			{
				SETUP_FOR(Hd)

				pin += dispPage<<4 | (v&7)<<10;
				bits = 0;

				HLOOP
				{
					bits |= (pin[kAux+0] & 0x7F) << 6;
					BAM(0);
					bits |= (pin[0] & 0x7F) << 9;
					BAM(1);  BAM(2);
					bits |= (pin[kAux+1] & 0x7F) << 8;
					BAM(3);  BAM(4);
					bits |= (pin[1] & 0x7F) << 7;
					BAM(5);  BAM(6);
				}
				BAM(0);  BAM_(1);

			}	break;

		} // end of switch on video flags

	} // end of for (v...)
}

//---------------------------------------------------------------------------
@end
