/*---------------------------------------------------------------------------*
  Project:  NitroSDK - MB - demos - mb_child
  File:     main.c

  Copyright 2003-2006 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $NoKeywords: $
 *---------------------------------------------------------------------------*/
#include <nitro.h>

#include <nitro/os.h>
#include <nitro/wm.h>

#include "loader.h"
#include "child.h"
#include "main.h"
#include "state.h"
#include "peripheral.h"
#include "../../../include/gflib/strcode.h"
#include "../../../include/pm_version.h"
#include "gui.h"


//#define USE_SCREEN /* デバッグ用画面描画 */

// localize_spec_mark(LANG_ALL) yamamoto 2007/02/27
#if PM_LANG == LANG_ENGLISH
#include "msg_eng.h"
#elif PM_LANG == LANG_FRANCE
#include "msg_fra.h"
#elif PM_LANG == LANG_GERMANY
#include "msg_ger.h"
#elif PM_LANG == LANG_ITALY
#include "msg_ita.h"
#elif PM_LANG == LANG_SPAIN
#include "msg_spa.h"
#elif PM_LANG == LANG_KOREA
#include "msg_kor.h"
#endif

#ifdef USE_SCREEN

#include "screen.h"
#define PRINTF screenPrintf

#else /* USE_SCREEN */

#define PRINTF OS_TPrintf

#endif /* USE_SCREEN */




#define  FS_DEFAULT_DMA FS_DMA_NOT_USE

// [for pokemon_dp download] Cryptoライブラリ使用分をみる。
#define MAIN_HEAP_SIZE	(0x21000)
#define CODE_BUF_SIZE	(0x10)


// [for pokemon_dp download] 親srlのファイルシステムを参照するため。
// localize_spec_mark(LANG_ALL) yamamoto 2007/02/09
// 参照先をＤＰ本体にする。
#include "../../../src/gflib/hack.h"



// heap、FNT/FATのキャッシュ
static u32 fs_tablework[(1024*8)/4];
static u32 heapstatic[MAIN_HEAP_SIZE / sizeof(u32)] ATTRIBUTE_ALIGN(32);
static u32 code_buf[CODE_BUF_SIZE];
// localize_spec_mark(LANG_ALL) yamamoto 2007/02/09
// SDK内に入れてもらったので、それを使用。
extern u32 cardi_rom_header_addr;


// カード抜けチェック用
static BOOL PulledOutCallback(void);
static int _pulledout = 0;


// main, child で使うダウンロードプログラムのステート
u8 prog_state;  /* STATE_MB_??? */
u8 prog_state_previous = 0xFF;
u32 prog_state_count = 0;
int prog_no = 0;



static void
HEAP_Init()
{
	/*
	 * マルチブートダウンロード可能領域と重複しない領域に、
	 * ArenaおよびHeapをセットする．
	 */
	void *nstart;
	OSHeapHandle heapHandle;		// Heapハンドル

	nstart = OS_InitAlloc(OS_ARENA_MAIN, (void *)heapstatic,
	                      (void *)((u32)heapstatic + MAIN_HEAP_SIZE), 1);
	OS_SetArenaLo(OS_ARENA_MAIN, nstart);
	OS_SetArenaHi(OS_ARENA_MAIN, (void *)((u32)heapstatic + MAIN_HEAP_SIZE));

	heapHandle = OS_CreateHeap(OS_ARENA_MAIN,
	                           OS_GetMainArenaLo(), OS_GetMainArenaHi());

	if (heapHandle < 0) {
		OS_Panic("ARM9: Fail to create heap...\n");
	}

	OS_TPrintf("ARM9: create heap %08x - %08x\n",
	           OS_GetMainArenaLo(), OS_GetMainArenaHi());

	heapHandle = OS_SetCurrentHeap(OS_ARENA_MAIN, heapHandle);
}


static void
VBlankIntr(void)
{
	OS_SetIrqCheckFlag(OS_IE_V_BLANK);
}


static BOOL
PulledOutCallback(void)
{
	_pulledout = 1;
	return FALSE;
}


