/*
 *	main.c
 *
 *	NcsEdit.exeのデータを利用するサンプルアプリ
 *
 *	* Fri Oct 09 00:01:44 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());
/*}}アプリケーション特有の初期化*/
}

/****************************************************************************
 *	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*/
			/* システム処理。 */
#ifdef USE_IDLE
			idle_yield();
#else /*USE_IDLE*/
			yield();
#endif /*USE_IDLE*/
			/* パッド状態更新。 */
			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);
	}

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

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

/* 敵の動きの軌跡となる、NCS補間係数テーブル。NcsEdit.exeを使って生成しました。
 * ncs_a.c等での定義と以下の宣言とでconstの有無が異なっている理由は、正しくconstを書くとconstが多すぎて面倒だからです。
 * P/ECEはRAMに展開して実行するからconstの有無は関係無いし、もしROMに置く場合も書き換えようとしなければ問題有りません。
 */
extern NCS* ncs_a[2/*x,y*/]; /* ENEMY.type = 0 */
extern NCS* ncs_b[2/*x,y*/]; /* ENEMY.type = 1 */
extern NCS* ncs_c[2/*x,y*/]; /* ENEMY.type = 2 */
NCS** ncs_tbl[] = { ncs_a, ncs_b, ncs_c };

/* 敵 */
typedef struct _ENEMY {
	int type;	/* 動きタイプ (0...2) */
	float s;	/* 速度 (毎フレームのt増分, 0.0 = 空き) */
	float t;	/* 位置係数 (0.0...1.0) */
	float x;	/* X座標 (...0.0...DISP_X...) */
	float y;	/* Y座標 (...0.0...DISP_Y...) */
} ENEMY;
ENEMY enemy;	/* 敵は1体のみ */

/* 敵を動かす */
void enemy_move() {
	/* 空きか、または、位置係数が終端を超えていたら… */
	if((enemy.s == 0.0f) || (enemy.t > 1.0f)) {
		/* 新しい敵を初期化する */
		enemy.type = RND32_RANGE(seed, 0, ARRAY_SIZE(ncs_tbl));	/* 動きタイプ */
		enemy.s = RNDF_RANGE(seed, 0.005f, 0.02f);		/* 速度 */
		enemy.t = 0.0f;						/* 位置係数 */
	}
	/* 動きタイプと位置係数から、NCS補間を使って、座標を計算する。 */
	enemy.x = NCS_interpolate(ncs_tbl[enemy.type][0/*x*/], enemy.t) + (DISP_X / 2.0f);
	enemy.y = NCS_interpolate(ncs_tbl[enemy.type][1/*y*/], enemy.t) + (DISP_Y / 2.0f);
	/* 位置係数を、速度の分進める */
	enemy.t += enemy.s;
}

/* 敵を描画する */
void enemy_draw() {
	/* 空きで無ければ… */
	if(enemy.s != 0.0) {
		/* 座標を中心に、10x10ドットの四角を描く */
		render_rectangle_fill(&render, enemy.x - 5, enemy.y - 5, 10, 10, 3);
	}
}

/*--------------------------------------------------------------------------*/

/* プレイヤーショット */
typedef struct _PSHOT {
	float x;	/* X座標 (...0.0...DISP_X...) */
	float y;	/* Y座標 (...0.0...DISP_Y..., -10.0以下 = 空き) */
} PSHOT;
PSHOT pshot = { 0.0f, -10.0f };	/* プレイヤーショットは1発のみ。初期状態は空き */

/* プレイヤーショットを動かす */
void pshot_move() {
	/* 空きで無ければ… */
	if(pshot.y > -10.0f) {
		/* 10ドット上へ移動する */
		pshot.y -= 10.0f;
	}
}

/* プレイヤーショットを描画する */
void pshot_draw() {
	/* 空きで無ければ… */
	if(pshot.y > -10.0f) {
		/* 座標を中心に、10x10ドットの四角を描く */
		render_rectangle_fill(&render, pshot.x - 5, pshot.y - 5, 10, 10, 3);
	}
}

/*--------------------------------------------------------------------------*/

/* プレイヤー */
typedef struct _PLAYER {
	float x;	/* X座標 (...0.0...DISP_X...) */
	float y;	/* Y座標 (...0.0...DISP_Y...) */
} PLAYER;
PLAYER player = { DISP_X / 2.0f, DISP_Y - 10.0f };	/* プレイヤーは1体のみ */

/* プレイヤーを動かす */
void player_move() {
	/* 左ボタンが押されていたら… */
	if(joy & PAD_LF) {
		/* 3ドット左へ移動する */
		player.x -= 3.0f;
		/* 左移動限界チェック */
		if(player.x < 10.0f) {
			player.x = 10.0f;
		}
	}
	/* 右ボタンが押されていたら… */
	if(joy & PAD_RI) {
		/* 3ドット右へ移動する */
		player.x += 3.0f;
		/* 右移動限界チェック */
		if(player.x > DISP_X - 10.0f) {
			player.x = DISP_X - 10.0f;
		}
	}
	/* Aボタンが押されたら… */
	if(joy & TRG_A) {
		/* プレイヤーショットが空きならば… */
		if(pshot.y <= -10.0f) {
			/* プレイヤーと同じ座標にプレイヤーショットを初期化する */
			pshot.x = player.x;
			pshot.y = player.y;
		}
	}
}

/* プレイヤーを描画する */
void player_draw() {
	render_rectangle_fill(&render, player.x - 5, player.y - 5, 10, 10, 3);
}

/*--------------------------------------------------------------------------*/

void
app_main()
{
	for(;;) {
		schedule();
		surface_clear(&surface, 0);

		/* 敵、プレイヤー、プレイヤーショットを動かす */
		enemy_move();
		player_move();
		pshot_move();

		/* 敵、プレイヤー、プレイヤーショットを描画する */
		enemy_draw();
		player_draw();
		pshot_draw();

		/* 敵とプレイヤーショットが空きで無ければ… */
		if((enemy.s != 0.0f) && (pshot.y > -10.0f)) {
			/* 座標の差が10ドット未満ならば、ヒット */
			if((fabsf(enemy.x - pshot.x) < 10.0f) && (fabsf(enemy.y - pshot.y) < 10.0f)) {
				/* 敵とプレイヤーショットを空きにする */
				enemy.s = 0.0f;
				pshot.y = -10.0f;
			}
		}
	}
}
