/*
 *	clipufe.c
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2018 Naoyuki Sawa
 *
 *	UFE - P/ECE USB File System Emulation
 *	Copyright (C) 2005 Naoyuki Sawa
 *
 *	* Wed Jun 08 20:00:00 JST 2005 Naoyuki Sawa
 *	- 新規作成。
 *	* Wed Jun 26 19:31:00 JST 2005 Naoyuki Sawa
 *	- これまで単体ライブラリだったものを、CLiPライブラリに組み込みました。
 *	* Wed Dec 31 17:37:43 JST 2014 Naoyuki Sawa
 *	- cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。
 *	  この変更に伴い、ufe_setup()内のpceLCDDispStart()呼び出しは不要になり、削除しました。
 *	* Thu May 24 23:59:59 JST 2018 Naoyuki Sawa
 *	- PFFSとUFEの優先順位を指定出来るようにしました。
 *	- pceFileDelete()にも対応しました。
 */
#include "clip.h"

/* ※TODO:
 *   pceFile*()は、エラーの種類に応じて異なるエラーコードを返しますが、
 *   ufeFile*()は、全てのエラーについて-1を返しています。(2005/06/26現在)
 *   戻り値が0(成功)か0以外(失敗)かだけを判断しているアプリケーションならば問題ありませんが、
 *   エラーの種類を判断しているアプリケーションでは正しく動作しない可能性があります。
 *   いずれ、ufeFile*()のエラーコードをpceFile*()互換とするよう、修正する必要がありそうです。
 */

/*****************************************************************************
 *	ローカル変数
 *****************************************************************************/

static UFE ufe;

//{{2018/05/24追加:PFFSとUFEの優先順位を指定出来るようにしました。
static int order = UFEORDER_PFFS_UFE;	//これまでの動作との互換性のために、初期値はPFFS→UFEの順とする。
//}}2018/05/24追加:PFFSとUFEの優先順位を指定出来るようにしました。

static int (*pffsFileFindOpen)(FILEINFO* pfi);
static int (*pffsFileFindNext)(FILEINFO* pfi);
static int (*pffsFileFindClose)(FILEINFO* pfi);
static int (*pffsFileOpen)(FILEACC* pfa, const char* fname, int mode);
static int (*pffsFileReadSct)(FILEACC* pfa, void* ptr, int sct, int len);
static int (*pffsFileWriteSct)(FILEACC* pfa, const void* ptr, int sct, int len);
static int (*pffsFileClose)(FILEACC* pfa);
static int (*pffsFileCreate)(const char* fname, unsigned long size);
//{{2018/05/24追加:pceFileDelete()にも対応しました。
static int (*pffsFileDelete)(const char* fname);
//}}2018/05/24追加:pceFileDelete()にも対応しました。

/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/

/* ★重要!!★
 * * ufe_exec()を、割り込み禁止の状態から呼び出してはいけません!!
 *   ufe_exec()は、PCへのサービス要求を格納した後、サービス完了をポーリングで待ちます。
 *   PCからのファイルサービスは、USB通信によるPCからの書き込みによって行われます。
 *   USB割り込みが受け付けられない状態では、PCからのファイルサービスが行われず、
 *   ポーリングによるサービス完了待ちが永久ループになってしまいます!!
 * * 従って、UFEを利用しているときは、割り込み禁止状態からファイルAPIが利用できません。
 *   たとえば、割り込みサービスルーチンの中からファイルの読み込みを行ってはいけません。
 */
static int
ufe_exec(int ksno)
{
	/* カーネルサービス番号を格納します。
	 * これが、ファイルサーバへのサービス要求通知となります。
	 * 「パラメータ格納→カーネルサービス番号格納」の順番を厳守してください。
	 */
	ufe.ksno = ksno;

	/* ファイルサーバのサービスを完了を待ちます。
	 * ファイルサーバは、サービスを完了するとカーネルサービス番号をクリアします。
	 */
	while(ufe.ksno) {
		/* ファイルサーバとの接続が切断されてしまったら、エラー扱いとします。 */
		if(!(pceUSBCOMGetStat() & UCS_PCSTAT)) {
			return -1;
		}
	}
	return ufe.result;
}

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