static void
list_dir()
{
	FSFile f;
	FS_InitFile(&f);

	if (!FS_FindDir(&f, "rom:/")) {
		OS_TPrintf("cannot open: /\n");
		return;
	}

	while (1) {
		FSDirEntry d;
		if (!FS_ReadDir(&f, &d))
			break;

		OS_TPrintf("%-20s ",d.name);

		if (d.is_directory) {
			OS_TPrintf("  <DIR>");
		} else {
			OS_TPrintf(" <FILE>");
		}

		OS_TPrintf("\n");
	}
}


static u16
GetPadTrigger(void)
{
	static u16 old = 0xffff;
	u16 cur = PAD_Read();
	u16 trigger = (u16)(cur & (cur ^ old));
	old = cur;
	return trigger;
}

static void
ShowLink()
{
	static int linklevel = -1;
	if (linklevel != WM_GetLinkLevel()) {
		linklevel = WM_GetLinkLevel();
		PRINTF("LinkLevel: %d\n", linklevel);
	}
	drawLevel(linklevel);
}


static void
vblank_delay(int n)
{
	int i;
	for (i=0;i<n;i++)
		OS_WaitVBlankIntr();
}




void
NitroMain()
{
	BOOL lidstate = FALSE;
	u16 lock_id;

	cardi_rom_header_addr = 0x027FF000;

// localize_spec_mark(LANG_ALL) yamamoto 2007/02/13
// ちらつき防止。
//	GX_SetMasterBrightness(-16);
//	GXS_SetMasterBrightness(-16);

	// OS初期化
	OS_InitPrintServer();
	OS_Init();


	OS_InitThread();
	OS_InitTick();
	OS_InitAlarm();
	RTC_Init();
	PXI_Init();


	// 設定
	/*
	 * プロテクションリージョン2を無効化
	 * (mb.h設定されている領域MB_BSSDESC_ADDRESSとの重複が起こっているため)
	 */
	OS_SetProtectionRegionParam(2,
	  HW_MAIN_MEM_SUB | HW_C6_PR_256KB | HW_C6_PR_DISABLE);


	/*
	 * マルチブート子機になる場合はここでローダーを初期化.
	 * ただし, 子機として動作するにはメモリ配置やコンポーネント等に
	 * 手を入れる必要があるため, 実質的には IPL のみ可能.
	 */
	LOADER_Init(NULL);



	// メモリ割り当て関連
	// MainRAM アリーナに対して メモリ割り当てシステム初期化
	HEAP_Init();


	// ファイルシステム初期化
	FS_Init(FS_DEFAULT_DMA);
// [for pokemon_dp download] ---
	/*
	 * 起動時に 1 回この関数を呼び出しておきます.
	 * この処理は, 初回に起動した本体プログラム自身からも
	 * 再起動した別プログラムからも呼び出す必要があります.
	 */
	NormalizeRomArchive();
	lock_id = (u16)OS_GetLockID();
// --- [for pokemon_dp download]

	{
		/*
		 * FNT/FATをキャッシュする
		 */
		u32 need_size = FS_GetTableSize();
		void *p_table = (void *)fs_tablework;

		if (sizeof(fs_tablework) > need_size) {
			(void)FS_LoadTable(p_table, need_size);
		}
	}


//	(void)OS_SetIrqFunction(OS_IE_V_BLANK, VBlankIntr);
//	(void)OS_EnableIrqMask(OS_IE_FIFO_RECV);
//	(void)OS_EnableIrq();
//	(void)OS_EnableInterrupts();



#ifdef USE_SCREEN
	// 画面・割り込み初期化
	screenInit();
#else
	guiInit();
	// 割込み設定
	(void)OS_DisableIrq();
	(void)OS_SetIrqFunction(OS_IE_V_BLANK, VBlankIntr);
	(void)OS_EnableIrqMask(OS_IE_V_BLANK);
	(void)OS_EnableIrqMask(OS_IE_FIFO_RECV);
	(void)GX_VBlankIntr(TRUE);
	(void)OS_EnableIrq();
	(void)OS_EnableInterrupts();
#endif

// localize_spec_mark(LANG_ALL) yamamoto 2007/02/13
// ちらつき防止。
	// LCD表示開始
	OS_WaitVBlankIntr();
	GX_DispOn();
	GXS_DispOn();



#if 0
	// 親のファイルシステムを読み込むテスト
	{
		OS_TPrintf("ROM filesystem access test\n");
		OS_TPrintf("----------------\n");
		list_dir();
		OS_TPrintf("----------------\n");
		OS_TPrintf("OK!\n\n");
	}
#endif

#if 0
	{
		CARDRomHeader * const arg_buffer = (CARDRomHeader *)0x027FF000/*HW_MAIN_MEM_SHARED*/;
		OS_TPrintf("banner = 0x%08x\n", arg_buffer->banner_offset);
	}
#endif


#if 0
	// フェードイン
	{
		int i;
		const int fadetime = 15;

		// (r, g, b) = (0, 16, 24) にフェードする。
		for (i = 0; i < fadetime; i++) {
			u16 g = 16 * i / fadetime;
			u16 b = 24 * i / fadetime;

			OS_WaitVBlankIntr();
			((u16 *)HW_BG_PLTT)[0] = b << 10 | g << 5;
		}
	}
#else
	{
		u16 g = 16;
		u16 b = 24;
		((u16 *)HW_BG_PLTT)[0] = (u16)(b << 10 | g << 5);
	}
	{
		int i;
		for (i=0; i<=16; i++) {
			OS_WaitVBlankIntr();
			GX_SetMasterBrightness(-16+i);
			GXS_SetMasterBrightness(-16+i);
		}
	}

#endif




	PRINTF("child start\n");
	ChildStart();

	PM_SetLEDPattern(PM_LED_PATTERN_WIRELESS);

	// OSi_ReferSymbol() が潰されるのでコードを保存しておく
	MI_CpuCopy8((void *)((u32)OSi_ReferSymbol & ~1), code_buf, CODE_BUF_SIZE);


	CARD_SetPulledOutCallback(PulledOutCallback);
	peripheral_save();	// LCD等を記憶する

	//================ メインループ
	while (1) {
		u16 pad_pressed;

		//---- Vブランク割込終了待ち
		OS_WaitVBlankIntr();


		/*
		 * 蓋が閉じられた場合の処理
		 */
		 {
			if (lidstate != IsLidClose()) {
				lidstate = IsLidClose();
				if (lidstate) {
					/* ON→OFF */
					OS_TPrintf("LCD OFF\n");

					peripheral_off();	// LCD等をoffにするよう指定

				} else {
					/* OFF→ON */
					OS_TPrintf("LCD ON\n");

					peripheral_restore();	// LCD等を通常の状態に戻すよう指定
				}
			}
#ifdef COMPAT_PBRJP_LIDCOMCLOSE
			if (lidstate) {
				// 通信中に蓋が閉じられた場合は強制的にエラーにする
				// (TIMEOUT or DOWNLOAD_ERROR になる)
				ChildForceError();
			}
#endif
		}
		peripheral_sync();	// 実際にON/OFFを行う



		pad_pressed = IsLidClose() ? (u16)0 : (u16)GetPadTrigger();


		/*
		 * prog_state_countはステート毎カウンタ。ステートが変わるたびに0になる。
		 * prog_state_count==0ならばステートが変わった直後
		 */
		if (prog_state_previous != prog_state) {
			OS_TPrintf("prog_state: %d -> %d\n", prog_state_previous, prog_state);
			prog_state_previous = prog_state;
			prog_state_count = 0;
		} else {
			prog_state_count++;
		}



		switch (prog_state) {
		case STATE_NONE:
		case STATE_MB_CHILDINIT:
			MI_CpuCopy8(code_buf,
			            (void *)((u32)OSi_ReferSymbol & ~1), CODE_BUF_SIZE);
			fontClear();
			winframeDraw(0);
			ChildInit();
			break;

		case STATE_MB_CHILDSCAN:
			{
				const u16 str[] = { DOWNLOADER_003 };
				u16 buf[sizeof(str) / sizeof(u16)];

				MI_CpuCopy16(str, buf, sizeof(str));
				// .、..、...と繰り返すようにしている。
				buf[sizeof(str) / sizeof(u16) + prog_state_count / 30 % 3 - 3] = EOM_;
				fontClear();
				fontDraw(1, 16, 152, buf);
			}
			ChildScan();
			ShowLink();

			if (pad_pressed & PAD_BUTTON_B) {
				// Bが押されたら強制終了
				ChildForceError();
				prog_state = STATE_QUERY_CONTINUE;
			}

			break;

		case STATE_MB_CHILD:
			{
				const u16 str[] = { DOWNLOADER_004 };
				u16 buf[sizeof(str) / sizeof(u16)];

				MI_CpuCopy16(str, buf, sizeof(str));
				// .、..、...と繰り返すようにしている。
				buf[sizeof(str) / sizeof(u16) + prog_state_count / 30 % 3 - 3] = EOM_;
				fontClear();
				fontDraw(1, 16, 152, buf);
			}
			ChildMain();
			ShowLink();
			break;

		case STATE_MB_TIMEOUT:
			// 親のサーチに時間がかかりすぎた場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_005 };

				PRINTF("SCAN TIMEOUT! (Press A)\n");
				drawLevel(-1);
				fontClear();
				winframeDraw(0);
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら画面送り
				prog_state = STATE_MB_TIMEOUT2;
			}

			break;

		case STATE_MB_TIMEOUT2:
			// 親のサーチに時間がかかりすぎた場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_006 };

				PRINTF("(Press A)\n");
				fontClear();
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら「再接続しますか？」へ
				prog_state = STATE_QUERY_CONTINUE;
			}

			break;

		case STATE_MB_CONNECT_ERROR:
			// 親に接続しようとしたが失敗した場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_007 };

				PRINTF("CONNECT ERROR! (Press A)\n");
				drawLevel(-1);
				fontClear();
				winframeDraw(0);
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら画面送り
				prog_state = STATE_MB_CONNECT_ERROR2;
			}

			break;

		case STATE_MB_CONNECT_ERROR2:
			// 親に接続しようとしたが失敗した場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_006 };

				PRINTF("(Press A)\n");
				fontClear();
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら「再接続しますか？」へ
				prog_state = STATE_QUERY_CONTINUE;
			}

			break;

		case STATE_MB_DOWNLOAD_ERROR:
			// 親に接続し、ダウンロード中にエラーが起きた場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_008 };

				PRINTF("DOWNLOAD ERROR! (Press A)\n");
				drawLevel(-1);
				fontClear();
				winframeDraw(0);
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら画面送り
				prog_state = STATE_MB_DOWNLOAD_ERROR2;
			}

			break;

		case STATE_MB_DOWNLOAD_ERROR2:
			// 親に接続し、ダウンロード中にエラーが起きた場合
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_006 };

				PRINTF("(Press A)\n");
				fontClear();
				fontDraw(1, 16, 152, str);
			}
			{
				const int count[] = {0, 1, 2, 1};
				drawCursor(count[prog_state_count / 8 % 4]);
			}

			if (pad_pressed & PAD_BUTTON_A) {
				// Aが押されたら「再接続しますか？」へ
				prog_state = STATE_QUERY_CONTINUE;
			}

			break;

		case STATE_QUERY_CONTINUE:
			// 再接続するかどうか？
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_009 };

				PRINTF("RETRY TO CONNECT?  YES(A)/NO(B)\n");
				drawLevel(-1);
				fontClear();
				winframeDraw(2);
				prog_no = 0;
				drawYesNo(0);
				fontDraw(1, 16, 152, str);
			}

