/*
 *	main.c
 *
 *	TGAビューア
 *
 *	* Sun Apr 26 20:12:29 JST 2009 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "app.h"

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

#ifdef USE_MUSIC
#include "instdef2.c"
#endif /*USE_MUSIC*/

#ifdef USE_SOUND
const void* sound_table[SOUND_COUNT];
#endif /*USE_SOUND*/

#ifdef USE_TEXTURE
TEXTURE texture_table[TEXTURE_COUNT];
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
#define SPRITE_ SPRITE_INIT2
const SPRITE sprite_table[SPRITE_COUNT] = {
#include "sprite.h"
};
#undef SPRITE_
#endif /*USE_SPRITE*/

#define extern
#include "work.h"
#undef extern

/****************************************************************************
 *	main
 ****************************************************************************/

int
main()
{
	/* 初期化。 */
	app_init();

	/* メインループ。 */
	app_main();

	return 0;
}

/****************************************************************************
 *	app_init
 ****************************************************************************/

void
app_init()
{
	/* 一般的な初期化。 */
	pceAppSetProcPeriod(1000 / FPS);
#ifdef V_SCREEN
	pceLCDSetBuffer(_def_vbuff);
#else /*V_SCREEN*/
	pceLCDSetBuffer(vbuff);
#endif /*V_SCREEN*/
	pceLCDDispStart();
#ifdef REPEAT_PAD
	pad_set_repeat(REPEAT_PAD, REPEAT_DELAY, REPEAT_INTERVAL);
#endif /*REPEAT_PAD*/

	/* 描画環境初期化。 */
	surface.w = DISP_X;
	surface.h = DISP_Y;
	surface.vbuff = vbuff;
#ifdef USE_3D
	surface.zbuff = zbuff;
	render_init_3d(&render, &surface, 0, 0, 0, 0, FRONT, BACK, FOVY);
#else /*USE_3D*/
	render_init_2d(&render, &surface, 0, 0, 0, 0);
#endif /*USE_3D*/
#ifdef USE_RENDF
	surface.zbuff = zbuff;
	rendf_init(&rendf, &surface, 0, 0, 0, 0, FRONT, BACK, FOVY);
#endif /*USE_RENDF*/

#if defined(TRACE_ON) && (TRACE_ON)
	/* 標準エラー出力を開きます。 */
	stderr_open(9/*標準エラー出力モニタ接続待ち時間(調整可)*/);
#endif /*TRACE_ON*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを開始します。 */
	ufe_setup(9/*PC側ファイルサーバ接続待ち時間(調整可)*/);
#endif /*USE_UFE*/

#ifdef RESOURCE_FNAME
	/* pexファイル添付リソースを取得します。 */
	resource = resource_address(RESOURCE_FNAME);
#endif /*RESOURCE_FNAME*/

#ifdef TURBO
	/* クロック・メモリウェイトを切替えます。 */
	turbo(TURBO);
#endif /*TURBO*/

#ifdef USE_DBG
	/* DBGユニットを設定します。 */
	dbg_init();
#endif /*USE_DBG*/

#ifdef USE_GC
	/* ガーベージコレクターを初期化します。 */
	gc_init();
#endif /*USE_GC*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを開始します。 */
	tcpip_start(0/*Ethernetドライバ送信バッファサイズ(調整可)*/);
#endif /*USE_TCPIP*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を初期化します。 */
	ldirect_init(LDIRECT_INTERVAL);
#endif /*LDIRECT_INTERVAL*/

#ifdef USE_MUSIC
	/* 音楽ライブラリを初期化します。 */
	music_loudness(1);
	loadInst(); /* 「Randomism Zone(http://www8.plala.or.jp/randomism/)」さんの『ドラム音色分離キットver.2.10』を使用 */
	InitMusic();
#endif /*USE_MUSIC*/

	/* クリーンアップ関数を登録。 */
	atexit(app_exit);

#ifdef USE_2BIT_ADPCM
	/* 2-bit ADPCM Decoderを起動します。 */
	adpcm_init(ADPCM_CHANNELS);
#endif /*USE_2BIT_ADPCM*/

	/* サウンドを初期化します。 */
#ifdef USE_SOUND
	{
#define SOUND_(name) { extern const unsigned char name[]; sound_table[SND_##name] = name; }
#define SOUND_RESOURCE_(name,fname) { sound_table[SND_##name] = fpk_address(resource, fname, NULL); }
#include "sound.h"
#undef SOUND_
#undef SOUND_RESOURCE_
	}
#endif /*USE_SOUND*/

	/* テクスチャを初期化します。 */
#ifdef USE_TEXTURE
	{
#define TEXTURE_(name) extern const unsigned char name[];
#define TEXTURE_RESOURCE_(name,fname) const void* const name = fpk_address(resource, fname, NULL);
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		texture_table_init(texture_table, TEXTURE_COUNT,
#define TEXTURE_(name) name,
#define TEXTURE_RESOURCE_(name,fname) name,
#include "texture.h"
#undef TEXTURE_
#undef TEXTURE_RESOURCE_
		NULL/*最後の','を吸収するためのダミー。texture_table_init()に対しては無意味です。*/);
	}
#endif /*USE_TEXTURE*/

#ifdef USE_SPRITE
	/* 簡易スプライトドライバを初期化します。 */
	sprite_init(&render, sprite_table, SPRITE_COUNT, texture_table, TEXTURE_COUNT);
#endif /*USE_SPRITE*/

#ifdef USE_VIDREC
	/* 画面出力を初期化します。 */
	yield(); /* runで実行した場合の実行確認に応答するため、USB機能の切り替えを僅かに遅らせる */
#ifdef LDIRECT_INTERVAL
	cdcacm_start(0, sizeof vbuff / 2, NULL, NULL);
#else /*LDIRECT_INTERVAL*/
	cdcacm_start(0, sizeof vbuff / 4, NULL, NULL);
#endif /*LDIRECT_INTERVAL*/
#endif /*USE_VIDREC*/

/*{{アプリケーション特有の初期化*/
//	srand(pceTimerGetCount());
	file_menu_init();
	task_init();
	decode_event = event_create(0);
	timeout_timer = timer_start(1000 / FPS, timeout_proc, NULL);
/*}}アプリケーション特有の初期化*/
}

