/*	
 *	framtia.c
 *
 *	P/ECE TIA (Television Interface Adapter) Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Fri Mar 11 03:00:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

#ifndef TIA_ASM

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

/* ATARI VCSのプログラムは、TIAへの書き込み頻度が非常に高いので、
 * tia_write()を高速化することが、全体の高速化に大きく影響します。
 */
int
tia_write(int now, int addr, int data)
{
	tia_scan_progress(now);

	ASSERT(addr >= 0);
	if(addr <= 0x2c) {
		return tia_write_table[addr](data);
	}

	return 0;
}

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

/* 1. 前回時刻(tia.now)と現在時刻(引数now)の差分だけ、走査位置を進ます。
 * 2. 現在時刻(引数now)を、次回の「前回時刻(tia.now)」として格納します。
 */
void
tia_scan_progress(int now)
{
	TIA* p_tia = &tia;
	//
	int scan_x;
	int scan_y;
	//
	unsigned clock;

	scan_x = p_tia->scan_x;		/* 取り出し */

	clock = (now - p_tia->now) * 3; /* 3 clock counts per machine cycle */
	if(clock > TIA_CLOCK / 60) { /* Safety */
		clock = TIA_CLOCK / 60;
	}
	p_tia->now = now;

	/* 水平タイミングを、経過クロックと同じPixel数、右へ進めます。 */
	scan_x += clock;
	if(scan_x >= 68/*Hblank*/ + 160) { /* 現在の走査線の右端を超えたら... */

		scan_y = p_tia->scan_y;	/* 取り出し */

		/* Hsync retrace */
		do {
			scan_y++;
			scan_x -= 68/*Hblank*/ + 160;
		} while(scan_x >= 68/*Hblank*/ + 160);

		/* Vsync retrace */
		if(p_tia->VSYNC & (1<<1)) {
			/* 標準的なタイミングでは、VSYNC=Onの状態を3Line維持します。
			 * VSYNC=Onの条件だけでVsync retraceすると、3回連続してしまいます。
			 * この問題を防ぐために、新しいフレーム開始後、一定の走査線区間は、
			 * VSYNC=OnであってもVsync retraceしないことにします。
			 */
			if(scan_y >= (3/*Vsync*/ + 37/*Vblank*/)) {
				//     scan_x = 0; ← としてはダメ!! 水平タイミングは継続!!
				       scan_y = 0;
				p_tia->last_x = 0;
				p_tia->last_y = 0;
			}
		}

		p_tia->scan_y = scan_y;	/* 書き戻し */
	}

	p_tia->scan_x = scan_x;		/* 書き戻し */
}

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

/* 1. 前回の走査終了位置(tia.last_x,y)から、現在の走査位置(tia.scan_x,y)までの区間を描画します。
 * 2. 現在の走査位置(tia.scan_x,y)を、次回の「走査終了位置(tia.last_x,y)」として格納します。
 */
void
tia_partial_update()
{
	TIA* p_tia = &tia;
	//
	const int scan_x = p_tia->scan_x;
	const int scan_y = p_tia->scan_y;
	//
	int last_x;
	int last_y;

	if(!(p_tia->VBLANK & (1<<1))) {	/* Vblank=Off */

		last_x = p_tia->last_x;
		last_y = p_tia->last_y;

		while(last_y < scan_y) {
			tia_line_update(last_y, last_x, 68/*Hblank*/ + 160);
			last_x = 0;
			last_y++;
		}
		tia_line_update(last_y, last_x, scan_x);

	} else { /* Vblank=On */
		/* 可視領域でVblank=Onは稀なので、黒塗りつぶしを省略することにします。 */
	}

	p_tia->last_x = scan_x;
	p_tia->last_y = scan_y;
}

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

