/*	
 *	clipdmv.c
 *
 *	P/ECE DMG-Video Emulator
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Fri Feb 18 20:18:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
#include "clip.h"

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

DMGVIDEO dmgvideo;

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

void
dmgvideo_reset(unsigned char* mem)
{
	DMGVIDEO* video = &dmgvideo;

	memset(video, 0, sizeof(DMGVIDEO));
	video->mem = mem;
}

int
dmgvideo_read(int addr)
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int data;

	switch(addr) {
	//case 0xff40: /* $FF40 (LCDC) */

	case 0xff41: /* $FF41 (STAT) */
		/* * 画面内では、各ライン毎に Mode 10->11->00 と変化します。
		 *   画面外では、             Mode 01         を維持します。
		 *   本当は時間でMode変化すべきですが、簡略化して、読み出し毎に変化させることにします。
		 * * このMode変化処理をはしょると、『パズルボーイ』が起動しません。
		 *   このMode変化処理を行っても『パズルボーイ』は動作が怪しいのですが、(パレット化け？)
		 *   起動しないよりはマシです。。。
		 */
		data = mem[0xff41];
		switch(data & 3) {
		//case 0: /* Mode 00: During H-Blank */ -> dmgvideo_next_line()が呼ばれるまでそのまま
		//case 1: /* Mode 01: During V-Blank */ -> dmgvideo_new_frame()が呼ばれるまでそのまま
		case 2:	/* Mode 10: During Searching OAM-RAM (Object Attribute Memory) */
			mem[0xff41] |=  1; /* 今回はMode 10を返し、次回はMode 11とする */
			break;
		case 3:	/* Mode 11: During Transfering Data to LCD Driver */
			mem[0xff41] &= ~3; /* 今回はMode 11を返し、次回はMode 00とする */
			break;
		}
		return data; /* mem[0xff41]変更前の値を返します */

	//case 0xff42: /* $FF42 (SCY) */
	//case 0xff43: /* $FF43 (SCX) */
	//case 0xff44: /* $FF44 (LY) */
	//case 0xff45: /* $FF45 (LYC) */
	//case 0xff46: /* $FF46 (DMA) */
	//case 0xff47: /* $FF47 (BGP) */
	//case 0xff48: /* $FF48 (OBP0) */
	//case 0xff49: /* $FF49 (OBP1) */
	//case 0xff4a: /* $FF4A (WY) */
	//case 0xff4b: /* $FF4B (WX) */
	}

	return mem[addr];
}

void
dmgvideo_write(int addr, int data)
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	unsigned char* clut;

	if(mem[addr] == data) {
		return;
	}

	switch(addr) {
	case 0xff40: /* $FF40 (LCDC) */
	case 0xff42: /* $FF42 (SCY) */
	case 0xff43: /* $FF43 (SCX) */
	case 0xff4a: /* $FF4A (WY) */
	case 0xff4b: /* $FF4B (WX) */
		/* レジスタ値を変更する前に、これまでの走査区間を描画します。 */
		dmgvideo_partial_update();
		break; /* レジスタ値格納へ */

	case 0xff41: /* $FF41 (STAT) */
		/* 表\示には影響ないので、これまでの走査区間の描画は不要です。 */
		/* 割り込み制御Bitだけを格納し、LYC==LYとMode値は維持します。 */
		mem[addr] = (mem[addr] & 7) | (data & ~7);
		return; /*↑レジスタ値格納済み */

	case 0xff44: /* $FF44 (LY) */
	case 0xff45: /* $FF45 (LYC) */
		/* 表\示には影響ないので、これまでの走査区間の描画は不要です。 */
		break; /* レジスタ値格納へ */

	case 0xff46: /* $FF46 (DMA) */
		/* スプライトを転送する前に、これまでの走査区間を描画します。 */
		dmgvideo_partial_update();
		/* スプライトを転送します。 */
		memcpy(&mem[0xfe00], &mem[data << 8], 4 * 40);
		/* 書き込み検出のため、レジスタ値=0(有り得ない値)のままです。 */
		return; /* レジスタ値格納★しません★!! */

	case 0xff47: /* $FF47 (BGP) */
	case 0xff48: /* $FF48 (OBP0) */
	case 0xff49: /* $FF49 (OBP1) */
		/* パレット値を変更する前に、これまでの走査区間を描画します。 */
		dmgvideo_partial_update();
		/* パレットを展開し、カラールックアップテーブルを更新します。 */
		clut = &video->clut[(addr - 0xff47) * 4];
		*clut++ = data & 3; data >>= 2;
		*clut++ = data & 3; data >>= 2;
		*clut++ = data & 3; data >>= 2;
		*clut++ = data & 3;
		break; /* レジスタ値格納へ */
	}

	mem[addr] = data;
}