/****************************************************************************
 *	app_exit
 ****************************************************************************/

void
app_exit()
{
/*{{アプリケーション特有のクリーンアップ*/
	/** no job **/
/*}}アプリケーション特有のクリーンアップ*/

#ifdef USE_VIDREC
	/* 画面出力を終了します。 */
	cdcacm_stop();
#endif /*USE_VIDREC*/

#ifdef USE_MUSIC
	/* 確実に音楽演奏を停止します。
	 * サウンドの停止はライブラリ内のpceAppExit()で行っているので、
	 * ここでは必要ありません。
	 */
	StopMusic();
	music_loudness(0);
#endif /*USE_MUSIC*/

#ifdef LDIRECT_INTERVAL
	/* 高速切替・16階調表示を解除します。 */
	ldirect_free();
#endif /*LDIRECT_INTERVAL*/

#ifdef USE_TCPIP
	/* TCP/IPプロトコルスイートを終了します。 */
	tcpip_stop();
#endif /*USE_TCPIP*/

#ifdef USE_GC
	/* ガーベージコレクターを解除します。 */
	gc_free();
#endif /*USE_GC*/

#ifdef USE_DBG
	/* DBGユニットの設定を解除します。 */
	dbg_free();
#endif /*USE_DBG*/

#ifdef USE_UFE
	/* USBファイルシステムエミュレーションを終了します。 */
	ufe_stop();
#endif /*USE_UFE*/
}

/****************************************************************************
 *	update
 ****************************************************************************/

/* * Mon Mar 10 13:29:56 JST 2008 Naoyuki Sawa
 * - update()から、画面更新処理を分離し、高速化しました。
 */