static __inline int
tia_player_serial_output(TIAPLAYER* player, unsigned x)
{
	int g;

	/* Delay設定に対応するGraphicsを取り出します。
	 * 高速化のために、まずNullGraphicsか否かを判断し、NullGraphicsならばすぐに帰ります。
	 * (全画面のうち、Playerを表示するLineよりも、表示しないLineの方が多いと予想しました)
	 */
	g = player->graphics[player->delay];
	if(!g) {
		return 0;
	}

	/* 指定Pixel座標に対する相対Positionを求めます。
	 * 画面左右端は円筒状につながっていて、片方だけに半身が見えるような表示はできません。
	 */
	ASSERT(x <= 159);
	x -= player->position;
	if(x > 159) {
		x += 160;
	}
	ASSERT(x <= 159);

	/* Size設定によって、指定Pixel座標に対応する、Graphics中のbit位置を求めます。
	 * Graphicsの範囲外ならば、帰ります。
	 */
	switch(player->size) {
	default: /* 0=x */
		if(x < 8) break;
		return 0;
	case 1:  /* 1=x_x */
		if(x < 8) break;
		x -= 16;
		if(x < 8) break;
		return 0;
	case 2:  /* 2=x___x */
		if(x < 8) break;
		x -= 32;
		if(x < 8) break;
		return 0;
	case 3:  /* 3=x_x_x */
		if(x < 8) break;
		x -= 16;
		if(x < 8) break;
		x -= 16;
		if(x < 8) break;
		return 0;
	case 4:  /* 4=x_______x */
		if(x < 8) break;
		x -= 64;
		if(x < 8) break;
		return 0;
	case 5:  /* 5=XX */
		if(x < 16) {
			x >>= 1;
			break;
		}
		return 0;
	case 6:  /* 6=x___x___x */
		if(x < 8) break;
		x -= 32;
		if(x < 8) break;
		x -= 32;
		if(x < 8) break;
		return 0;
	case 7:  /* 7=XXXX */
		if(x < 32) {
			x >>= 2;
			break;
		}
		return 0;
	}
	ASSERT(x <= 7);

	/* Reflect=0ならば(左:D7..D0:右)、Reflect=1ならば(左:D0..D7:右)です。 */
	if(!player->reflect) {
		x = 7 - x;
	}
	ASSERT(x <= 7);

	/* Graphics中のbitを返します。 */
	return (g >> x) & 1;
}

static __inline int
tia_missile_serial_output(TIAMISSILE* missile, unsigned x)
{
	/* 表示Offならば、すぐに帰ります。 */
	if(!missile->graphics || /* 0=graphics disable     */
	    missile->reset) {    /* 1=missile-player reset */
		return 0;
	}

	/* 指定Pixel座標に対する相対Positionを求めます。
	 * 画面左右端は円筒状につながっていて、片方だけに半身が見えるような表示はできません。
	 */
	ASSERT(x <= 159);
	x -= missile->position;
	if(x > 159) {
		x += 160;
	}
	ASSERT(x <= 159);

	/* Size=0(1clock)の場合、(x=0   )ならばbit=1、すなわち!(x>>0)ならばbit=1です。
	 * Size=1(2clock)の場合、(x=0..1)ならばbit=1、すなわち!(x>>1)ならばbit=1です。
	 * Size=2(4clock)の場合、(x=0..3)ならばbit=1、すなわち!(x>>2)ならばbit=1です。
	 * Size=3(8clock)の場合、(x=0..7)ならばbit=1、すなわち!(x>>3)ならばbit=1です。
	 */
	return !(x >> missile->size);
}

static __inline int
tia_ball_serial_output(TIABALL* ball, unsigned x)
{
	/* Delay設定に対応する表示Offならば、すぐに帰ります。 */
	if(!ball->graphics[ball->delay]) {
		return 0;
	}

	/* 指定Pixel座標に対する相対Positionを求めます。
	 * 画面左右端は円筒状につながっていて、片方だけに半身が見えるような表示はできません。
	 */
	ASSERT(x <= 159);
	x -= ball->position;
	if(x > 159) {
		x += 160;
	}
	ASSERT(x <= 159);

	/* Size=0(1clock)の場合、(x=0   )ならばbit=1、すなわち!(x>>0)ならばbit=1です。
	 * Size=1(2clock)の場合、(x=0..1)ならばbit=1、すなわち!(x>>1)ならばbit=1です。
	 * Size=2(4clock)の場合、(x=0..3)ならばbit=1、すなわち!(x>>2)ならばbit=1です。
	 * Size=3(8clock)の場合、(x=0..7)ならばbit=1、すなわち!(x>>3)ならばbit=1です。
	 */
	return !(x >> ball->size);
}