int
dmgvideo_new_frame(unsigned char vbuff[/*160*144]*/])
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int irq = 0;

	video->vbuff      = vbuff;
	video->scan_start = 0;
	video->scan_end   = 0;

	/*{{dmgvideo_new_frame(),dmgvideo_next_line()共通*/
	mem[0xff44] = video->scan_end;			/* $FF44 (LY) */
	mem[0xff41] &= ~(1<<2);				/* $FF41 (STAT) [LYC!=LY] */
	if(mem[0xff45] == video->scan_end) {		/* $FF45 (LYC) */
		mem[0xff41] |= (1<<2);			/* $FF41 (STAT) [LYC==LY] */
		if(mem[0xff41] & (1<<6)) {		/* $FF41 (STAT) */
			irq |= (1<<1);			/* LCDC Status Interrupt */
		}
	}
	if(video->scan_end < 144) {
		mem[0xff41] = (mem[0xff41] & ~3) | 2;	/* $FF41 (STAT) [Mode 10] */
		if(mem[0xff41] & ((1<<5)|(1<<3))) {	/* $FF41 (STAT) [10:OAM,00:H-Blank(ﾌｪｲｸ)] */ //Nemesisが使ってます
			irq |= (1<<1);			/* LCDC Status Interrupt */
		}
	}
	/*}}dmgvideo_new_frame(),dmgvideo_next_line()共通*/

	return irq;
}

int
dmgvideo_next_line()
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int irq = 0;

	if(video->scan_end >= 154) {
		/* dmgvideo_new_frame()一回に対し、dmgvideo_next_line()が(154+1)回以上呼び出されました!!
		 * dmgvideo_new_frame()一回に対する正しいdmgvideo_next_line()呼び出し回数は、154回です。
		 */
		DIE();
	}

	video->scan_end++;
	if(video->scan_end < 154) {				/* 0..152 -> 1..153 */

		/*{{dmgvideo_new_frame(),dmgvideo_next_line()共通*/
		mem[0xff44] = video->scan_end;			/* $FF44 (LY) */
		mem[0xff41] &= ~(1<<2);				/* $FF41 (STAT) [LYC!=LY] */
		if(mem[0xff45] == video->scan_end) {		/* $FF45 (LYC) */
			mem[0xff41] |= (1<<2);			/* $FF41 (STAT) [LYC==LY] */
			if(mem[0xff41] & (1<<6)) {		/* $FF41 (STAT) */
				irq |= (1<<1);			/* LCDC Status Interrupt */
			}
		}
		if(video->scan_end < 144) {
			mem[0xff41] = (mem[0xff41] & ~3) | 2;	/* $FF41 (STAT) [Mode 10] */
			if(mem[0xff41] & ((1<<5)|(1<<3))) {	/* $FF41 (STAT) [10:OAM,00:H-Blank(ﾌｪｲｸ)] */ //Nemesisが使ってます
				irq |= (1<<1);			/* LCDC Status Interrupt */
			}
		}
		/*}}dmgvideo_new_frame(),dmgvideo_next_line()共通*/

		if(video->scan_end == 144) {
			mem[0xff41] = (mem[0xff41] & ~3) | 1;	/* $FF41 (STAT) [Mode 01] */
			if(mem[0xff41] & (1<<4)) {		/* $FF41 (STAT) [01:V-Blank] */
				irq |= (1<<1);			/* LCDC Status Interrupt */
			}
			irq |= (1<<0);				/* Vertical Blank Interrupt */
			dmgvideo_partial_update();		/* 未更新部分を描画します。 */
		}
	}

	return irq;
}

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