#ifdef V_SCREEN
/* vbuff(縦画面)→_def_vbuff(横画面)変換 */
//static void rotate_screen() {
//	unsigned char *src = &vbuff[88 - 1];	/* 転送先の横画面は左上から走査開始します */
//	unsigned char *dst = &_def_vbuff[0];	/* 転送元の縦画面は右上から走査開始します */
//	int x = 88;
//	do {
//		int y = 128;
//		do {
//			*dst++ = *src;		/* 横画面を右方向に走査します */
//			src += 88;		/* 縦画面を下方向に走査します */
//		} while(--y);
//						/* 横画面は既に次の行へ移動しています */
//		src -= 88 * 128 + 1;		/* 縦画面を左の列の一番上へ移動します */
//	} while(--x);
//}
//↓高速化 (C版 = 9.2ms/1call → ASM版 = 3.2ms/1call)
/*static*/ void rotate_screen();
asm("
	.code
	.align	1
rotate_screen:
	xld.w	%r12, 0x0e00			; %r12 := FRAM4_START
	xld.w	%r13, rotate_screen_DOX		; %r13 := DOY
	xld.w	%r14, rotate_screen_END		; %r14 := END
	xcall.d	memcpy				; %r10 := FRAM4_START = memcpy(FRAM4_START, DOX, END - DOX)
	sub	%r14, %r13			; %r14 := END - DOY			*delay*
	xld.w	%r4, vbuff+87			; %r4  := src = &vbuff[88 - 1]
	xld.w	%r5, _def_vbuff			; %r5  := dst = &_def_vbuff[0]
	xld.w	%r6, 88				; %r6  := 88
	xld.w	%r7, 128			; %r7  := 128
	xld.w	%r11, 11265			; %r11 := 88 * 128 + 1
	ld.w	%r12, %r6			; %r12 := x = 88
	jp	%r10				; goto FRAM4_START			(jp.dは不可!!)
;//{{ここからFRAM4領域へ転送
rotate_screen_DOX:				; do {
	ld.w	%r13, %r7			;   %r13 := y = 128
rotate_screen_DOY:				;   do {
	ld.ub	%r9, [%r4]			;     %r9  := c = *src
	sub	%r13, 1				;     %r13 := --y			*anti-interlock*
	ld.b	[%r5]+, %r9			;     %r5  := *dst++ = c
	jrne.d	rotate_screen_DOY		;   } while(--y)
	add	%r4, %r6			;     %r4  := src += 88			*delay*
	sub	%r12, 1				;   %r12 := --x
	jrne.d	rotate_screen_DOX		; } while(--x)
	sub	%r4, %r11			;   %r4  := src -= 88 * 128 + 1		*delay*
	ret
rotate_screen_END:
;//}}ここまでFRAM4領域へ転送
");
#endif /*V_SCREEN*/

/* * Mon Nov 06 17:37:17 JST 2006 Naoyuki Sawa
 * - schedule()から、画面更新処理だけを分離しました。
 *   フレーム待ち無し画面更新だけを行う場合は、この関数を呼び出してください。
 */
void
update()
{
#ifdef V_SCREEN
	rotate_screen();
#endif /*V_SCREEN*/

	/* 画面転送 */
	pceLCDTrans();
}

/****************************************************************************
 *	vidrec
 ****************************************************************************/

#ifdef USE_VIDREC
static void
vidrec()
{
#ifdef LDIRECT_INTERVAL
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,4/*[bpp]*/,FPS };
#else /*LDIRECT_INTERVAL*/
	static const unsigned char hdr[4] = { DISP_X,DISP_Y,2/*[bpp]*/,FPS };
#endif /*LDIRECT_INTERVAL*/
	static int active;
	//
	unsigned char buf[128/*調整可*/]; /* (sizeof vbuff)/Nのサイズとすること */
	const unsigned char* src;
	unsigned char* dst;

	if(cdcacm_recv(buf, 1) > 0) {
		switch(buf[0]) {
		case 0:
			active = 0; /* 画面出力停止 */
			break;
		case 1:
			active = 1; /* 画面出力開始 */
			src = hdr;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				src += cdcacm_send(src, &hdr[sizeof hdr] - src);
			} while(src < &hdr[sizeof hdr]);
			break;
		}
	}
	if(active) {
		src = vbuff;
		do {
			dst = buf;
			do {
#ifdef LDIRECT_INTERVAL
				*dst++ = src[0]|(src[1]<<4); 
				src += 2;
#else /*LDIRECT_INTERVAL*/
				*dst++ = src[0]|(src[1]<<2)|(src[2]<<4)|(src[3]<<6);
				src += 4;
#endif /*LDIRECT_INTERVAL*/
			} while(dst < &buf[sizeof buf]);
			dst = buf;
			do {
				if(pcePadGetDirect() & PAD_SELECT) {
					exit(0); /* 送信待ちに割り込んで強制終了 */
				}
				dst += cdcacm_send(dst, &buf[sizeof buf] - dst);
			} while(dst < &buf[sizeof buf]);
		} while(src < &vbuff[sizeof vbuff]);
	}
}
#endif /*USE_VIDREC*/

/****************************************************************************
 *	schedule
 ****************************************************************************/

/* * Fri Dec 15 00:49:15 JST 2006 Naoyuki Sawa
 * - schedule()関数に、サウンドポーズ対応コードを追加しました。
 *   少し試した限りでは上手く動作しているようですが、スリープからの復帰時に問題が起きないか未確認です。
 * - もし、ポーズ状態のまま、すなわち、サウンドポーズ状態のままでアプリケーションを終了してしまった場合にも、
 *   CLiPライブラリ内のpceAppExit()でpceWaveStop(1)を呼び出しており、サウンド状態がリセットされるので、大丈夫です。
 */

