/*
 *	clipfsm.c
 *
 *	有限状態機械
 *
 *	* Sat Jun 20 18:44:16 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sun Jun 21 22:22:12 JST 2015 Naoyuki Sawa
 *	- 当モジュールで使用するデータを生成するための、有限状態機械マップコンパイラを作りました。
 *	  /clip/tool/dFsmMapC/を参照してください。
 *	* Mon Jun 29 21:48:11 JST 2015 Naoyuki Sawa
 *	- 遷移の名前が同じで、ガード条件が異なる、遷移セットを定義出来るようにしました。
 *	  優先順位は、「SMC - The State Machine Compiler」(http://smc.sourceforge.net/)を参考にさせて頂きました。
 *	    1-1-1. 現在の状態の、指定された遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    1-1-2. 現在の状態の、指定された遷移の、ガードが無い遷移
 *	    1-2-1. 状態マップの、指定された遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    1-2-2. 状態マップの、指定された遷移の、ガードが無い遷移
 *	    2-1-1. 現在の状態の、デフォルト遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    2-1-2. 現在の状態の、デフォルト遷移の、ガードが無い遷移
 *	    2-2-1. 状態マップの、デフォルト遷移の、ガードが有る遷移で、ガード条件が成立した遷移	複数有る場合は順不同。現在の実装ではガード名の順としているが、その挙動に依存してはいけない。
 *	    2-2-2. 状態マップの、デフォルト遷移の、ガードが無い遷移
 *	  上記の順に検索して、実行できる遷移がなかった場合は、エラー停止します。
 *	  詳細は、/clip/tool/dFsmMapC/Sample/Sample.txt の例を参照してください。
 *	* Wed Jul 01 21:16:16 JST 2015 Naoyuki Sawa
 *	- FsmObj_CancelTrans()を追加しました。
 *	  FsmObj_ApplyTrans()から呼び出された、Act関数の中から、遷移をキャンセル出来るようになりました。
 *	- 原則として遷移をキャンセルするのは、Guard関数で判断すべきです。
 *	  しかし実際には、Act関数まで処理が進んでから、遷移をキャンセルしたいケースも有ります。(エラー検出や例外処理など)
 *	  そのようなケースに対応するために、FsmObj_CancelTrans()を追加する事にしました。
 *	- Act関数がFsmObj_CancelTrans()を使って遷移をキャンセルする事と、Guard関数が遷移を受け入れない事は、等価ではありません。
 *	  Act関数がFsmObj_CancelTrans()を使って遷移をキャンセルした場合、上記の優先順位に従った次の遷移を検索する動作は行われない事に注意してください。
 *	- FsmObj_CancelTrans()を呼び出せるのは、Act関数の中からだけです。
 *	  Enter関数,Leave関数,Guard関数の中から、呼び出してはいけません。
 *	- Act関数の中で、遷移をキャンセルすると共に、明示的に状態をセット(又は,プッシュ,ポップ)を行う事も可能です。
 *	  <例>
 *	  │Map1
 *	  │  State1
 *	  │    Trans1 State2 {}
 *	  │  end
 *	  │  State2
 *	  │    ...
 *	  │  end
 *	  │  StateE
 *	  │    ...
 *	  │  end
 *	  │end
 *	  ├────────────────────────────────────────
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    FsmObj_SetState(pFsmObj, FsmState_StateE);┬明示的に状態'StateE'をセットし、
 *	  │    FsmObj_CancelTrans(); ──────────┘State2への遷移をキャンセルする。
 *	  │} }
 *	* Fri Jul 03 21:00:32 JST 2015 Naoyuki Sawa
 *	- FsmObj_CancelTrans()を削除しました。
 *	  今後は構造化例外処理(SEH)を使用してください。
 *	  構造化例外処理(SEH)を使用する方が、より柔軟に制御出来ます。
 *	  <例1>
 *	  │//Act関数側で、遷移キャンセル後のエラー状態も設定する例
 *	  │void Sample1() {
 *	  │  SEH_try {
 *	  │    FsmObj_ApplyTrans(&stFsmObj, FsmTrans_Trans1, NULL);
 *	  │  } SEH_catch(&stFsmObj) {
 *	  │  } SEH_end
 *	  │}
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    FsmObj_SetState(pFsmObj, FsmState_StateE);┬明示的に状態'StateE'をセットし、
 *	  │    SEH_throw(pFsmObj); ───────────┘State2への遷移をキャンセルする。
 *	  │  }
 *	  │}
 *	  <例2>
 *	  │//呼び出し側で、遷移キャンセル後のエラー状態を設定する例
 *	  │void Sample2() {
 *	  │  SEH_try {
 *	  │    FsmObj_ApplyTrans(&stFsmObj, FsmTrans_Trans1, NULL);
 *	  │  } SEH_catch(&stFsmObj) {───────────┬遷移'Trans1'がキャンセルされたら…
 *	  │    FsmObj_SetState(&stFsmObj, FsmState_StateE);┘明示的に状態'StateE'をセットする。
 *	  │  } SEH_end
 *	  │}
 *	  │void FsmFunc_Map1_State1_act(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
 *	  │  if(例外判定) {
 *	  │    SEH_throw(pFsmObj); ─────────────遷移をキャンセルする。
 *	  │  }
 *	  │}
 */