#if 0
			// とりあえず「はい」「いいえ」を A と B で代用
			if (pad_pressed & PAD_BUTTON_A) { 
				// MB子機として再開
				// (prog_stateはChildRestart()の中で書き換えられるので注意)
				PRINTF("child restart\n");
				ChildRestart();
			} else if (pad_pressed & PAD_BUTTON_B) {
				prog_state = STATE_QUERY_POWEROFF;
			}
#endif
			if (pad_pressed & PAD_BUTTON_A) {
				if (prog_no == 0) {
					PRINTF("child restart\n");
					ChildRestart();
				} else {
					prog_state = STATE_QUERY_POWEROFF;
				}
			} else if (pad_pressed & PAD_BUTTON_B) {
				fontClear();
				winframeDraw(0);
				vblank_delay(5);
				prog_state = STATE_QUERY_POWEROFF;
			} else if (pad_pressed & PAD_KEY_DOWN) {
				if (prog_no == 0) {
					prog_no = 1;
					drawYesNo(1);
				}
			} else if (pad_pressed & PAD_KEY_UP) {
				if (prog_no != 0) {
					prog_no = 0;
					drawYesNo(0);
				}
			}
			break;

		case STATE_QUERY_POWEROFF:
			// 電源をOFFするかどうか？
			if (prog_state_count==0) {
				const u16 str[] = { DOWNLOADER_010 };

				PRINTF("POWER OFF?  YES(A)/NO(B)\n");
				drawLevel(-1);
				fontClear();
				winframeDraw(2);
				prog_no = 1;
				drawYesNo(1);
				fontDraw(1, 16, 152, str);
			}