void
schedule()
{
#ifdef USE_PAUSE
	{
		/* PAUSEメッセージ領域のイメージ退避用バッファは、324バイト必要です。
		 * スタック上に取るのは危険ですし、staticに取るのもメモリの無駄です。
		 * そこで、PAUSE時にのみヒープから確保することにします。
		 */
		//unsigned char save[PAUSE_H][PAUSE_W];
		unsigned char (*save)[PAUSE_W] = NULL/*警告抑制*/;
		int pause = 0;
		/*{{2006/12/15:サウンドポーズ対応*/
		int sound_pause = 0/*警告抑制*/;
		/*}}2006/12/15:サウンドポーズ対応*/
		int x, y;
		for(;;) {
#endif /*USE_PAUSE*/
			/* 画面更新。 */
			update();
#ifdef USE_VIDREC
			/* 画面出力。 */
			vidrec();
#endif /*USE_VIDREC*/
			/* システム処理。 */
			yield();
			/* パッド状態更新。 */
			joy = pad_get();
#ifdef V_SCREEN
			joy = (joy & ~(PAD_LF | PAD_RI | PAD_UP | PAD_DN | /* 縦画面用入力変換 */
				       TRG_LF | TRG_RI | TRG_UP | TRG_DN))
				| (joy & PAD_LF ? PAD_UP : 0)
				| (joy & PAD_RI ? PAD_DN : 0)
				| (joy & PAD_UP ? PAD_RI : 0)
				| (joy & PAD_DN ? PAD_LF : 0)
				| (joy & TRG_LF ? TRG_UP : 0)
				| (joy & TRG_RI ? TRG_DN : 0)
				| (joy & TRG_UP ? TRG_RI : 0)
				| (joy & TRG_DN ? TRG_LF : 0);
#endif /*V_SCREEN*/

#ifdef USE_PAUSE
			/* PAUSE処理。 */
			if(joy & TRG_START) {
				pause = !pause;	/* PAUSE状態切り替え */
				if(pause) {	/* PAUSE IN */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					sound_pause = fINT_EDMA.EHDM1;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 0;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを退避。 */
					save = (unsigned char (*)[PAUSE_W])malloc(PAUSE_W * PAUSE_H); /* イメージ退避用バッファ確保 */
					if(save == NULL) DIE();
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							save[y][x] = vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)];
						}
					}
				} else {	/* PAUSE OUT */
					/*{{2006/12/15:サウンドポーズ対応*/
					ENTER_CS;
					if(sound_pause) {
						fINT_EDMA.EHDM1 = 1;
					}
					LEAVE_CS;
					/*}}2006/12/15:サウンドポーズ対応*/
					/* PAUSEメッセージ表示部分のイメージを復帰。 */
					for(y = 0; y < PAUSE_H; y++) {
						for(x = 0; x < PAUSE_W; x++) {
							vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
						}
					}
					free(save); /* イメージ退避用バッファ開放 */
				}
			}
			if(!pause) break;

			/* PAUSEメッセージ点滅表示。 */
			if(pceTimerGetCount() % 1000 < 750) {
				render_string_framed(&render, PAUSE_X, PAUSE_Y, PAUSE_STR, PAUSE_FONT, PAUSE_COLOR);
			} else {
				for(y = 0; y < PAUSE_H; y++) {
					for(x = 0; x < PAUSE_W; x++) {
						vbuff[DISP_X * (PAUSE_Y + y) + (PAUSE_X + x)] = save[y][x];
					}
				}
			}
		}
	}
#endif /*USE_PAUSE*/

	if(joy & TRG_SELECT) {
		exit(0);
	}
	/*{{確実に(1000/FPS)[sec]の処理時間を与えるために、リスタート*/
	timer_stop(timeout_timer);
	decode_timeout = 0;
	timeout_timer = timer_start(1000 / FPS, timeout_proc, NULL);
	/*}}確実に(1000/FPS)[sec]の処理時間を与えるために、リスタート*/
	event_set(decode_event);
	buff_disp();
	menu_disp(file_menu, &render);

	now++;	/* グローバルタイマ更新 */
}

/****************************************************************************
 *	app_main
 ****************************************************************************/