#include "clip.h"
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
//{{2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
//static jmp_buf* FsmObj_env;	//Act関数が遷移をキャンセルする場合に使用する環境(ネスト可能)
//}}2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
//アプリケーション定義の関数が有れば呼び出す。
//アプリケーション定義の関数が無ければ、(-1)を返す。
static int FsmObj_CallFunc(ST_FsmObj* pFsmObj, int iFunc, int iTrans, void* pParam) {
	//指定された関数番号が(-1)ならば、何もせずに(-1)を返す。
	if(iFunc == -1) { return -1; }
	//指定された関数番号が(0)以下,又は,関数配列の要素数以上ならば、エラー停止する。
	// - アプリケーション定義の関数配列の先頭に、関数配列の要素数が格納されている。
	if((iFunc <= 0) || (iFunc >= (int)TBL_FsmFunc[0])) { DIE(); }
	//アプリケーション定義の関数を呼び出す。
	// - 常にFsmGuardFunc()形式として呼び出す。他の形式だった場合、余分な引数や戻り値は無視される。
	return ((int (*)(ST_FsmObj*, int, void*))TBL_FsmFunc[iFunc])(pFsmObj, iTrans, pParam);
}
/*--------------------------------------------------------------------------*/
//状態マップのレジストリキーを取得する。
static const void* FsmObj_GetMapKey(ST_FsmObj* pFsmObj) {
	return pFsmObj->pMapKey;
}
/*--------------------------------------------------------------------------*/
//現在の状態のレジストリキーを取得する。
static const void* FsmObj_GetStateKey(ST_FsmObj* pFsmObj) {
	return REG_open_key_l(
		FsmObj_GetMapKey(pFsmObj),		//状態マップのレジストリキー
		FsmObj_GetState(pFsmObj) | 0x8000,	//現在の状態の状態番号		状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
		-1);
}
/*--------------------------------------------------------------------------*/
//現在の状態のEnter関数を呼び出す。
//現在の状態にEnter関数が無ければ、状態マップのEnter関数を呼び出す。
//状態マップにもEnter関数が無ければ、何もしない。
static void FsmObj_CallStateEnter(ST_FsmObj* pFsmObj) {
	int iFunc = REG_get_value_l(
		FsmObj_GetStateKey(pFsmObj),		//現在の状態のレジストリキー
		Fsm_Enter,				//Enter関数番号の値の名前
		-1);
	if(iFunc == -1) {				//現在の状態にEnter関数が無ければ…
		iFunc = REG_get_value_l(
			FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
			Fsm_Enter,			//Enter関数番号の値の名前
			-1);
	}
	FsmObj_CallFunc(pFsmObj, iFunc, -1, NULL);	//Enter関数が無ければ、ダミー処理となる。
}
/*--------------------------------------------------------------------------*/
//現在の状態のLeave関数を呼び出す。
//現在の状態にLeave関数が無ければ、状態マップのLeave関数を呼び出す。
//状態マップにもLeave関数が無ければ、何もしない。
static void FsmObj_CallStateLeave(ST_FsmObj* pFsmObj) {
	int iFunc = REG_get_value_l(
		FsmObj_GetStateKey(pFsmObj),		//現在の状態のレジストリキー
		Fsm_Leave,				//Leave関数番号の値の名前
		-1);
	if(iFunc == -1) {				//現在の状態にLeave関数が無ければ…
		iFunc = REG_get_value_l(
			FsmObj_GetMapKey(pFsmObj),	//状態マップのレジストリキー
			Fsm_Leave,			//Leave関数番号の値の名前
			-1);
	}
	FsmObj_CallFunc(pFsmObj, iFunc, -1, NULL);	//Leave関数が無ければ、ダミー処理となる。
}
/*--------------------------------------------------------------------------*/
//状態が指定されていたら、状態に属する指定された遷移のレジストリキーを取得する。
//状態が指定されなければ、マップに属する指定された遷移のレジストリキーを取得する。
static const void* FsmObj_GetTransKey(ST_FsmObj* pFsmObj, int iState, int iTrans) {
	const void* pKey = REG_open_key_l(
		FsmObj_GetMapKey(pFsmObj),		//状態マップのレジストリキー
		iState | 0x8000,			//(指定された状態の状態番号|0x8000),又は,-1		状態のキーの名前は最上位ビットをセットする。状態マップのキーの直下に状態と遷移のキーが並存するので、状態と遷移のキーの名前が重複する事を避けるためです。
		-1);
	return REG_open_key_l(
		pKey,					//状態マップのレジストリキー,又は,指定された状態のレジストリキー
		iTrans,					//指定された遷移のキーの名前
		-1);
}
/****************************************************************************
 *	グローバル関数
 ****************************************************************************/