#if 0
			// とりあえず「はい」「いいえ」を A と B で代用
			if (pad_pressed & PAD_BUTTON_A) { 
				prog_state = STATE_DO_POWEROFF;
			} else if (pad_pressed & PAD_BUTTON_B) {
				prog_state = STATE_QUERY_CONTINUE;
			}
#endif
			if (pad_pressed & PAD_BUTTON_A) {
				if (prog_no == 0) {
					prog_state = STATE_DO_POWEROFF;
				} else {
					prog_state = STATE_QUERY_CONTINUE;
				}
				fontClear();
				winframeDraw(0);
				vblank_delay(5);
			} else if (pad_pressed & PAD_BUTTON_B) {
				fontClear();
				winframeDraw(0);
				vblank_delay(5);
				prog_state = STATE_QUERY_CONTINUE;
			} else if (pad_pressed & PAD_KEY_DOWN) {
				if (prog_no == 0) {
					prog_no = 1;
					drawYesNo(1);
				}
			} else if (pad_pressed & PAD_KEY_UP) {
				if (prog_no != 0) {
					prog_no = 0;
					drawYesNo(0);
				}
			}
			break;

		case STATE_DO_POWEROFF:
			// 電源をOFFにする
			(void)PM_ForceToPowerOff();

			while (1)
				OS_Terminate();  /* 上のPowerOffが失敗した時のため… */

			break;

		case STATE_CARD_PULLEDOUT:
			// カードが抜かれた場合
			{
				const u16 str[] = { DOWNLOADER_011 };

				PRINTF("CARD PULLED OUT!\n");
				// localize_spec_mark(LANG_ALL) yamamoto 2007/02/10
				// このときはfaltして電波OFFになるので。
				drawLevel(-1);
				fontClear();
				winframeDraw(1);
				fontDraw(1, 16, 104, str);
			}

			if (IsLidClose()) {
				// 蓋が閉じられた状態でカードが抜かれたら、電源をOFFにする
				(void)PM_ForceToPowerOff();
			}

			// MB_End()が完了するまで60フレーム程度待つ
			{
				int i;
				for (i=0; i<60; i++) {
					OS_WaitVBlankIntr();
				}
			}

			/* エラー画面を出力後、halt */
			while (1)
				OS_Terminate();

			break;

		default:
			OS_TPanic("illegal state\n");
			break;
		}



		{
			CARD_LockRom(lock_id);
			CARD_CheckPulledOut();
			CARD_UnlockRom(lock_id);
		}

		// CARD抜けをチェック。
		if (_pulledout) {
			ChildForceError();
			prog_state = STATE_CARD_PULLEDOUT;
		}
	}

}



