/*	
 *	clipt18.c
 *
 *	P/ECE TMS9918(A) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Mon Jan 3 13:00:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

/****************************************************************************
 *
 ****************************************************************************/

/*
 *	----------
 *	Register#0
 *	----------
 *	BIT 7-2		Reserved (0)
 *	BIT 1		M3 (mode bit 3)
 *	BIT 0		External VDP enable/disable
 *			0 disables external VDP input
 *			1 enables external VDP input
 *	
 *	----------
 *	Register#1
 *	----------
 *	BIT 7		4/16K Selection
 *				0 selects 4027 RAM operation
 *				1 selects 4108/4116 RAM operation
 *	BIT 6		BLANK enable/disable
 *				0 causes the active display area to blank
 *				1 enables the active display
 *				Blanking causes the display to show border color only
 *	BIT 5		IE (Interrupt Enable)
 *				0 disables VDP interrupt
 *				1 enables VDP interrupt
 *	BIT 4,3		M1, M2 (Mode bits 1 and 2)
 *			M1, M2 and M3 determine the operating mode of the VDP:
 *				M1	M2	M3
 *				 0	 0	 0	Graphics I mode
 *				 0	 0	 1	Graphics II mode
 *				 0	 1	 0	Multicolor mode
 *				 1	 0	 0	Text mode
 *	BIT 2		Reserved (0)
 *	BIT 1		Size (sprite size select)
 *				0 selects Size 0 sprites (8 x 8 bit)
 *				1 selects Size 1 sprites (16 x 16 bits)
 *	BIT 0		MAG (Magnification option for sprites)
 *				0 selects MAG0 sprites (1X)
 *				1 selects MAG1 sprites (2X)
 *	
 *	----------
 *	Register#2
 *	----------
 *	BIT 7-4		Reserved (0)
 *	BIT 3-0		Name Table base address
 *			Register 2 defines the base address of the Name Table sub-block.
 *			The range of its contents is from 0 to 15.
 *			The contents of the register from the upper 4 bits of the 14-bit Name Table address;
 *			thus the Name Table base address is equal to (Register 2)*400(hex).
 *	----------
 *	Register#3
 *	----------
 *	BIT 7-0		Color Table base address
 *			Register 3 defines the base address of the Color Table sub-block.
 *			The range of its contents is from 0 to 255.
 *			The contents of the register from the upper 8 bits of the 14-bit Color Table address;
 *			thus the Color Table base address is equal to (Register 3)*40(hex).
 *	----------
 *	Register#4
 *	----------
 *	BIT 7-3		Reserved (0)
 *	BIT 2-0		Pattern Generator base address
 *			Register 4 defines the base address of the Pattern, Text or Multicolor Generator sub-block.
 *			The range of its contents is from 0 to 7.
 *			The contents of the register from the upper 3 bits of the 14-bit Generator address;
 *			thus the Generator base address is equal to (Register 5)*800(hex).
 *	----------
 *	Register#5
 *	----------
 *	BIT 7		Reserved (0)
 *	BIT 6-0		Sprite Attribute Table base address
 *			Register 5 defines the base address of the Sprite Attribute Table sub-block.
 *			The range of its contents is from 0 to 127.
 *			The contents of the register from the upper 7 bits of the 14-bit Sprite Attribute Table address;
 *			Sprite Attribute Table base address is equal to (Register 5)*80(hex).
 *	----------
 *	Register#6
 *	----------
 *	BIT 7-3		Reserved (0)
 *	BIT 2-0		Sprite Pattern Generator base address
 *			Register 6 defines the base address of the Sprite Pattern Generator sub-block.
 *			The range of its contents is from 0 to 7.
 *			The contents of the register from the upper 3 bits of the 14-bit Sprite Pattern Generator address;
 *			Sprite Pattern Generator base address is equal to (Register 6)*800(hex).
 *	----------
 *	Register#7
 *	----------
 *	BIT 7-4		Text Color 1
 *	BIT 3-0		Text Color 0/Backdrop Color
 *			The upper 4 bits of Register 7 contain the color code of color 1 in the Text mode.
 *			The lower 4 bits contain the color code for color 0 in the Text mode and the backdrop color in all modes.
 *	------
 *	Status
 *	------
 *	BIT 7		Interrupt Flag (F)
 *			The F status flag in the status register is set to 1 at the end of the raster scan of the last line of the active display.
 *			It is reset to a 0 after the status register is read or when the VDP is externally reset.
 *			If the Interrupt Enable bit in VDP Register 1 is active (1), the VDP interrupt output (INT) will be active (low) whenever the F status flag is a 1.
 *			Note that the status register needs to be read frame by frame in order to clear the interrupt and receive the new interrupt of the next frame.
 *	BIT 6		Fifth Sprite Flag (5S)
 *			The 5S status flag in the status register is set to a 1 whenever there are five or more sprites on a horizontal line (lines 0 to 192) and the frame flag is equal to a 0.
 *			The 5S status flag is cleared to a 0 after the status register is read or the VDP is externally reset.
 *			The number of the fifth sprite is placed into the lower 5 bits of the status register when the 5S flag is set and is valid whenever the 5S flag is 1.
 *			The setting of the fifth sprite flag will not cause an interrupt.
 *	BIT 5		Coincidence Flag (C)
 *			The C status flag in the status register is set to a 1 if two or more sprites coincide.
 *			Coincidence occurs if any two sprites on the screen have one overlapping pixel.
 *			Transparent colored splites, as well as those that are partially or completely off the screen, are also considered.
 *			Sprites beyond the Sprite Attribute Table terminator (D0(16)) are not considered.
 *			The C flag is cleared to a 0 after the status register is read or the VDP is externally reset.
 *			The status register should be read immediately upon powerup to ensure that the coincidence flags is reset.
 *			The VDP checks each pixel position for coincidence during the generation of the pixel regardless of where it is located on the screen.
 *			This is occurs every 1/60th of a second for the TMS9918A and TMS9928A and every 1/50th of a second for the TMS9929A.
 *			Thus, when moving sprites more than one pixel position during these intervals,
 *			it is possible for the sprites to have multiple pixels overlapping or even to have passed completely over one another when the VDP checks for coincidence.
 *	BIT 4-0		Fifth Sprite Number
 *			(See BIT 6 : Fifth Sprite Flag (5S))
 */