//FSMオブジェクトを初期化する。
void FsmObj_Init(ST_FsmObj* pFsmObj, const void* pFsmKey, int iMap, int iState) {
	//構造体をクリアする。
	memset(pFsmObj, 0, sizeof(ST_FsmObj));
	//状態マップのレジストリキーを格納する。
	pFsmObj->pMapKey = REG_open_key_l(pFsmKey, iMap, -1);
	//状態マップのレジストリキーが無ければ、エラー停止する。
	if(!pFsmObj->pMapKey) { DIE(); }
	//初期状態を指定された状態とする。
	pFsmObj->iStack = -1;			//┐
	FsmObj_PushState(pFsmObj, iState);	//┴'TBL_Stack[(iStack=0)]=iState'とし、Enter関数が有れば呼び出す。
}
/*--------------------------------------------------------------------------*/
//現在の状態の状態番号を取得する。
int FsmObj_GetState(ST_FsmObj* pFsmObj) {
	if((unsigned)pFsmObj->iStack >= FsmObj_StackSize) { DIE(); }
	return pFsmObj->TBL_Stack[pFsmObj->iStack];
}
/*--------------------------------------------------------------------------*/
//状態をセットする。
void FsmObj_SetState(ST_FsmObj* pFsmObj, int iState) {
	//Leave関数が有れば呼び出す。
	FsmObj_CallStateLeave(pFsmObj);
	//指定された状態番号をセットする。
	if((unsigned)pFsmObj->iStack >= FsmObj_StackSize) { DIE(); }
	pFsmObj->TBL_Stack[pFsmObj->iStack] = iState;
	//指定された状態のレジストリキーが存在しなければ、エラー停止する。
	if(!FsmObj_GetStateKey(pFsmObj)) { DIE(); }
	//Enter関数が有れば呼び出す。
	FsmObj_CallStateEnter(pFsmObj);
}
/*--------------------------------------------------------------------------*/
//状態をプッシュする。
void FsmObj_PushState(ST_FsmObj* pFsmObj, int iState) {
	//指定された状態番号をプッシュする。
	if((unsigned)(++pFsmObj->iStack) >= FsmObj_StackSize) { DIE(); }
	pFsmObj->TBL_Stack[pFsmObj->iStack] = iState;
	//指定された状態のレジストリキーが存在しなければ、エラー停止する。
	if(!FsmObj_GetStateKey(pFsmObj)) { DIE(); }
	//Enter関数が有れば呼び出す。
	FsmObj_CallStateEnter(pFsmObj);
}
/*--------------------------------------------------------------------------*/
//状態をポップする。
void FsmObj_PopState(ST_FsmObj* pFsmObj) {
	//Leave関数が有れば呼び出す。
	FsmObj_CallStateLeave(pFsmObj);
	//状態番号をポップする。
	if((unsigned)(--pFsmObj->iStack) >= FsmObj_StackSize) { DIE(); }
}
/*--------------------------------------------------------------------------*/
//遷移を適用する。
void FsmObj_ApplyTrans(ST_FsmObj* pFsmObj, int iTrans, void* pParam) {
	//指定された遷移(iTrans),デフォルト遷移(0)の順に検索する。
	int iTrDef = iTrans;
	for(;;) {
		//現在の状態に属する遷移,マップに属する遷移(-1)の順に検索する。
		int iStMap = FsmObj_GetState(pFsmObj);
		for(;;) {
			//遷移のキーを取得する。
			const void* pKey = FsmObj_GetTransKey(pFsmObj, iStMap, iTrDef);
			int i = 0;
			for(;;) {
				//指定された遷移のGuard関数,Act関数,遷移先状態番号を取得する。
				int iFnGrd = REG_get_value_l(pKey, i++, -1);	//i=(3N+0)
				int iFnAct = REG_get_value_l(pKey, i++, -1);	//i=(3N+1)
				int iStNex = REG_get_value_l(pKey, i++, -1);	//i=(3N+2)
				//指定された遷移のAct関数,又は,遷移先状態番号が有れば…
				if((iFnAct != -1) || (iStNex != -1)) {
					//指定された遷移のGuard関数が(0)以外を返したら、遷移を実行する。
					// - Guard関数が無ければFsmObj_CallFunc()が(-1)を返し、遷移を実行する。
					if(FsmObj_CallFunc(pFsmObj, iFnGrd, iTrans, pParam)) {		//デフォルト遷移を使用する場合も、関数引数には'指定された遷移番号'を引き渡す。
//{{2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
//						//Act関数が遷移をキャンセルする場合に使用する環境を追加する。(ネスト可能)
//						jmp_buf env, * const env_save = FsmObj_env;
//						//Act関数が遷移をキャンセルしたら、環境を復元して、遷移を中止する。
//						if(setjmp(*(FsmObj_env = &env))) { FsmObj_env = env_save; return; }
//}}2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
						//指定された遷移のAct関数が有れば呼び出す。
						// - Act関数が現在の状態を変える可能性が有る。
						FsmObj_CallFunc(pFsmObj, iFnAct, iTrans, pParam);	//デフォルト遷移を使用する場合も、関数引数には'指定された遷移番号'を引き渡す。
//{{2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
//						//Act関数が遷移をキャンセルする場合に使用する環境を復元する。(ネスト可能)
//						FsmObj_env = env_save;
//}}2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
						//遷移先状態番号が無ければ、ここまで。
						if(iStNex == -1) { return; }
						//遷移先状態番号が(0xFFFF)ならば、ポップ遷移である。
						else if(iStNex == 0xFFFF) { FsmObj_PopState(pFsmObj); return; }
						//遷移先状態番号が(0x8000〜0xFFFE)ならば、下位15ビットを状態番号と見なす、プッシュ遷移である。
						else if(iStNex & 0x8000) { FsmObj_PushState(pFsmObj, iStNex & 0x7FFF); return; }
						//遷移先状態番号が(0x0000〜0x7FFF)ならば、通常遷移である。
						else { FsmObj_SetState(pFsmObj, iStNex); return; }
					}
				}
				//指定された遷移のGuard関数が無ければ、終端である。
				// - Act関数,遷移先状態番号の有無に関らず、Guard関数が無ければ終端である。
				if(iFnGrd == -1) { break; }
			}
			//マップに属する遷移を検索したら、このループを抜ける。
			if(iStMap == -1/*Map*/) { break; }
			//マップに属する遷移を検索する。
			   iStMap  = -1/*Map*/;
		}
		//デフォルト遷移を検索したら、このループを抜ける。
		if(iTrDef == 0/*default*/) { break; }
		//デフォルト遷移を検索する。
		   iTrDef  = 0/*default*/;
	}
	DIE();	//実行可能な遷移が一つも無ければ、エラー停止する。
}
/*--------------------------------------------------------------------------*/
//{{2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
////Act関数の中から、遷移をキャンセルする。
//void FsmObj_CancelTrans() {
//	if(!FsmObj_env) { DIE(); }	//バグ
//	longjmp((*FsmObj_env), 1);
//}
//}}2015/07/03削除:FsmObj_CancelTrans()を削除しました。今後は構造化例外処理(SEH)を使用してください。