/* * GameBoyのLCDCタイミングは、下図のとおりです。
 *   サイクル単位はCPUクロックと同じで、(2^22)=4194304[Hz] です。
 *   一画面のサイクル数は 456×154 = 70224 ですから、
 *   フレームレートは 4194304[Hz]÷70224 = 59.73[Hz] となります。
 *
 *	|<---------------456[Cycle]--------------->|
 *	|77~83[Cycle]| 169-173[Cycle] |            |
 *	|<--Mode10-->|<----Mode11---->|<--Mode00-->|
 *	+------------+----------------+------------+
 *	|////////////|                |////////////|<--Line#0
 *	|////////////|                |////////////|
 *	|////////////|                |////////////|
 *	|////////////|                |////////////|
 *	|////////////| この部分が     |////////////|
 *	|////////////| 160x144Pixelの |////////////|
 *	|////////////| 可視領域です。 |////////////|
 *	|////////////|                |////////////|
 *	|////////////|                |////////////|
 *	|////////////|                |////////////|
 *	|////////////|                |////////////|<--Line#143
 *	+------------+----------------+------------+
 *	|//////////////////////////////////////////|<--Line#144
 *	|//////////////////////////////////////////| A
 *	|//////////////////////////////////////////| | Mode01
 *	|//////////////////////////////////////////| V
 *	|//////////////////////////////////////////|<--Line#153
 *	+------------------------------------------+
 *
 *	[$FF41 (STAT) D1:0]
 *	Mode 00: During H-Blank
 *	Mode 01: During V-Blank
 *	Mode 10: During Searching OAM-RAM (Object Attribute Memory)
 *	Mode 11: During Transfering Data to LCD Driver
 */

void
dmgvideo_partial_update()
{
	DMGVIDEO* video      = &dmgvideo;
	unsigned char* mem   = video->mem;
	unsigned char* vbuff = video->vbuff;
	unsigned char* clut  = video->clut;
	int scan_start       = video->scan_start;
	int scan_end         = video->scan_end;
	//
	int mode;

	if(!vbuff) {			/* 現在のフレームの実描画が要求されていない */
		goto L_EXIT;
	}
	if(scan_start >= scan_end) {	/* 前回の更新以降、まだ走査線が進んでいない */
		goto L_EXIT;
	}
	if(scan_end > 144) {		/* V-Blank区間 */
		goto L_EXIT;
	}

	/* 更新領域の背景を塗りつぶします。 */
	memset(&vbuff[scan_start * 160], clut[0/*BGP(0)*/], (scan_end - scan_start) * 160);

	mode = mem[0xff40]; /* $FF40 (LCDC) */
	if(mode & (1<<7)) {
		if(mode & (1<<1)) {
			dmgvideo_draw_sprite(1);
		}
		if(mode & (1<<0)) {
			dmgvideo_draw_bg();
			if(mode & (1<<5)) {
				dmgvideo_draw_window();
			}
		}
		if(mode & (1<<1)) {
			dmgvideo_draw_sprite(0);
		}
	}

L_EXIT:
	/* 次回の更新区間の先頭ラインを設定します。 */
	video->scan_start = scan_end;
}