static __inline int
tia_playfield_serial_output(TIAPLAYFIELD* playfield, unsigned x)
{
	int g;

	/* Graphicsを取り出します。
	 * 高速化のために、まずNullGraphicsか否かを判断し、NullGraphicsならばすぐに帰ります。
	 * (全画面のうち、Playfieldを表示するLineよりも、表示しないLineの方が多いと予想しました)
	 */
	g = playfield->graphics;
	if(!g) {
		return 0;
	}

	/* Playfieldの解像度は1/4の粗さで、Graphicsの1bitが画面上の4Pixelに対応します。 */
	ASSERT(x <= 159);
	x >>= 2;
	ASSERT(x <= 39);

	/* 画面左半分の80Pixel(80/4=20bit)と、右半分の80bitに、同じGraphicsを表示します。
	 * 左半分のbit並びは、Reflectに拘らず(左:D19..D0:右)です。
	 * 右半分のbit並びは、Reflect=0ならば(左:D19..D0:右)、Reflect=1ならば(左:D0..D19:右)です。
	 */
	if(x < 20) { /* left half */
		x = 19 - x;
	} else {     /* right half */
		if(!playfield->reflect) {
			x = 39 - x;
		} else {
			x = x - 20;
		}
	}
	ASSERT(x <= 19);

	/* Graphics中のbitを返します。 */
	return (g >> x) & 1;
}

void
tia_line_update(unsigned y, unsigned x1, unsigned x2)
{
	TIA* p_tia = &tia;
	//
	//
	int v;
	int collision;
	unsigned char* vbuff;
	const unsigned char* priority;

	ASSERT(x1 <= 68/*Hblank*/ + 160);
	ASSERT(x2 <= 68/*Hblank*/ + 160);
	ASSERT(x1 <= x2);

	/* 引数のyは、Vsync区間(0..2)、Vblank区間(3..39)、Overscan区間(232..)を含むLine座標です。
	 * Vsync区間とVblank区間を除いて、可視領域のLine座標(0..191)に変換します。
	 * 変換の結果、Overscan区間(192..)ならば、何もせずに帰ります。
	 */
	y -= (3/*Vsync*/ + 37/*Vblank*/);
	if(y > 191) return;

	/* 引数のx1,x2は、Hblank区間(0..67)を含むPixel座標(0..227)です。
	 * Hblank区間を除いて、可視領域のPixel座標(0..159)に変換します。
	 */
	x1 -= 68/*Hblank*/;
	if(x1 > 160) x1 = 0;
	x2 -= 68/*Hblank*/;
	if(x2 > 160) x2 = 0;
	if(x1 >= x2) return;

	/* 描画開始位置の仮想画面バッファアドレスと、
	 * Priority設定に対応する、Objectの重なりパターン->描画色index 変換テーブルを取得します。
	 */
	vbuff = p_tia->vbuff + 160 * y + x1;
	priority = tia_priority_table[(p_tia->CTRLPF >> 2) & 1];

	collision = p_tia->collision; /* 取り出し */

	/* 描画区間の各Pixelについて... */
	ASSERT(x1 < x2);
	do {
		/* Objectの重なりパターンを求めます。 */
		v =          tia_player_serial_output(&p_tia->player[0], x1);    /* Player0   */
		v = v << 1 | tia_missile_serial_output(&p_tia->missile[0], x1);  /* Missile0  */
		v = v << 1 | tia_player_serial_output(&p_tia->player[1], x1);    /* Player1   */
		v = v << 1 | tia_missile_serial_output(&p_tia->missile[1], x1);  /* Missile1  */
		v = v << 1 | tia_ball_serial_output(&p_tia->ball, x1);           /* Ball      */
		v = v << 1 | tia_playfield_serial_output(&p_tia->playfield, x1); /* Playfield */

		/* このPixelにおけるObjectの衝突を、既存の衝突フラグにマージします。 */
		collision |= tia_collision_table[v];

		/* このPixelの描画色を決定し、仮想画面バッファへ書き込みます。 */
		v = priority[v];
		if(p_tia->CTRLPF & (1<<1)) {	/* score */
			if(v == 2) {		/* COLUPF -> */
				if(x1 < 80) {	/* left half */
					v = 0;	/* -> COLUP0 */
				} else {	/* right half */
					v = 1;	/* -> COLUP1 */
				}
			}
		}
		*vbuff++ = p_tia->clut[v];

		/* 次のPixelへ。 */
		x1++;
	} while(x1 < x2);

	p_tia->collision = collision; /* 書き戻し */
}

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