static int
ufeFileFindOpen(FILEINFO* pfi)
{
	ufe.api.find_open.pfi = pfi;
	return ufe_exec(KSNO_FileFindOpen);
}

static int
ufeFileFindNext(FILEINFO* pfi)
{
	ufe.api.find_next.pfi = pfi;
	return ufe_exec(KSNO_FileFindNext);
}

static int
ufeFileFindClose(FILEINFO* pfi)
{
	ufe.api.find_close.pfi = pfi;
	return ufe_exec(KSNO_FileFindClose);
}

static int
ufeFileOpen(FILEACC* pfa, const char* fname, int mode)
{
	ufe.api.open.pfa = pfa;
	ufe.api.open.fname = fname;
	ufe.api.open.mode = mode;
	return ufe_exec(KSNO_FileOpen);
}

static int
ufeFileReadSct(FILEACC* pfa, void* ptr, int sct, int len)
{
	ufe.api.read_sct.pfa = pfa;
	ufe.api.read_sct.ptr = ptr;
	ufe.api.read_sct.sct = sct;
	ufe.api.read_sct.len = len;
	return ufe_exec(KSNO_FileReadSct);
}

static int
ufeFileWriteSct(FILEACC* pfa, const void* ptr, int sct, int len)
{
	ufe.api.write_sct.pfa = pfa;
	ufe.api.write_sct.ptr = ptr;
	ufe.api.write_sct.sct = sct;
	ufe.api.write_sct.len = len;
	return ufe_exec(KSNO_FileWriteSct);
}

static int
ufeFileClose(FILEACC* pfa)
{
	ufe.api.close.pfa = pfa;
	return ufe_exec(KSNO_FileClose);
}

static int
ufeFileCreate(const char* fname, unsigned long size)
{
	ufe.api.create.fname = fname;
	ufe.api.create.size = size;
	return ufe_exec(KSNO_FileCreate);
}

//{{2018/05/24追加:pceFileDelete()にも対応しました。
static int
ufeFileDelete(const char* fname)
{
	ufe.api.delete.fname = fname;
	return ufe_exec(KSNO_FileDelete);
}
//}}2018/05/24追加:pceFileDelete()にも対応しました。

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

static int
myFileFindOpen(FILEINFO* pfi)
{
	int retval;

//{{2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
//	retval = pffsFileFindOpen(pfi);
//	if(retval == 0) {
//		pfi->works[15] = FILEINFO_PFFS;
//	} else {
//		retval = ufeFileFindOpen(pfi);
//		if(retval == 0) {
//			pfi->works[15] = FILEINFO_UFE;
//		} else {
//			memset(pfi, 0, sizeof(FILEINFO));
//		}
//	}
//↓2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
	switch(order) {
	default:DIE();
	case UFEORDER_PFFS:
	case UFEORDER_PFFS_UFE:
		retval = pffsFileFindOpen(pfi);
		if(retval == 0) {
			pfi->works[15] = FILEINFO_PFFS;
		} else if(order == UFEORDER_PFFS_UFE) {
			retval = ufeFileFindOpen(pfi);
			if(retval == 0) {
				pfi->works[15] = FILEINFO_UFE;
			} else {
				memset(pfi, 0, sizeof(FILEINFO));
			}
		}
		break;
	case UFEORDER_UFE:
	case UFEORDER_UFE_PFFS:
		retval = ufeFileFindOpen(pfi);
		if(retval == 0) {
			pfi->works[15] = FILEINFO_UFE;
		} else if(order == UFEORDER_UFE_PFFS) {
			retval = pffsFileFindOpen(pfi);
			if(retval == 0) {
				pfi->works[15] = FILEINFO_PFFS;
			} else {
				memset(pfi, 0, sizeof(FILEINFO));
			}
		}
		break;
	}
//}}2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。

	return retval;
}

