/*
 *	clipscnm.c
 *
 *	シーン管理
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Sun Nov 08 21:45:34 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Mon Nov 09 21:43:23 JST 2015 Naoyuki Sawa
 *	- 当モジュールで使用するレジストリデータを作成するためのツールとして、'/clip/tool/MkScnInf.awk,MkScnInf.bat'を作成しました。
 *	  詳細は、上記ツールの実装を参照して下さい。
 *	* Tue Aug 02 21:11:08 JST 2016 Naoyuki Sawa
 *	- cliptaps.cに自然遷移通知を追加した事に伴い、当モジュール(clipscnm.c)の「連動処理の例」のサンプルコードに、'case TapEvt_Next:'を追加しました。
 *	  詳細は、cliptaps.c の同日のコメントを参照して下さい。
 *	  サンプルコードの変更のみであり、当モジュール(clipscnm.c)の処理には変更有りません。
 */
#include "clip.h"
/*****************************************************************************
 *	ローカル関数宣言
 *****************************************************************************/
static void ScnMgrInf_Verify(const ST_ScnMgrInf* pInf);
static void ScnMgr_LyrSta(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/, int iScnNo/*1〜*/);
static void ScnMgr_LyrCon(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/);
static void ScnMgr_LyrEnd(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/);
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//シーン管理を動的に作成する。
ST_ScnMgr* ScnMgr_New(const ST_ScnMgrInf* pInf) {
	ST_ScnMgr* pScnMgr;
	//初期化情報を検査する。
	ScnMgrInf_Verify(pInf);
	//メモリを確保する。
#ifndef __RENESAS__
	pScnMgr = malloc(sizeof(ST_ScnMgr) + (sizeof(ST_ScnLyr) *  pInf->nLyr));
#else //__RENESAS__
	pScnMgr = malloc(sizeof(ST_ScnMgr) + (sizeof(ST_ScnLyr) * (pInf->nLyr - 1/*ST_ScnMgr.TBL_Lyr[0]の分*/*/)));	//Hewは要素数[0]の配列に対応していないので、要素数[1]として調整しました。
#endif//__RENESAS__
	if(!pScnMgr) { DIE(); }
	//シーン管理を静的に初期化する。
	ScnMgr_Init(pScnMgr, pInf);
	return pScnMgr;
}
/*---------------------------------------------------------------------------*/
//シーン管理を静的に初期化する。
void ScnMgr_Init(ST_ScnMgr* pScnMgr, const ST_ScnMgrInf* pInf) {
	//初期化情報を検査する。
	ScnMgrInf_Verify(pInf);
	//メモリをクリアする。
#ifndef __RENESAS__
	memset(pScnMgr, 0, sizeof(ST_ScnMgr) + (sizeof(ST_ScnLyr) *  pInf->nLyr));
#else //__RENESAS__
	memset(pScnMgr, 0, sizeof(ST_ScnMgr) + (sizeof(ST_ScnLyr) * (pInf->nLyr - 1/*ST_ScnMgr.TBL_Lyr[0]の分*/*/)));	//Hewは要素数[0]の配列に対応していないので、要素数[1]として調整しました。
#endif//__RENESAS__
	//初期化情報へのポインタを格納する。
	pScnMgr->pInf = pInf;
}
/*---------------------------------------------------------------------------*/
//レイヤ情報を取得する。
ST_ScnLyr* ScnMgr_GetLyr(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/) {
	const int nLyr = pScnMgr->pInf->nLyr;
	if((unsigned)iLyrNo >= (unsigned)nLyr) { DIE(); }	//バグ
	return &pScnMgr->TBL_Lyr[iLyrNo];
}
/*---------------------------------------------------------------------------*/
//このシーンの再生レイヤ番号を取得する。
// - 再生レイヤ有りならばiLyrNo=0〜nLyr-1，再生レイヤ無しならばiLyrNo=-1を返す。
int ScnMgr_GetLyrNo(ST_ScnMgr* pScnMgr, int iScnNo/*1〜*/) {
	const int nLyr = pScnMgr->pInf->nLyr;
	const uint8_t* pScnInf;
	int iLyrNo;
	//シーン情報を取得する。
	if(!iScnNo) { DIE(); }	//多分アプリケーションのバグ
	pScnInf = (*pScnMgr->pInf->fnGetInf)(iScnNo/*1〜*/);
	if(!pScnInf) { DIE(); }	//多分データバグ
	//シーン情報の1バイト目は、(このシーンの再生レイヤ番号+1)である。
	iLyrNo = *pScnInf - 1;		//再生レイヤ有りならばiLyrNo=0〜nLyr-1，再生レイヤ無しならばiLyrNo=-1になる。
	if(iLyrNo >= nLyr) { DIE(); }	//多分データバグ
	return iLyrNo;
}
/*---------------------------------------------------------------------------*/
//シーンを再生する。
void ScnMgr_SetScn(ST_ScnMgr* pScnMgr, int iScnNo/*1〜*/) {
	const int nLyr = pScnMgr->pInf->nLyr;
	const uint8_t* pScnInf;
	int iLyrNo1, iLyrNo2;
	ST_ScnLyr* pLyr;
	//シーン情報を取得する。
	if(!iScnNo) { DIE(); }	//多分アプリケーションのバグ
	pScnInf = (*pScnMgr->pInf->fnGetInf)(iScnNo/*1〜*/);
	if(!pScnInf) { DIE(); }	//多分データバグ
	//シーン情報の1バイト目は、(このシーンの再生レイヤ番号+1)である。
	iLyrNo1 = *pScnInf++ - 1;	//再生レイヤ有りならばiLyrNo=0〜nLyr-1，再生レイヤ無しならばiLyrNo=-1になる。
	if(iLyrNo1 >= nLyr) { DIE(); }	//多分データバグ
	//全てのレイヤについて…
	// - 再生レイヤの有無にかかわらず、このシーンが停止するレイヤのビットマスクは有効である。
	for(iLyrNo2 = 0; iLyrNo2 < nLyr; iLyrNo2++) {
		//このシーンが、このレイヤを停止するならば…
		// - シーン情報の2バイト目以降は、このシーンが停止するレイヤのビットマスクである。
		if(pScnInf[iLyrNo2 >> 3] & (1 << (iLyrNo2 & 7))) {
			//このレイヤのシーン停止処理を行う。
			// - このレイヤの'現在のシーン'が無ければダミー処理となる。
			ScnMgr_LyrEnd(pScnMgr, iLyrNo2);
		}
	}
	//再生レイヤ有りならば…
	if(iLyrNo1 != -1) {
		//再生レイヤのシーンが変化するならば…(上の処理で停止した場合も含む。)
		pLyr = ScnMgr_GetLyr(pScnMgr, iLyrNo1);
		if(pLyr->iScnNo_Now != iScnNo) {
			//このレイヤのシーン停止処理を行う。
			// - このレイヤの'現在のシーン'が無ければダミー処理となる。
			ScnMgr_LyrEnd(pScnMgr, iLyrNo1);
			//このレイヤのシーン開始処理を行う。
			ScnMgr_LyrSta(pScnMgr, iLyrNo1, iScnNo);
		//再生レイヤのシーンが変化しなければ…
		} else {
			//このレイヤのシーン継続処理を行う。
			ScnMgr_LyrCon(pScnMgr, iLyrNo1);
		}
	//再生レイヤ無しならば…
	} else {
		//再生レイヤの無いシーン開始通知を行う。【特殊処理】
		// - 再生レイヤが無くても、シーン開始通知は行う。
		//   この場所から、アプリケーション定義の(*fnScnSta)()を呼び出す場合のみ、(iLyrNo=-1)で呼び出す。
		// - 再生レイヤが無くても、シーン開始通知を行う目的は、再生レイヤの無いダミーシーンに対して、再生状態を管理しないワンショットのSE等を関連付けるためである。
		//   又は、それ以外の用途でも、アプリケーションが任意に利用しても構わない。
		//   アプリケーションがこの機能を利用しない場合は、アプリケーション定義の(*fnScnSta)()は、(iLyrNo=-1)の場合は何もせずに処理を返すように作成せよ。
		(*pScnMgr->pInf->fnScnSta)(iLyrNo1/*-1*/, iScnNo/*1〜*/);
	}
}
/*---------------------------------------------------------------------------*/
//レイヤを停止する。
void ScnMgr_DelLyr(ST_ScnMgr* pScnMgr, .../*iLyrMsk[31:0],iLyrMsk[63:32],…,iLyrMsk[(nLyr-1):(nLyr-1)&~31]*/) {
	const int nLyr = pScnMgr->pInf->nLyr;
	va_list ap;
	int iLyrNo;
	ST_ScnLyr* pLyr;
	unsigned iLyrMsk = 0;	//警告抑制
	va_start(ap, pScnMgr);
	//全てのレイヤについて…
	for(iLyrNo = 0; iLyrNo < nLyr; iLyrNo++) {
		//レイヤ0,32,64,...ならば…
		if(!(iLyrNo & 31)) {
			//レイヤマスクを取得する。
			iLyrMsk = va_arg(ap, unsigned);
		}
		//レイヤマスクが指定されていたら…
		if(iLyrMsk & 1) {
			//このレイヤのシーン停止処理を行う。
			// - このレイヤの'現在のシーン'が無ければダミー処理となる。
			ScnMgr_LyrEnd(pScnMgr, iLyrNo);
			//'前回のシーン'を確実にクリアする。
			// - ScnMgr_DelLyr()が呼び出されたという事は、アプリケーションが意図的にレイヤを停止したと見なせるので、'前回のシーン'(=ヒストリ)をクリアする。
			//   '前回のシーン'はあくまでも、同一レイヤに対して2個のシーンが例外的に重複して再生されるような特例ケースを記憶するためのものである。
			//   アプリケーションが必要に応じてScnMgr_GetLyrNo()->iScnNo_Now,iScnNo_Oldを取得して、アプリケーション毎の方法で判断するのが目的だ。
			//   尚、厳密には、同一レイヤに対して3個以上のシーンが例外的に重複して再生される可能性も有るが、当モジュールでは2個までしかサポートしていない。
			pLyr = ScnMgr_GetLyr(pScnMgr, iLyrNo);
			pLyr->iScnNo_Old = 0;
		}
		//レイヤマスクを次へ進める。
		iLyrMsk >>= 1;
	}
	va_end(ap);
}
/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/
//初期化情報を検査する。
static void ScnMgrInf_Verify(const ST_ScnMgrInf* pInf) {
	if(!pInf->nLyr ||		//レイヤ数
	   !pInf->fnGetInf ||		//シーン情報取得
	   !pInf->fnScnSta ||		//シーン開始通知
	   !pInf->fnScnCon ||		//シーン継続通知
	   !pInf->fnScnEnd) { DIE(); }	//シーン停止通知
}
/*---------------------------------------------------------------------------*/
//このレイヤのシーン開始処理を行う。
static void ScnMgr_LyrSta(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/, int iScnNo/*1〜*/) {
	ST_ScnLyr* pLyr = ScnMgr_GetLyr(pScnMgr, iLyrNo);
	//'現在のシーン'を格納する。
	if(!iScnNo) { DIE(); }	//多分当モジュールのバグ
	pLyr->iScnNo_Now = iScnNo;
	//シーン開始通知を行う。
	(*pScnMgr->pInf->fnScnSta)(iLyrNo/*0〜(nLyr-1)*/, iScnNo/*1〜*/);
}
/*---------------------------------------------------------------------------*/
//このレイヤのシーン継続処理を行う。
static void ScnMgr_LyrCon(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/) {
	ST_ScnLyr* pLyr = ScnMgr_GetLyr(pScnMgr, iLyrNo);
	//'現在のシーン'を取得する。
	int iScnNo = pLyr->iScnNo_Now;
	if(!iScnNo) { DIE(); }	//多分当モジュールのバグ
	//シーン継続通知を行う。
	(*pScnMgr->pInf->fnScnCon)(iLyrNo/*0〜(nLyr-1)*/, iScnNo/*1〜*/);
}
/*---------------------------------------------------------------------------*/
//このレイヤのシーン停止処理を行う。
static void ScnMgr_LyrEnd(ST_ScnMgr* pScnMgr, int iLyrNo/*0〜(nLyr-1)*/) {
	ST_ScnLyr* pLyr = ScnMgr_GetLyr(pScnMgr, iLyrNo);
	//このレイヤの'現在のシーン'を取得する。
	int iScnNo = pLyr->iScnNo_Now;
	//このレイヤの'現在のシーン'が有れば…
	if(iScnNo) {
		//'現在のシーン'を、'前回のシーン'として格納する。
		pLyr->iScnNo_Old = iScnNo;
		//'現在のシーン'をクリアする。
		pLyr->iScnNo_Now = 0;
		//シーン停止通知を行う。
		(*pScnMgr->pInf->fnScnEnd)(iLyrNo/*0〜(nLyr-1)*/, iScnNo/*1〜*/);
	//このレイヤの'現在のシーン'が無ければ…
	} else {
		//'前回のシーン'を変更せずに維持する。
		/** no job **/
	}
}