/****************************************************************************
 *
 ****************************************************************************/

#ifdef TMS9918_GRAYSCALE
const unsigned char tms9918_grayscale_table[16] = {
#define TMS9918_RGB(r,g,b) ((int)((r*0.30 + g*0.59 + b*0.11) * (15/3) + 0.5) ^ 15)
#else /*TMS9918_GRAYSCALE*/
const unsigned char tms9918_color_table[16][3/*R,G,B*/] = {
#define TMS9918_RGB(r,g,b) { r*(255/3), g*(255/3), b*(255/3) }
#endif /*TMS9918_GRAYSCALE*/
/* 0 TRANSPARENT  */ TMS9918_RGB(0,0,0),
/* 1 BLACK        */ TMS9918_RGB(0,0,0),
/* 2 MEDIUM GREEN */ TMS9918_RGB(0,3,0),
/* 3 LIGHT GREEN  */ TMS9918_RGB(1,3,1),
/* 4 DARK BLUE    */ TMS9918_RGB(0,0,3),
/* 5 LIGHT BLUE   */ TMS9918_RGB(1,1,3),
/* 6 DARK RED     */ TMS9918_RGB(2,0,0),
/* 7 CYAN         */ TMS9918_RGB(1,3,3),
/* 8 MEDIUM RED   */ TMS9918_RGB(3,0,0),
/* 9 LIGHT RED    */ TMS9918_RGB(3,1,1),
/* A DARK YELLOW  */ TMS9918_RGB(3,3,0),
/* B LIGHT YELLOW */ TMS9918_RGB(3,3,2),
/* C DARK GREEN   */ TMS9918_RGB(0,2,0),
/* D MAGENTA      */ TMS9918_RGB(3,1,2),
/* E GRAY         */ TMS9918_RGB(2,2,2),
/* F WHITE        */ TMS9918_RGB(3,3,3),
};
#undef TMS9918_RGB

#ifdef TMS9918_GRAYSCALE
/* tms9918_reduce()から利用します。 */
const unsigned char
tms9918_div6_table[15 * 6 + 1] = {
	 0/6, 1/6, 2/6, 3/6, 4/6, 5/6, 6/6, 7/6, 8/6, 9/6,
	10/6,11/6,12/6,13/6,14/6,15/6,16/6,17/6,18/6,19/6,
	20/6,21/6,22/6,23/6,24/6,25/6,26/6,27/6,28/6,29/6,
	30/6,31/6,32/6,33/6,34/6,35/6,36/6,37/6,38/6,39/6,
	40/6,41/6,42/6,43/6,44/6,45/6,46/6,47/6,48/6,49/6,
	50/6,51/6,52/6,53/6,54/6,55/6,56/6,57/6,58/6,59/6,
	60/6,61/6,62/6,63/6,64/6,65/6,66/6,67/6,68/6,69/6,
	70/6,71/6,72/6,73/6,74/6,75/6,76/6,77/6,78/6,79/6,
	80/6,81/6,82/6,83/6,84/6,85/6,86/6,87/6,88/6,89/6,
	90/6,
};
#endif TMS9918_GRAYSCALE

/****************************************************************************
 *
 ****************************************************************************/

void
tms9918_reset(TMS9918* tms9918)
{
	memset(tms9918, 0, sizeof(TMS9918));
	tms9918->latch = -1;
}

int
tms9918_read(TMS9918* tms9918, int mode)
{
	int data;

	/* VRAM */
	if(!mode) {
		data = tms9918->vram[tms9918->addr];
		tms9918->addr = (tms9918->addr + 1) & 0x3fff; /* Addressオートインクリメント */

	/* Status */
	} else {
		data = tms9918->status;
		tms9918->status = 0; /* Statusクリア */
	}

	return data;
}

void
tms9918_write(TMS9918* tms9918, int mode, int data)
{
	ASSERT(!(data & ~0xff));

	/* VRAM */
	if(!mode) {
		tms9918->vram[tms9918->addr] = data;
		tms9918->addr = (tms9918->addr + 1) & 0x3fff; /* Addressオートインクリメント */

	/* Register */
	} else {
		if(tms9918->latch == -1) { /* 1st byte */
			tms9918->latch = data;
		} else {		  /* 2nd byte */
			if(data & 0x80) { /* Register */
				tms9918->reg[data & 7] = (unsigned char)tms9918->latch;
			} else {	  /* Address  */
				/* 本当はbit6で{0:Read/1:Write}を指定しているのですが、
				 * エミュレータでは区別する必要がないので同じ処理です。
				 */
				tms9918->addr = (tms9918->latch | (data << 8)) & 0x3fff;
			}
			tms9918->latch = -1;
		}
	}
}

int
tms9918_update(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/])
{
	unsigned char* reg = tms9918->reg;
	int reg0 = reg[0];
	int reg1 = reg[1];
	//
	int mode;
	int backdrop;

	/* VBLANK割り込み許可ならば、割り込みフラグをセットします。 */
	if(reg1 & 0x20) {
		tms9918->status |= 0x80;
	}

	/* 出力バッファを指定しないダミー更新ならば、ここまでです。 */
	if(!vbuff) {
		goto L_EXIT;
	}

	/* 画面非表示ならば、画面全体をバックドロップカラーで塗ります。 */
	if(!(reg1 & 0x40)) {
		backdrop = reg[7] & 0x0f;
#ifdef TMS9918_GRAYSCALE
		backdrop = tms9918_grayscale_table[backdrop];
#endif /*TMS9918_GRAYSCALE*/
		memset(vbuff, backdrop, 256 * 192);
		goto L_EXIT;
	}

	/* 画面モードを求めます。 */
	mode = ((reg0 >> 1) & 1) | ((reg1 >> 2) & 6); /* {M1,M2,M3} */

	/* 画面モードによってグラフィックスを描きます。 */
	switch(mode) {
	case 0: /* Graphics I */
		tms9918_update_graphics1(tms9918, vbuff);
		break;
	case 1: /* Graphics II */
		tms9918_update_graphics2(tms9918, vbuff);
		break;
	case 2: /* Multicolor */
		tms9918_update_multicolor(tms9918, vbuff);
		break;
	case 4: /* Text */
		tms9918_update_text(tms9918, vbuff);
		break;
	}

	/* スプライトモードによってスプライトを描きます。 */
	switch(mode) {
	case 0: /* Graphics I */
	case 1: /* Graphics II */
		switch(reg1 & 3) { /* {Size,MAG} */
		case 0: /* Size0(8x8),MAG0 */
			tms9918_update_sprite00(tms9918, vbuff);
			break;
		case 1: /* Size0(8x8),MAG1 */
			tms9918_update_sprite01(tms9918, vbuff);
			break;
		case 2: /* Size1(16x16),MAG0 */
			tms9918_update_sprite10(tms9918, vbuff);
			break;
		case 3: /* Size1(16x16),MAG1 */
			tms9918_update_sprite11(tms9918, vbuff);
			break;
		}
		break;
	}

L_EXIT:
	return tms9918->status;
}