static int
myFileFindNext(FILEINFO* pfi)
{
	int retval = 0;

//{{2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
//	if(pfi->works[15] == FILEINFO_PFFS) {
//		retval = pffsFileFindNext(pfi);
//		if(!retval) {
//			pffsFileFindClose(pfi);
//			if(ufeFileFindOpen(pfi) == 0) {
//				pfi->works[15] = FILEINFO_UFE;
//			} else {
//				memset(pfi, 0, sizeof(FILEINFO));
//			}
//		}
//	}
//	if(pfi->works[15] == FILEINFO_UFE) {
//		retval = ufeFileFindNext(pfi);
//	}
//↓2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
	switch(order) {
	default:DIE();
	case UFEORDER_PFFS:
	case UFEORDER_PFFS_UFE:
		if(pfi->works[15] == FILEINFO_PFFS) {
			retval = pffsFileFindNext(pfi);
			if(!retval) {
				pffsFileFindClose(pfi);
				if(order == UFEORDER_PFFS_UFE) {
					if(ufeFileFindOpen(pfi) == 0) {
						pfi->works[15] = FILEINFO_UFE;
					} else {
						memset(pfi, 0, sizeof(FILEINFO));
					}
				}
			}
		}
		if(pfi->works[15] == FILEINFO_UFE) {
			retval = ufeFileFindNext(pfi);
		}
		break;
	case UFEORDER_UFE:
	case UFEORDER_UFE_PFFS:
		if(pfi->works[15] == FILEINFO_UFE) {
			retval = ufeFileFindOpen(pfi);
			if(!retval) {
				ufeFileFindClose(pfi);
				if(order == UFEORDER_UFE_PFFS) {
					if(pffsFileFindNext(pfi) == 0) {
						pfi->works[15] = FILEINFO_PFFS;
					} else {
						memset(pfi, 0, sizeof(FILEINFO));
					}
				}
			}
		}
		if(pfi->works[15] == FILEINFO_PFFS) {
			retval = pffsFileFindNext(pfi);
		}
		break;
	}
//}}2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。

	return retval;
}

static int
myFileFindClose(FILEINFO* pfi)
{
	int retval;

	switch(pfi->works[15]) {
	case FILEINFO_PFFS:
		retval = pffsFileFindClose(pfi);
		break;
	case FILEINFO_UFE:
		retval = ufeFileFindClose(pfi);
		break;
	default:
		retval = -1;
		break;
	}
	if(retval == 0) {
		memset(pfi, 0, sizeof(FILEINFO));
	}

	return retval;
}

static int
myFileOpen(FILEACC* pfa, const char* fname, int mode)
{
	int retval;

//{{2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
//	retval = pffsFileOpen(pfa, fname, mode);
//	if(retval != 0) {
//		retval = ufeFileOpen(pfa, fname, mode);
//		if(retval != 0) {
//			memset(pfa, 0, sizeof(FILEACC));
//		}
//	}
//↓2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
	switch(order) {
	default:DIE();
	case UFEORDER_PFFS:
	case UFEORDER_PFFS_UFE:
		retval = pffsFileOpen(pfa, fname, mode);
		if(retval != 0) {
			if(order == UFEORDER_PFFS_UFE) {
				retval = ufeFileOpen(pfa, fname, mode);
				if(retval != 0) {
					memset(pfa, 0, sizeof(FILEACC));
				}
			}
		}
		break;
	case UFEORDER_UFE:
	case UFEORDER_UFE_PFFS:
		retval = ufeFileOpen(pfa, fname, mode);
		if(retval != 0) {
			if(order == UFEORDER_UFE_PFFS) {
				retval = pffsFileOpen(pfa, fname, mode);
				if(retval != 0) {
					memset(pfa, 0, sizeof(FILEACC));
				}
			}
		}
		break;
	}
//}}2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。

	return retval;
}

static int
myFileReadSct(FILEACC* pfa, void* ptr, int sct, int len)
{
	int retval;

	switch(pfa->valid) {
	case FILEACC_PFFS:
		retval = pffsFileReadSct(pfa, ptr, sct, len);
		break;
	case FILEACC_UFE:
		retval = ufeFileReadSct(pfa, ptr, sct, len);
		break;
	default:
		retval = 0;
		break;
	}

	return retval;
}

static int
myFileWriteSct(FILEACC* pfa, const void* ptr, int sct, int len)
{
	int retval;

	switch(pfa->valid) {
	case FILEACC_PFFS:
		retval = pffsFileWriteSct(pfa, ptr, sct, len);
		break;
	case FILEACC_UFE:
		retval = ufeFileWriteSct(pfa, ptr, sct, len);
		break;
	default:
		retval = 0;
		break;
	}

	return retval;
}