/****************************************************************************
 *	連動処理の例
 ****************************************************************************/
#if 0

□RegTbl.txt
│	ScnNo
│		1000
│			ScnInf		= {1,bit 1,bit 1,bit 0,bit 0}
│			TapSeqNo
│				0	= 2000
│				1	= 2001
│			end
│			LedSeqNo	= 3000
│			RudSeqNo
│				0	= 4000
│				1	= 4001
│			end
│		end
│	end
│	PhrNo
│		5000
│			LedSeqNo
│				0	= 3000
│			end
│			RudSeqNo
│				0	= 4000
│				1	= 4001
│			end
│		end
│	end

□Sample.c
│	#define RegKey_ScnInf   1 //┐
│	#define RegKey_ScnNo    2 //│
│	#define RegKey_TapSeqNo 3 //│実際はdRegTblC.exeがRegTbl.hに出力する。
│	#define RegKey_LedSeqNo 4 //│
│	#define RegKey_RudSeqNo 5 //│
│	#define RegKey_PhrNo    6 //┘
│	#define ScnLyr_Max      3 //アプリケーション定義
│	extern ST_TapSeq* pTapSeq;//TAPシーケンサ
│	extern ST_LedSeq* pLedSeq;//LEDシーケンサ
│	extern const uint8_t TBL_RegTbl[];//RegTbl.bin
│	extern const int   TBL_TapSeqNo[];//TapDef.bin
│	extern const int   TBL_LedSeqNo[];//LedDef.bin
│	extern const int   TBL_RudSeqNo[];//RudDef.bin
│	void onTapEvt() {//アプリケーションの周期処理,及び,TapSeqCh_PlayNo()/TapSeqCh_Stop()を呼び出した直後にもonTapEvt()を呼び出せ。
│		ST_TapEvt* pEvt;
│		while((pEvt = TapSeq_EvtGet(pTapSeq))) {
│			switch(pEvt->iEvt) {
│			default:DIE();
│			case TapEvt_Play:
│			case TapEvt_Loop:
│			case TapEvt_Next:	//{{2016/08/02追加:自然遷移通知を追加しました。}}
│				{
│					const void* pPhrKey = REG_open_key_l(TBL_RegTbl, RegKey_PhrNo, pEvt->iPhr, -1);
│					//フレーズ⇒LED連動
│					{
│						const void* pKey = REG_open_key(pPhrKey, RegKey_LedSeqNo);
│						int index = 0, iLedSeqCh, iLedSeqNo;
│						while((iLedSeqNo = REG_get_nth_value(pKey, index++, &iLedSeqCh)) != -1) {
│							LedSeqCh_PlayNo(pLedSeq, iLedSeqCh, TBL_LedSeqNo, iLedSeqNo);
│						}
│					}
│					//フレーズ⇒モーター連動
│					{
│						const void* pKey = REG_open_key(pPhrKey, RegKey_RudSeqNo);
│						int index = 0, iRudSeqCh, iRudSeqNo;
│						while((iRudSeqNo = REG_get_nth_value(pKey, index++, &iRudSeqCh)) != -1) {
│							RudSeqCh_PlayNo(iRudSeqCh, TBL_RudSeqNo, iRudSeqNo);
│						}
│					}
│				}
│				break;
│			case TapEvt_Stop:
│			case TapEvt_End:
│				{
│					const void* pPhrKey = REG_open_key_l(TBL_RegTbl, RegKey_PhrNo, pEvt->iPhr, -1);
│					//フレーズ⇒LED連動
│					{
│						const void* pKey = REG_open_key(pPhrKey, RegKey_LedSeqNo);
│						int index = 0, iLedSeqCh, iLedSeqNo;
│						while((iLedSeqNo = REG_get_nth_value(pKey, index++, &iLedSeqCh)) != -1) {
│							LedSeqCh_Stop(pLedSeq, iLedSeqCh);
│						}
│					}
│					//フレーズ⇒モーター連動
│					{
│						const void* pKey = REG_open_key(pPhrKey, RegKey_RudSeqNo);
│						int index = 0, iRudSeqCh, iRudSeqNo;
│						while((iRudSeqNo = REG_get_nth_value(pKey, index++, &iRudSeqCh)) != -1) {
│							RudSeqCh_Stop(iRudSeqCh);
│						}
│					}
│				}
│				break;
│			case TapEvt_Ctrl:
│				/** no job **/
│				break;
│			}
│		}
│	}
│	static const void* fnGetInf(int iScnNo/*1〜*/) {
│		const void* pScnInf = REG_get_blob_l(TBL_RegTbl, RegKey_ScnNo, iScnNo, RegKey_ScnInf, -1);
│		if(REG_get_blob_size(pScnInf) != (((ScnLyr_Max+1)+15)>>3)) { DIE(); }
│		return pScnInf;
│	}
│	static void fnScnSta(int iLyrNo/*-1〜(nLyr-1)*/, int iScnNo/*1〜*/) {
│		const void* pScnKey = REG_open_key_l(TBL_RegTbl, RegKey_ScnNo, iScnNo, -1);
│		//シーン⇒サウンド連動
│		{
│			const void* pKey = REG_open_key(pScnKey, RegKey_TapSeqNo);
│			int index = 0, iTapSeqCh, iTapSeqNo;
│			while((iTapSeqNo = REG_get_nth_value(pKey, index++, &iTapSeqCh)) != -1) {
│				TapSeqCh_PlayNo(pTapSeq, iTapSeqCh, TBL_TapSeqNo, iTapSeqNo);
│				onTapEvt();//割り込み外でのサウンド開始停止要求とサウンドイベント処理順序を同じにするために即反映する。ここ以外からTapSeqCh_PlayNo()/TapSeqCh_Stop()を呼び出した直後にもonTapEvt()を呼び出せ。
│			}
│		}
│		//シーン⇒LED連動
│		{
│			int iLedSeqNo = REG_get_value(pScnKey, RegKey_LedSeqNo);
│			if(iLedSeqNo != -1) {
│				int iLedSeqCh = iLyrNo;
│				LedSeqCh_PlayNo(pLedSeq, iLedSeqCh, TBL_LedSeqNo, iLedSeqNo);
│			}
│		}
│		//シーン⇒モーター連動
│		{
│			const void* pKey = REG_open_key(pScnKey, RegKey_RudSeqNo);
│			int index = 0, iRudSeqCh, iRudSeqNo;
│			while((iRudSeqNo = REG_get_nth_value(pKey, index++, &iRudSeqCh)) != -1) {
│				RudSeqCh_PlayNo(iRudSeqCh, TBL_RudSeqNo, iRudSeqNo);
│			}
│		}
│	}
│	static void fnScnCon(int iLyrNo/* 0〜(nLyr-1)*/, int iScnNo/*1〜*/) {
│	    //	const void* pScnKey = REG_open_key_l(TBL_RegTbl, RegKey_ScnNo, iScnNo, -1);
│		//シーン⇒サウンド連動
│		{
│			/** no job **/
│		}
│		//シーン⇒LED連動
│		{
│			/** no job **/
│		}
│		//シーン⇒モーター連動
│		{
│			/** no job **/
│		}
│	}
│	static void fnScnEnd(int iLyrNo/* 0〜(nLyr-1)*/, int iScnNo/*1〜*/) {
│		const void* pScnKey = REG_open_key_l(TBL_RegTbl, RegKey_ScnNo, iScnNo, -1);
│		//シーン⇒サウンド連動
│		{
│			const void* pKey = REG_open_key(pScnKey, RegKey_TapSeqNo);
│			int index = 0, iTapSeqCh, iTapSeqNo;
│			while((iTapSeqNo = REG_get_nth_value(pKey, index++, &iTapSeqCh)) != -1) {
│				TapSeqCh_Stop(pTapSeq, iTapSeqCh);
│				onTapEvt();//割り込み外でのサウンド開始停止要求とサウンドイベント処理順序を同じにするために即反映する。ここ以外からTapSeqCh_PlayNo()/TapSeqCh_Stop()を呼び出した直後にもonTapEvt()を呼び出せ。
│			}
│		}
│		//シーン⇒LED連動
│		{
│			int iLedSeqNo = REG_get_value(pScnKey, RegKey_LedSeqNo);
│			if(iLedSeqNo != -1) {
│				int iLedSeqCh = iLyrNo;
│				LedSeqCh_Stop(pLedSeq, iLedSeqCh);
│			}
│		}
│		//シーン⇒モーター連動
│		{
│			const void* pKey = REG_open_key(pScnKey, RegKey_RudSeqNo);
│			int index = 0, iRudSeqCh, iRudSeqNo;
│			while((iRudSeqNo = REG_get_nth_value(pKey, index++, &iRudSeqCh)) != -1) {
│				RudSeqCh_Stop(iRudSeqCh);
│			}
│		}
│	}
│	static const ST_ScnMgrInf stScnMgrInf = { (ScnLyr_Max+1), fnGetInf, fnScnSta, fnScnCon, fnScnEnd };

#endif