void
tms9918_update_graphics1(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/])
{
	unsigned char* reg  = tms9918->reg;
	unsigned char* vram = tms9918->vram;
	//
	int backdrop;
	int row;
	int col;
	int code;
	int line;
	int c0;
	int c1;
	int v;
	const unsigned char* name_table;
	const unsigned char* color_table;
	const unsigned char* pattern_generator;
	const unsigned char* pattern;
	const unsigned char* color;

	/* テーブルアドレスを取得します。 */
	name_table        = &vram[(reg[2] & 0x0f) << 10]; /* 1KB境界に整列 */
	color_table       = &vram[(reg[3] & 0xff) <<  6]; /* 64B境界に整列 */
	pattern_generator = &vram[(reg[4] & 0x07) << 11]; /* 2KB境界に整列 */

	/* バックドロップカラーを取得します。 */
	backdrop = reg[7] & 0x0f;

	/* 画面全体、256x192ピクセル＝32x24セルについて... */
	for(row = 0; row < 24; row++) {
		for(col = 0; col < 32; col++) {

			/* パターン番号を取得します。 */
			code = *name_table++;

			/* パターンデータ、カラーデータの先頭アドレスを求めます。 */
			pattern = pattern_generator + code * 8;
			color = color_table + code / 8; /* 8パターン毎にカラー共有 */

			/* パターンデータの0および1に対応するカラーを取得します。 */
			c1 = *color++;
			c0 = c1 & 0xf;
			c1 >>= 4;

			/* カラーコードが0ならば、バックドロップカラーを使います。 */
			if(!c0) c0 = backdrop;
			if(!c1) c1 = backdrop;

#ifdef TMS9918_GRAYSCALE
			/* グレイスケール時は、カラーをグレイスケールに変換します。 */
			c0 = tms9918_grayscale_table[c0];
			c1 = tms9918_grayscale_table[c1];
#endif /*TMS9918_GRAYSCALE*/

			/* 1セル8ラインについて... */
			for(line = 0; line < 8; line++) {

				/* パターンデータを取得します。 */
				v = *pattern++;

				/* パターンを描きます。 */
				if(v & 0x80) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x40) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x20) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x10) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x08) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x04) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x02) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x01) { *vbuff++ = c1; } else { *vbuff++ = c0; }

				/* 次のラインへ進めます。 */
				vbuff += 256 - 8;
			}

			/* 次のセルへ進めます。 */
			vbuff -= 256 * 8 - 8;
		}

		/* 左端の下のセルへ進めます。 */
		vbuff += 256 * 8 - 256;
	}
}