static int
myFileClose(FILEACC* pfa)
{
	int retval;

	switch(pfa->valid) {
	case FILEACC_PFFS:
		retval = pffsFileClose(pfa);
		break;
	case FILEACC_UFE:
		retval = ufeFileClose(pfa);
		break;
	default:
		retval = -1;
		break;
	}
	if(retval == 0) {
		memset(pfa, 0, sizeof(FILEACC));
	}

	return retval;
}

static int
myFileCreate(const char* fname, unsigned long size)
{
	int retval;

//{{2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
//	retval = pffsFileCreate(fname, size);
//	if(retval != 0) {
//		retval = ufeFileCreate(fname, size);
//	}
//↓2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。
	switch(order) {
	default:DIE();
	case UFEORDER_PFFS:
	case UFEORDER_PFFS_UFE:
		retval = pffsFileCreate(fname, size);
		if(retval != 0) {
			if(order == UFEORDER_PFFS_UFE) {
				retval = ufeFileCreate(fname, size);
			}
		}
		break;
	case UFEORDER_UFE:
	case UFEORDER_UFE_PFFS:
		retval = ufeFileCreate(fname, size);
		if(retval != 0) {
			if(order == UFEORDER_UFE_PFFS) {
				retval = pffsFileCreate(fname, size);
			}
		}
		break;
	}
//}}2018/05/24変更:PFFSとUFEの優先順位を指定出来るようにしました。

	return retval;
}

//{{2018/05/24追加:pceFileDelete()にも対応しました。
static int
myFileDelete(const char* fname)
{
	int retval;

	switch(order) {
	default:DIE();
	case UFEORDER_PFFS:
	case UFEORDER_PFFS_UFE:
		retval = pffsFileDelete(fname);
		if(retval != 0) {
			if(order == UFEORDER_PFFS_UFE) {
				retval = ufeFileDelete(fname);
			}
		}
		break;
	case UFEORDER_UFE:
	case UFEORDER_UFE_PFFS:
		retval = ufeFileDelete(fname);
		if(retval != 0) {
			if(order == UFEORDER_UFE_PFFS) {
				retval = pffsFileDelete(fname);
			}
		}
		break;
	}

	return retval;
}
//}}2018/05/24追加:pceFileDelete()にも対応しました。

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

static void
ufe_hook()
{
	if(!pffsFileFindOpen) {
		pffsFileFindOpen  = pceVectorSetKs(KSNO_FileFindOpen , myFileFindOpen );
		pffsFileFindNext  = pceVectorSetKs(KSNO_FileFindNext , myFileFindNext );
		pffsFileFindClose = pceVectorSetKs(KSNO_FileFindClose, myFileFindClose);
		pffsFileOpen      = pceVectorSetKs(KSNO_FileOpen     , myFileOpen     );
		pffsFileReadSct   = pceVectorSetKs(KSNO_FileReadSct  , myFileReadSct  );
		pffsFileWriteSct  = pceVectorSetKs(KSNO_FileWriteSct , myFileWriteSct );
		pffsFileClose     = pceVectorSetKs(KSNO_FileClose    , myFileClose    );
		pffsFileCreate    = pceVectorSetKs(KSNO_FileCreate   , myFileCreate   );
//{{2018/05/24追加:pceFileDelete()にも対応しました。
		pffsFileDelete    = pceVectorSetKs(KSNO_FileDelete   , myFileDelete   );
//}}2018/05/24追加:pceFileDelete()にも対応しました。
	}
}