void
app_main()
{
	int cancel_ok = 0;
	int size;

	for(;;) {
		/* メニュー出現。 */
		for(size = 1; size < MENU_SIZE; size += 10) {
			schedule();
			menu_set_pos(file_menu, MENU_X, MENU_Y, size, size);
		}
		menu_set_pos(file_menu, MENU_X, MENU_Y, MENU_SIZE, MENU_SIZE);

		/* メニュー選択。 */
		for(;;) {
			schedule();
			/*{{キャンセル時に選択変更されないよう、menu_action()の完了機構は使わない*/
			menu_action(file_menu, joy & ~TRG_AB);
			if(joy & TRG_A) {
				decode_start();
				break;
			}
			if(cancel_ok) {
				if(joy & TRG_B) {
					break;
				}
			}
			/*}}キャンセル時に選択変更されないよう、menu_action()の完了機構は使わない*/
		}
		cancel_ok = 1;

		/* メニュー消去。 */
		for(size = MENU_SIZE; size > 0; size -= 10) {
			schedule();
			menu_set_pos(file_menu, MENU_X, MENU_Y, size, size);
		}
		menu_set_pos(file_menu, MENU_X, MENU_Y, 0, 0);

		/* 画像表示。 */
		for(;;) {
			schedule();
			if(joy & TRG_AB) {
				break;
			}
		}
	}
}

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

void
file_menu_init()
{
	static const MENUPALETTE pal = {
		item   : 0,
		back   : 12,
		title  : 15,
		border : 9,
	};
	FILEINFO fi;

	file_menu = menu_create();
	menu_set_font(file_menu, 2);
	menu_set_palette(file_menu, &pal);
	menu_set_pos(file_menu, MENU_X, MENU_Y, 0, 0);

	pceFileFindOpen(&fi);
	while(pceFileFindNext(&fi)) {
		int len = strlen(fi.filename);
		if((len >= 4) && !strcasecmp(&fi.filename[len - 4], ".tga")) {
			menu_add_item(file_menu, fi.filename);
		}
	}
	pceFileFindClose(&fi);

	if(!menu_num_items(file_menu)) {
		ldirect_free();
		//   0123456789012345678901234
		die("TGAﾌｧｲﾙが有りません");
	}
}

void
timeout_proc(void* context)
{
	decode_timeout = 1;
}

void
decode_start()
{
	int sel;
	const void* ptr;
	char fname[MAXFILENAME + 1];

	if(decode_task) {
		task_kill(decode_task);
	}
	memset(abuff, 0, sizeof abuff);
	memset(bbuff, 0, sizeof bbuff);
	memset(cbuff, 0, sizeof cbuff);

	sel = menu_get_sel(file_menu);
	menu_get_item(file_menu, sel, fname, sizeof fname);
	ptr = file_defrag(fname, NULL);
	decode_task = task_create(0, decode_task_proc, (void*)ptr, 0);
}

int
decode_task_proc(void* param)
{
	DECODE_PARAM dp;
	dp.scale_mul = 1;
	dp.scale_div = 1;
	tga_decode(param, size_proc, draw_proc, &dp);
	return 0;
}

void
size_proc(int w, int h, void* user_data)
{
	DECODE_PARAM* dp = user_data;
	int scale_w;
	int scale_h;
	scale_w = w * dp->scale_mul / dp->scale_div;
	scale_h = h * dp->scale_mul / dp->scale_div;
	if(scale_w > DISP_X) {
		dp->scale_mul = DISP_X;
		dp->scale_div = w;
	}
	scale_w = w * dp->scale_mul / dp->scale_div;
	scale_h = h * dp->scale_mul / dp->scale_div;
	if(scale_h > DISP_Y) {
		dp->scale_mul = DISP_Y;
		dp->scale_div = h;
	}
	scale_w = w * dp->scale_mul / dp->scale_div;
	scale_h = h * dp->scale_mul / dp->scale_div;
	dp->org_x = (DISP_X - scale_w) / 2;
	dp->org_y = (DISP_Y - scale_h) / 2;
}

void
draw_proc(int x, int y, int r, int g, int b, int a, void* user_data)
{
	DECODE_PARAM* dp = user_data;
	x = (x * dp->scale_mul / dp->scale_div) + dp->org_x;
	y = (y * dp->scale_mul / dp->scale_div) + dp->org_y;
	cbuff[y][x] += 1;
	abuff[y][x] += a;
	bbuff[y][x] += (r * 30 + g * 59 + b * 11) / 100;
	if(decode_timeout) {
		event_wait(decode_event);
	}
}