void
dmgvideo_draw_bg()
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	int scan_start     = video->scan_start;
	int scan_end       = video->scan_end;
	//
	int i;
	int x;
	int y;
	int col;
	int row;
	int code;
	int mode;
	int tile_map;

	mode = mem[0xff40]; /* $FF40 (LCDC) */
	tile_map = !(mode & (1<<3)) ? 0x9800 : 0x9c00;

	y = mem[0xff42]; /* $FF42 (SCY) */
	x = mem[0xff43]; /* $FF43 (SCX) */
	row = y >> 3; /* y = 0..255 -> row = 0..31 */
	col = x >> 3; /* x = 0..255 -> col = 0..31 */
	y = -(y & 7); /* y = -7..0 */
	x = -(x & 7); /* x = -7..0 */

	do {
		/* 描画領域の上方向に完全に外れるRowはスキップします。 */
		if(y > scan_start - 8) {
			i = 160 / 8 + 1;
			do {
				code = mem[tile_map | row<<5 | col];
				if(!(mode & (1<<4))) {
					code = 0x100 + (char)code;
				}
				dmgvideo_draw_chr(x, y, code, 0/*BGP*/);
				//
				x += 8;
				col = (col + 1) & 31;
			} while(--i);
			x -= 8 * (160 / 8 + 1);
			col = (col - (160 / 8 + 1)) & 31;
		}
		y += 8;
		row = (row + 1) & 31;
	} while(y < scan_end); /* 描画領域を下方向へ完全に外れたら、抜けます。 */
}

void
dmgvideo_draw_window()
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	int scan_start     = video->scan_start;
	int scan_end       = video->scan_end;
	//
	int x;
	int y;
	int col;
	int row;
	int code;
	int mode;
	int tile_map;

	mode = mem[0xff40]; /* $FF40 (LCDC) */
	tile_map = !(mode & (1<<6)) ? 0x9800 : 0x9c00;

	y = mem[0xff4a]; /* $FF4A (WY) */
	x = mem[0xff4b]; /* $FF4B (WX) */
	x -= 7; /* 仕様 */
	if((y >= 144) || (x >= 160)) {
		return;
	}

	row = 0;
	do {
		/* 描画領域の上方向に完全に外れるRowはスキップします。 */
		if(y > scan_start - 8) {
			col = 0;
			do {
				code = mem[tile_map | row<<5 | col];
				if(!(mode & (1<<4))) {
					code = 0x100 + (char)code;
				}
				dmgvideo_draw_chr_opaque(x, y, code, 0/*BGP*/);
				//
				x += 8;
				col++;
			} while(x < 160); /* 画面右へ完全に外れたら、1行終了。 */
			x -= 8 * col;
		}
		y += 8;
		row++;
	} while(y < scan_end); /* 描画領域を下方向へ完全に外れたら、抜けます。 */
}

void
dmgvideo_draw_sprite(int priority)
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int mode;

	mode = mem[0xff40]; /* $FF40 (LCDC) */
	if(!(mode & (1<<2))) {
		dmgvideo_draw_sprite_8x8(priority);
	} else {
		dmgvideo_draw_sprite_8x16(priority);
	}
}

void
dmgvideo_draw_sprite_8x8(int priority)
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int i;
	int xpos;
	int ypos;
	int code;
	int attr;
	int palno;
	unsigned int* p;

	p = (unsigned int*)&mem[0xfe00 + (4 * 40)];
	i = 40;
	do {
		attr = *--p;
		if((!priority && attr >= 0) ||
		   ( priority && attr <  0)) {

			ypos = (unsigned char)attr;
			attr >>= 8;
			xpos = (unsigned char)attr;
			attr >>= 8;
			code = (unsigned char)attr;
			attr >>= 8;

			ypos -= 16; /* 仕様 */
			xpos -= 8;  /* 仕様 */
			palno = ((attr >> 4) & 1) + 1/*OBPx*/;

			switch((attr >> 5) & 3) {
			default: dmgvideo_draw_chr      (xpos, ypos, code, palno); break;
			case 1:  dmgvideo_draw_chr_revx (xpos, ypos, code, palno); break;
			case 2:  dmgvideo_draw_chr_revy (xpos, ypos, code, palno); break;
			case 3:  dmgvideo_draw_chr_revxy(xpos, ypos, code, palno); break;
			}
		}
	} while(--i);
}