static void
ufe_unhook()
{
	if(pffsFileFindOpen) {
		pceVectorSetKs(KSNO_FileFindOpen , pffsFileFindOpen ); pffsFileFindOpen  = NULL;
		pceVectorSetKs(KSNO_FileFindNext , pffsFileFindNext ); pffsFileFindNext  = NULL;
		pceVectorSetKs(KSNO_FileFindClose, pffsFileFindClose); pffsFileFindClose = NULL;
		pceVectorSetKs(KSNO_FileOpen     , pffsFileOpen     ); pffsFileOpen      = NULL;
		pceVectorSetKs(KSNO_FileReadSct  , pffsFileReadSct  ); pffsFileReadSct   = NULL;
		pceVectorSetKs(KSNO_FileWriteSct , pffsFileWriteSct ); pffsFileWriteSct  = NULL;
		pceVectorSetKs(KSNO_FileClose    , pffsFileClose    ); pffsFileClose     = NULL;
		pceVectorSetKs(KSNO_FileCreate   , pffsFileCreate   ); pffsFileCreate    = NULL;
//{{2018/05/24追加:pceFileDelete()にも対応しました。
		pceVectorSetKs(KSNO_FileDelete   , pffsFileDelete   ); pffsFileDelete    = NULL;
//}}2018/05/24追加:pceFileDelete()にも対応しました。
	}
}

/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/

int
ufe_setup(int timeout)
{
	USBCOMINFO uci;
	unsigned char* old_vbuff;
	int ucs;
	int T0;
	int T1;

	/* まず、確実に切断しておきます。 */
	ufe_stop();

	/* USBCOMを初期化し、ファイルサーバからの接続を待ちます。
	 * USBCOMシグネチャの書式は、次のとおりです。
	 *	+0- 3:	"UFE1"
	 *	+4- 7:	UFE構造体のアドレス
	 *	+8-15:	未使用
	 */
	memset(&uci, 0, sizeof uci);
	memcpy(&uci.signature[0], "UFE1", 4);
	*(int*)&uci.signature[4] = (int)&ufe;
	pceUSBCOMSetup(&uci);

	/* 接続待ちメッセージを表示します。 */
	old_vbuff = pceLCDSetBuffer(_def_vbuff);
//不要	pceLCDDispStart();	//{{2014/12/31削除:cstart.cのpceAppInit00()でpceLCDDispStart()を呼び出し、液晶画面の表示を開始するようにしました。}}
	pceFontSetType(0x80);
	pceFontSetTxColor(3);
	pceFontSetBkColor(0);
	pceFontSetPos(0, (DISP_Y - 10 * 5) / 2);
	/*             ０１２３４５６７８９０１２ */
	pceFontPutStr(" +----------------------+\n"
	              " | ファイルサーバからの |\n"
	              " | 接続を待っています… |\n"
	              " |(Ａボタンでキャンセル)|\n"
	              " +----------------------+");
	pceLCDTrans();

	/* ファイルサーバからの接続か、またはキャンセルを待ちます。 */
	while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }
	T0 = pceTimerGetCount();
	while(!((ucs = pceUSBCOMGetStat()) & UCS_PCSTAT)) {
		if(pcePadGetDirect() & PAD_A) {
			break; /* キャンセル */
		}
		if(timeout >= 0) {
			T1 = timeout * 1000 - (pceTimerGetCount() - T0);
			if(T1 <= 0) {
				break; /* タイムアウト */
			}
			pceFontSetPos((DISP_X - 5 * 9) / 2, DISP_Y - 16);
			/*             012345678 */
			pceFontPrintf("あと%3d秒", (T1 + 999) / 1000);
			pceLCDTrans();
		}
	}
	while(pcePadGetDirect() & PAD_A) { /* 開放待ち*/ }

	/* 仮想画面を元に戻します。 */
	pceLCDSetBuffer(old_vbuff);

	/* 接続成功ならば、ファイルAPIをフックし、0を返します。 */
	if(ucs & UCS_PCSTAT) {
		ufe_hook();
		return 0;
	}

	/* 接続失敗ならば、-1を返します。
	 * 時間差で接続してしまっている可能性があるので、確実にUSBCOMを終了します。
	 */
	ufe_stop();
	return -1;
}

void
ufe_stop()
{
	USBCOMINFO uci;

	/* ファイルAPIがフックされていたら、解除します。 */
	ufe_unhook();

	/* pceUSBCOMStop()はシグネチャをクリアしないので、明示的にクリアします。 */
	memset(&uci, 0, sizeof uci);
	pceUSBCOMSetup(&uci);

	/* USBCOMを終了します。 */
	pceUSBCOMStop();
}

//{{2018/05/24追加:PFFSとUFEの優先順位を指定出来るようにしました。
void
ufe_order(int _order)
{
	order = _order;
}