void
tms9918_update_multicolor(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/])
{
	unsigned char* reg  = tms9918->reg;
	unsigned char* vram = tms9918->vram;
	//
	int backdrop;
	int row;
	int col;
	int code;
	int line;
	int c0;
	int c1;
	int i;
	const unsigned char* name_table;
	const unsigned char* pattern_generator;
	const unsigned char* pattern;

	/* テーブルアドレスを取得します。 */
	name_table        = &vram[(reg[2] & 0x0f) << 10]; /* 1KB境界に整列 */
	pattern_generator = &vram[(reg[4] & 0x07) << 11]; /* 2KB境界に整列 */

	/* バックドロップカラーを取得します。 */
	backdrop = reg[7] & 0x0f;

	/* 画面全体、256x192ピクセル＝32x24セルについて... */
	for(row = 0; row < 24; row++) {
		for(col = 0; col < 32; col++) {

			/* パターン番号を取得します。 */
			code = *name_table++;

			/* パターンデータの先頭アドレスを求めます。 */
			pattern = pattern_generator + code * 8;

			/* セル行が0,4,...,20ならば、パターンデータの{0,1}バイト目を使います。
			 * セル行が1,5,...,21ならば、パターンデータの{2,3}バイト目を使います。
			 * セル行が2,6,...,22ならば、パターンデータの{4,5}バイト目を使います。
			 * セル行が3,7,...,23ならば、パターンデータの{6,7}バイト目を使います。
			 */
			pattern += (row & 3) * 2;

			/* 1セル8x8ピクセルを描きます。
			 *            +--------+--------+
			 * pattern[0] |c1c1c1c1|c0c0c0c0| i=0,line=0
			 *            |c1c1c1c1|c0c0c0c0| i=0,line=1
			 *            |c1c1c1c1|c0c0c0c0| i=0,line=2
			 *            |c1c1c1c1|c0c0c0c0| i=0,line=3
			 *            +--------+--------+
			 * pattern[1] |c1c1c1c1|c0c0c0c0| i=1,line=0
			 *            |c1c1c1c1|c0c0c0c0| i=1,line=1
			 *            |c1c1c1c1|c0c0c0c0| i=1,line=2
			 *            |c1c1c1c1|c0c0c0c0| i=1,line=3
			 *            +--------+--------+
			 */
			for(i = 0; i < 2; i++) {

				/* 左4ピクセル(c1)、右4ピクセル(c0)のカラーを取得します。 */
				c1 = *pattern++;
				c0 = c1 & 0xf;
				c1 >>= 4;

				/* カラーコードが0ならば、バックドロップカラーを使います。 */
				if(!c0) c0 = backdrop;
				if(!c1) c1 = backdrop;

#ifdef TMS9918_GRAYSCALE
				/* グレイスケール時は、カラーをグレイスケールに変換します。 */
				c0 = tms9918_grayscale_table[c0];
				c1 = tms9918_grayscale_table[c1];
#endif /*TMS9918_GRAYSCALE*/

				/* 1セルの半分、8ラインについて... */
				for(line = 0; line < 4; line++) {
					*vbuff++ = c1; *vbuff++ = c1; *vbuff++ = c1; *vbuff++ = c1;
					*vbuff++ = c0; *vbuff++ = c0; *vbuff++ = c0; *vbuff++ = c0;

					/* 次のラインへ進めます。 */
					vbuff += 256 - 8;
				}
			}

			/* 次のセルへ進めます。 */
			vbuff -= 256 * 8 - 8;
		}

		/* 左端の下のセルへ進めます。 */
		vbuff += 256 * 8 - 256;
	}
}