#ifdef TIA_GRAYSCALE
/*
 * * 水平圧縮は 160pixel -> 128pixel の、4/5圧縮です。
 *   転送元5pixelを転送先4pixelに圧縮する作業を、1line当り32回繰り返します。
 *   転送元pixelと転送先pixelの対応は、次の通りです。
 * 
 * 	転送元pixel	転送先pixel
 * 	-----------	-----------
 * 	   0		     0
 * 	   1		     1
 * 	   2		     2
 * 	   3 + 4	     3
 * 	-----------	-----------
 * 	   5		     4
 * 	   6		     5
 * 	   7		     6
 * 	   8 + 9	     7
 * 	-----------	-----------
 * 	     .		     .
 * 	     .		     .
 * 	     .		     .
 * 
 * * 垂直圧縮は 192line -> 88line の、11/24圧縮です。
 *   転送元24lineを転送先11lineに圧縮する作業を、1frame当り8回繰り返します。
 *   転送元lineと転送先lineの対応は、次の通りです。
 * 
 * 	転送元line	転送先line	i=4
 * 	----------	----------	---------
 * 	 0 + 1		     0		i=4->3
 * 	 2 + 3		     1		i=3->2
 * 	 4 + 5		     2		i=2->1
 * 	 6 + 7 + 8	     3		i=1->0->4
 * 	 9 +10		     4		i=4->3
 * 	11 +12		     5		i=3->2
 * 	13 +14		     6		i=2->1
 * 	15 +16 +17	     7		i=1->0->4
 * 	18 +19		     8		i=4->3
 * 	20 +21		     9		i=3->2
 * 	22 +23		    10		i=2->1
 * 	----------	----------	---------
 * 	24 +25		    11		i=4->3
 * 	26 +27		    12		i=3->2
 * 	28 +29		    13		i=2->1
 * 	30 +31 +32	    14		i=1->0->4
 * 	33 +34		    15		i=4->3
 * 	35 +36		    16		i=3->2
 * 	37 +38		    17		i=2->1
 * 	39 +40 +41	    18		i=1->0->4
 * 	42 +43		    19		i=4->3
 * 	44 +45		    20		i=3->2
 * 	46 +47		    21		i=2->1
 * 	----------	----------	---------
 * 	     .		     .		    .
 * 	     .		     .		    .
 * 	     .		     .		    .
 */
void
tia_reduce(unsigned char dst[/*128*88*/], const unsigned char src[/*160*192*/])
{
	int i;
	int x;
	int y;
	int z;

	z = 8;
	do {
		y = 11;
		i = 4;
		do {
			x = 32;
			if(!--i) {
				do {
					dst[0] = tia_div3_table[(src[0] + src[160] + src[320])];
					dst[1] = tia_div3_table[(src[1] + src[161] + src[321])];
					dst[2] = tia_div3_table[(src[2] + src[162] + src[322])];
					dst[3] = tia_div3_table[(src[3] + src[163] + src[323] +
								 src[4] + src[164] + src[324]) >> 1];
					dst += 4;
					src += 5;
				} while(--x);
				src += 320;
				i = 4;
			} else {
				do {
					dst[0] = (src[0] + src[160]) >> 1;
					dst[1] = (src[1] + src[161]) >> 1;
					dst[2] = (src[2] + src[162]) >> 1;
					dst[3] = (src[3] + src[163] +
						  src[4] + src[164]) >> 2;
					dst += 4;
					src += 5;
				} while(--x);
				src += 160;
			}
		} while(--y);
	} while(--z);
}
#endif TIA_GRAYSCALE

/****************************************************************************
 *	AUDIO
 ****************************************************************************/

/* Audio0: (-15..+15) = 符号付き5bit
 * Audio1: (-15..+15) = 符号付き5bit
 * =合計=:            = 符号付き6bit
 */
#define TIA_VOLUME_SHIFT ((16-6)+1/*調整可*/) /* =11 */
 
void
tia_volume_shift(short wbuff[/*TIABUFLEN*/])
{
	int i;
	int v;

	i = TIABUFLEN;
	do {
		v = *wbuff;
		v <<= TIA_VOLUME_SHIFT;
		if(v >  32767) v =  32767;
		if(v < -32768) v = -32768;
		*wbuff++ = v;
	} while(--i);
}

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

#endif /*TIA_ASM*/