void
dmgvideo_draw_sprite_8x16(int priority)
{
	DMGVIDEO* video    = &dmgvideo;
	unsigned char* mem = video->mem;
	//
	int i;
	int xpos;
	int ypos;
	int code;
	int attr;
	int palno;
	unsigned int* p;

	p = (unsigned int*)&mem[0xfe00 + (4 * 40)];
	i = 40;
	do {
		attr = *--p;
		if((!priority && attr >= 0) ||
		   ( priority && attr <  0)) {

			ypos = (unsigned char)attr;
			attr >>= 8;
			xpos = (unsigned char)attr;
			attr >>= 8;
			code = (unsigned char)attr;
			attr >>= 8;

			ypos -= 16; /* 仕様 */
			xpos -= 8;  /* 仕様 */
			code &= ~1; /* 仕様 */
			palno = ((attr >> 4) & 1) + 1/*OBPx*/;

			switch((attr >> 5) & 3) {
			default: dmgvideo_draw_chr      (xpos, ypos    , code + 0, palno);
			         dmgvideo_draw_chr      (xpos, ypos + 8, code + 1, palno); break;
			case 1:  dmgvideo_draw_chr_revx (xpos, ypos    , code + 0, palno);
			         dmgvideo_draw_chr_revx (xpos, ypos + 8, code + 1, palno); break;
			case 2:  dmgvideo_draw_chr_revy (xpos, ypos    , code + 1, palno);
			         dmgvideo_draw_chr_revy (xpos, ypos + 8, code + 0, palno); break;
			case 3:  dmgvideo_draw_chr_revxy(xpos, ypos    , code + 1, palno);
			         dmgvideo_draw_chr_revxy(xpos, ypos + 8, code + 0, palno); break;
			}
		}
	} while(--i);
}

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

/* * 描画速度は、垂直、水平反転無しのキャラ描画速度だけで九割方決まるはずです。
 *   スプライトよりもBGとWindowの描画面積がずっと大きく、BGとWindowには反転機能\が無いからです。
 * * 実行速度と高速RAM容量節約との兼ね合いで、反転無しのキャラ描画だけを高速RAMに配置することにしました。
 *   反転有りのキャラ描画(三種類)は、SRAMに配置し、アセンブラ化もしていません。(2005/03/04現在)
 */

#define DMGVIDEO_DRAW_CHR_IMPLEMENT
//
#ifndef DMGVIDEO_ASM
//
#define DMGVIDEO_REVX	0
#define DMGVIDEO_REVY	0
#define DMGVIDEO_OPAQUE	0
void dmgvideo_draw_chr(int xpos, int ypos, int code, int palno)
#include "clipdmvi.h"
#undef DMGVIDEO_REVX
#undef DMGVIDEO_REVY
#undef DMGVIDEO_OPAQUE
//
#define DMGVIDEO_REVX	0
#define DMGVIDEO_REVY	0
#define DMGVIDEO_OPAQUE	1
void dmgvideo_draw_chr_opaque(int xpos, int ypos, int code, int palno)
#include "clipdmvi.h"
#undef DMGVIDEO_REVX
#undef DMGVIDEO_REVY
#undef DMGVIDEO_OPAQUE
//
#endif /*DMGVIDEO_ASM*/
//
#define DMGVIDEO_REVX	1
#define DMGVIDEO_REVY	0
#define DMGVIDEO_OPAQUE	0
void dmgvideo_draw_chr_revx(int xpos, int ypos, int code, int palno)
#include "clipdmvi.h"
#undef DMGVIDEO_REVX
#undef DMGVIDEO_REVY
#undef DMGVIDEO_OPAQUE
//
#define DMGVIDEO_REVX	0
#define DMGVIDEO_REVY	1
#define DMGVIDEO_OPAQUE	0
void dmgvideo_draw_chr_revy(int xpos, int ypos, int code, int palno)
#include "clipdmvi.h"
#undef DMGVIDEO_REVX
#undef DMGVIDEO_REVY
#undef DMGVIDEO_OPAQUE
//
#define DMGVIDEO_REVX	1
#define DMGVIDEO_REVY	1
#define DMGVIDEO_OPAQUE	0
void dmgvideo_draw_chr_revxy(int xpos, int ypos, int code, int palno)
#include "clipdmvi.h"
#undef DMGVIDEO_REVX
#undef DMGVIDEO_REVY
#undef DMGVIDEO_OPAQUE
//
#undef DMGVIDEO_DRAW_CHR_IMPLEMENT

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