void
tms9918_update_text(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/])
{
	unsigned char* reg  = tms9918->reg;
	unsigned char* vram = tms9918->vram;
	//
	int row;
	int col;
	int code;
	int line;
	int c0;
	int c1;
	int i;
	int v;
	const unsigned char* name_table;
	const unsigned char* pattern_generator;
	const unsigned char* pattern;

	/* テーブルアドレスを取得します。 */
	name_table        = &vram[(reg[2] & 0x0f) << 10]; /* 1KB境界に整列 */
	pattern_generator = &vram[(reg[4] & 0x07) << 11]; /* 2KB境界に整列 */

	/* パターンデータの0および1に対応するカラーを取得します。 */
	c1 = reg[7];
	c0 = c1 & 0xf;
	c1 >>= 4;

#ifdef TMS9918_GRAYSCALE
	/* グレイスケール時は、カラーをグレイスケールに変換します。 */
	c0 = tms9918_grayscale_table[c0];
	c1 = tms9918_grayscale_table[c1];
#endif /*TMS9918_GRAYSCALE*/

	/* Textモードは、1セル6x8ピクセルで、描画範囲は40x24セル＝240x192ピクセルです。
	 * 左右8ピクセルづつの空きを含めて、画面全体サイズは256x192ピクセルとなります。
	 */
	for(row = 0; row < 24; row++) {

		/* 左端の空き8x8ピクセルを描きます。 */
		for(line = 0; line < 8; line++) {
			for(i = 0; i < 8; i++) {
				*vbuff++ = c0;
			}
			vbuff += 256 - 8; /* 次のラインへ進めます */
		}
		vbuff -= 256 * 8 - 8; /* 次のセルへ進めます */

		/* 描画領域の一行40セルについて... */
		for(col = 0; col < 40; col++) {

			/* パターン番号を取得します。 */
			code = *name_table++;

			/* パターンデータの先頭アドレスを求めます。 */
			pattern = pattern_generator + code * 8;

			/* 1セル8ラインについて... */
			for(line = 0; line < 8; line++) {

				/* パターンデータを取得します。 */
				v = *pattern++;

				/* パターンを描きます。(1ライン6ピクセル) */
				if(v & 0x80) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x40) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x20) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x10) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x08) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				if(v & 0x04) { *vbuff++ = c1; } else { *vbuff++ = c0; }
				/* パターンデータの下位2bitは使いません。 */

				/* 次のラインへ進めます。 */
				vbuff += 256 - 6;
			}

			/* 次のセルへ進めます。 */
			vbuff -= 256 * 8 - 6;
		}

		/* 右端の空き8x8ピクセルを描きます。 */
		for(line = 0; line < 8; line++) {
			for(i = 0; i < 8; i++) {
				*vbuff++ = c0;
			}
			vbuff += 256 - 8; /* 次のラインへ進めます */
		}
		vbuff -= 256 * 8 - 8; /* 次のセルへ進めます */

		/* 左端の下のセルへ進めます。 */
		vbuff += 256 * 8 - 256;
	}
}

/* 高速RAM節約のため、使用頻度の低いスプライトモードの描画はSRAMに配置します。 */
#define TMS9918_SPRITE_IMPLEMENT
void tms9918_update_sprite00(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/]) {
#define SIZE  8 /* 8x8  */
#define MAG   1 /* 標準 */
#include "clipt18i.h"
#undef SIZE
#undef MAG
}
void tms9918_update_sprite01(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/]) {
#define SIZE  8 /* 8x8  */
#define MAG   2 /* 拡大 */
#include "clipt18i.h"
#undef SIZE
#undef MAG
}
void tms9918_update_sprite11(TMS9918* tms9918, unsigned char vbuff[/*256 * 192*/]) {
#define SIZE 16 /* 16x16 */
#define MAG   2 /* 拡大  */
#include "clipt18i.h"
#undef SIZE
#undef MAG
}
#undef TMS9918_SPRITE_IMPLEMENT

