/*
 *	clipdirs.c
 *
 *	演出シーケンサ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015 Naoyuki Sawa
 *
 *	* Sun Feb 08 19:01:54 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Thu Apr 23 21:42:53 JST 2015 Naoyuki Sawa
 *	- DirSeq_Stop(),DirSeqCh_Stop()を追加しました。
 *	  演出テーブルをクリアせずに、現在実行中の演出シーケンスを停止したい場合に使用します。
 *	  例えば、'あるトリガ'から??秒後に'ある表示'を行うような演出シーケンスを開始した後に、何らかの条件でその予約を取り消す場合等です。
 *	* Tue May 05 17:15:43 JST 2015 Naoyuki Sawa
 *	- DirSeqCh_SetTblNo()を追加しました。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
static ST_DirSeq stDirSeq;
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static void _DirSeq_SetActBuf(void (*fn)(), int arg1, int arg2);
static void _DirSeq_AddActBuf(int ActNo);
static void _DirSeq_GetActBuf(int bOverFlow);
static void _DirSeqCh_Start(int iCh, const uint8_t* p);
static void _DirSeq_Exec();
static void _DirSeqCh_Exec1(int iCh, int bStop);
static void _DirSeqCh_Exec2(int iCh, int bUnlock);
static void _DirSeq_ClrTbl();
static void _DirSeqCh_SetTbl(int iCh, const uint8_t* pDirTbl/*NULL=クリア*/);
static void _DirSeq_SendTrig(int iTrig);
static void _DirSeqCh_SendTrig(int iCh, int iTrig);
static void _DirSeq_Unlock();
static void _DirSeqCh_Unlock(int iCh);
static void _DirSeq_Stop();
static void _DirSeqCh_Stop(int iCh);
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Init(void (*fnAct)(int ActNo/*[31]=オーバーフロー,[30:24]=未定義,[23:0]=演出番号*/)) {
	//構造体をクリアする。
	memset(&stDirSeq, 0, sizeof stDirSeq);
	//演出実行関数を格納する。
	stDirSeq.fnAct = fnAct;
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_SetActBuf(void (*fn)(), int arg1, int arg2) {
	uint8_t ActBuf[DirSeq_ActBufSize][3];	//_DirSeq_SetActBuf()にてスタック上に確保したバッファのポインタをバッファが有効な期間中のみST_DirSeq.pActBufに設定する。充分なサイズ,かつ,スタックが溢れないようにDirSeq_ActBufSizeを調整せよ。
	if(stDirSeq.pActBuf) { DIE(); }		//バグ
	//演出番号バッファを設定する。
	stDirSeq.pActBuf = ActBuf;
	//引数で指定された関数を実行する。
	(*fn)(arg1, arg2);
	//演出番号を全て送出する。
	while(stDirSeq.ActBufCnt) {
		_DirSeq_GetActBuf(0/*bOverFlow*/);	//通常の送出であることを示す。
	}
	//演出番号バッファをクリアする。
	stDirSeq.pActBuf = NULL;
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_AddActBuf(int ActNo) {
	int i;
	if(!stDirSeq.pActBuf) { DIE(); }	//バグ
	//演出番号バッファに空きが無ければ…
	if(stDirSeq.ActBufCnt >= DirSeq_ActBufSize) {
		//演出番号を一個送出して空きを確保する。
		_DirSeq_GetActBuf(1/*bOverFlow*/);	//オーバーフロー時の送出であることを示す。
		if(stDirSeq.ActBufCnt >= DirSeq_ActBufSize) { DIE(); }	//バグ
	}
	//演出番号を演出番号バッファに追加する。
	i = stDirSeq.ActBufCnt + stDirSeq.ActBufPos;
	if(i >= DirSeq_ActBufSize) { i -= DirSeq_ActBufSize; }
	stDirSeq.pActBuf[i][0] = ActNo; ActNo >>= 8;
	stDirSeq.pActBuf[i][1] = ActNo; ActNo >>= 8;
	stDirSeq.pActBuf[i][2] = ActNo;
	stDirSeq.ActBufCnt++;
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeq_GetActBuf(int bOverFlow) {
	int ActNo;
	if(!stDirSeq.pActBuf ||
	   !stDirSeq.ActBufCnt) { DIE(); }	//バグ
	//演出番号を一個送出する。
	ActNo  = stDirSeq.pActBuf[stDirSeq.ActBufPos][0];	//┐
	ActNo |= stDirSeq.pActBuf[stDirSeq.ActBufPos][1] <<  8;	//├[23:0]=演出番号
	ActNo |= stDirSeq.pActBuf[stDirSeq.ActBufPos][2] << 16;	//┘
	ActNo |= bOverFlow                               << 31;	//─[31]=オーバーフロー,[30:24]=未定義
	stDirSeq.ActBufCnt--;
	if(++stDirSeq.ActBufPos >= DirSeq_ActBufSize) { stDirSeq.ActBufPos = 0; }
	(*stDirSeq.fnAct)(ActNo);
}
/****************************************************************************
 *	
 ****************************************************************************/
static void _DirSeqCh_Start(int iCh, const uint8_t* p) {
	ST_DirSeqCh* pDirSeqCh;
	int c1, c2;
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh > (unsigned)DirSeqCh_Max) { DIE(); }	//バグ
	pDirSeqCh = &stDirSeq.TBL_DirSeqCh[iCh];
	//実行中の演出シーケンスが有れば停止する。
	_DirSeqCh_Stop(iCh);
	//相対位置とロック時間を取得する。
	c1  =         *p++;
	c1 |=         *p++ <<  8;
	c1 |= (int8_t)*p++ << 16;	//符号拡張、忘れずに!!
	c2  =         *p++;
	c2 |=         *p++ <<  8;
	//命令位置とロック時間を設定する。
	pDirSeqCh->pDirSeq = p + c1;
	pDirSeqCh->LockCnt =     c2;
	//演出シーケンスの開始と同時に、演出シーケンスを1フレーム実行する。
	_DirSeqCh_Exec1(iCh, 0/*bStop*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Exec() {
	if(stDirSeq.pActBuf) {
		_DirSeq_Exec();
	} else {
		_DirSeq_SetActBuf(_DirSeq_Exec, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Exec() {
	int iCh;
	for(iCh = 0; iCh <= DirSeqCh_Max; iCh++) {
		//演出シーケンス処理を行う。
		_DirSeqCh_Exec1(iCh, 0/*bStop*/);
		//ロックバッファ処理を行う。
		_DirSeqCh_Exec2(iCh, 0/*bUnlock*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Exec1(int iCh, int bStop) {
	ST_DirSeqCh* pDirSeqCh;
	int c1, c2;
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh > (unsigned)DirSeqCh_Max) { DIE(); }	//バグ
	pDirSeqCh = &stDirSeq.TBL_DirSeqCh[iCh];
	//停止中になるまで…
	while(pDirSeqCh->pDirSeq) {
		//停止時の処理でなければ…
		if(!bStop) {
			//待ち時間が残っていたら、抜ける。
			if(pDirSeqCh->WaitCnt) { break; }
		//停止時の処理ならば…
		} else {
			//待ち時間をクリアする。
			pDirSeqCh->WaitCnt = 0;
		}
		//1バイト目を取得する。
		c1 = *pDirSeqCh->pDirSeq++;
		switch(c1) {
		case 0x00://end
			//スタックが有れば…
			if(pDirSeqCh->StackLev) {
				//スタックを減らす。
				pDirSeqCh->StackLev--;
				//Repeatスタックならば…
				if(pDirSeqCh->TBL_StackRep[pDirSeqCh->StackLev]) {
					//停止時の処理でなければ…
					if(!bStop) {
						//繰り返し回数が無限(255)か,又は,繰り返し回数を1減らしてまだ残っていたら…
						if((  pDirSeqCh->TBL_StackRep[pDirSeqCh->StackLev] == 255/*無限*/) ||
						   (--pDirSeqCh->TBL_StackRep[pDirSeqCh->StackLev] != 0)) {
							//命令位置を設定する。
							pDirSeqCh->pDirSeq = pDirSeqCh->TBL_StackRet[pDirSeqCh->StackLev];
							//スタックを増やす。
							pDirSeqCh->StackLev++;
						}
					}
				//Callスタックならば…
				} else {
					//命令位置を設定する。
					pDirSeqCh->pDirSeq = pDirSeqCh->TBL_StackRet[pDirSeqCh->StackLev];
				}
			//スタックが無ければ…
			} else {
				//停止中にする。
				pDirSeqCh->pDirSeq = NULL;
			}
			break;
		case 0x01://repeat
			//繰り返し回数を取得する。
			c2 = *pDirSeqCh->pDirSeq++;
			if(!c2) { DIE(); }	//データバグ
			//繰り返し回数と命令位置を格納する。
			if(pDirSeqCh->StackLev >= DirSeqCh_StackSize) { DIE(); }	//スタック溢れ
			pDirSeqCh->TBL_StackRep[pDirSeqCh->StackLev] = c2;
			pDirSeqCh->TBL_StackRet[pDirSeqCh->StackLev] = pDirSeqCh->pDirSeq;
			//スタックを増やす。
			pDirSeqCh->StackLev++;
			break;
		case 0x02://wait
			//待ち時間を取得する。
			c2  = *pDirSeqCh->pDirSeq++;
			c2 |= *pDirSeqCh->pDirSeq++ << 8;
			if(!c2) { DIE(); }	//データバグ
			//待ち時間を格納する。
			pDirSeqCh->WaitCnt = c2;
			break;
		case 0x03://call
			//相対位置を取得する。
			c2  =         *pDirSeqCh->pDirSeq++;
			c2 |=         *pDirSeqCh->pDirSeq++ <<  8;
			c2 |= (int8_t)*pDirSeqCh->pDirSeq++ << 16;	//符号拡張、忘れずに!!
			//繰り返し回数(0=Callスタック)と命令位置を格納する。
			if(pDirSeqCh->StackLev >= DirSeqCh_StackSize) { DIE(); }	//スタック溢れ
			pDirSeqCh->TBL_StackRep[pDirSeqCh->StackLev] = 0;
			pDirSeqCh->TBL_StackRet[pDirSeqCh->StackLev] = pDirSeqCh->pDirSeq;
			//スタックを増やす。
			pDirSeqCh->StackLev++;
			//命令位置を設定する。
			pDirSeqCh->pDirSeq += c2;
			break;
		case 0x04://act
		case 0x05://act flush
			//演出番号を取得する。
			c2 =  *pDirSeqCh->pDirSeq++;
			c2 |= *pDirSeqCh->pDirSeq++ <<  8;
			c2 |= *pDirSeqCh->pDirSeq++ << 16;
			//停止時の処理でないか,又は,停止時にも実行する命令(act flush)ならば…
			if(!bStop || (c1 == 0x05/*act flush*/)) {
				//演出番号を演出番号バッファに追加する。
				_DirSeq_AddActBuf(c2);
			}
			break;
		default:DIE();
		}
	}
	//停止時の処理でなければ…
	if(!bStop) {
		//次回のために待ち時間を減らす。
		if(pDirSeqCh->WaitCnt) { pDirSeqCh->WaitCnt--; }
	//停止時の処理ならば…
	} else {
		//待ち時間をクリアする。
		pDirSeqCh->WaitCnt = 0;
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Exec2(int iCh, int bUnlock) {
	ST_DirSeqCh* pDirSeqCh;
	const uint8_t* p;
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh > (unsigned)DirSeqCh_Max) { DIE(); }	//バグ
	pDirSeqCh = &stDirSeq.TBL_DirSeqCh[iCh];
	//ロックバッファが空になるまで…
	while((p = pDirSeqCh->TBL_LockBuf[0])) {
		//ロック解除時の処理でなければ…
		if(!bUnlock) { 
			//ロック時間が残っていたら、抜ける。
			if(pDirSeqCh->LockCnt) { break; }
		//ロック解除時の処理ならば…
		} else { 
			//ロック時間をクリアする。
			pDirSeqCh->LockCnt = 0;
		}
		//ロックバッファの先頭要素を削除する。
//{{このmemmove()の使い方は全く正しいにもかかわらずVC++6.0では警告出るので一時的に警告を抑制して回避する。
#ifdef  _MSC_VER
#pragma warning(disable:4090)	//'function' : 'const' 修飾子は異なります。
#pragma warning(disable:4022)	//'memmove' : 1 番目の実引数のﾎﾟｲﾝﾀの型が、仮引数のﾎﾟｲﾝﾀの型と異なります。
#endif//_MSC_VER
		memmove(&pDirSeqCh->TBL_LockBuf[0],
		        &pDirSeqCh->TBL_LockBuf[1],
		 (sizeof pDirSeqCh->TBL_LockBuf[0]) * (DirSeqCh_LockBufSize - 1));
#ifdef  _MSC_VER
#pragma warning(default:4090)	//'function' : 'const' 修飾子は異なります。
#pragma warning(default:4022)	//'memmove' : 1 番目の実引数のﾎﾟｲﾝﾀの型が、仮引数のﾎﾟｲﾝﾀの型と異なります。
#endif//_MSC_VER
//}}このmemmove()の使い方は全く正しいにもかかわらずVC++6.0では警告出るので一時的に警告を抑制して回避する。
		         pDirSeqCh->TBL_LockBuf[      (DirSeqCh_LockBufSize - 1)] = NULL;
		//演出シーケンスを開始する。
		_DirSeqCh_Start(iCh, p);
	}
	//ロック解除時の処理でなければ…
	if(!bUnlock) { 
		//次回のためにロック時間を減らす。
		if(pDirSeqCh->LockCnt) { pDirSeqCh->LockCnt--; }
	//ロック解除時の処理ならば…
	} else { 
		//ロック時間をクリアする。
		pDirSeqCh->LockCnt = 0;
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_ClrTbl() {
	if(stDirSeq.pActBuf) {
		_DirSeq_ClrTbl();
	} else {
		_DirSeq_SetActBuf(_DirSeq_ClrTbl, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
void _DirSeq_ClrTbl() {
	int iCh;
	for(iCh = 0; iCh <= DirSeqCh_Max; iCh++) {
		DirSeqCh_SetTbl(iCh, NULL);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_SetTbl(int iCh, const uint8_t* pDirTbl/*NULL=クリア*/) {
	if(stDirSeq.pActBuf) {
		_DirSeqCh_SetTbl(iCh, pDirTbl);
	} else {
		_DirSeq_SetActBuf(_DirSeqCh_SetTbl, iCh, (int)pDirTbl);
	}
}
/*--------------------------------------------------------------------------*/
void _DirSeqCh_SetTbl(int iCh, const uint8_t* pDirTbl/*NULL=クリア*/) {
	ST_DirSeqCh* pDirSeqCh;
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh > (unsigned)DirSeqCh_Max) { DIE(); }	//バグ
	pDirSeqCh = &stDirSeq.TBL_DirSeqCh[iCh];
	//'clr'トリガを送信する。
	DirSeqCh_SendTrig(iCh, 255/*clr*/);
	//演出テーブルを設定する。
	pDirSeqCh->pDirTbl = pDirTbl;
	//'set'トリガを送信する。
	DirSeqCh_SendTrig(iCh,   0/*set*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_SendTrig(int iTrig) {
	if(stDirSeq.pActBuf) {
		_DirSeq_SendTrig(iTrig);
	} else {
		_DirSeq_SetActBuf(_DirSeq_SendTrig, iTrig, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_SendTrig(int iTrig) {
	int iCh;
	for(iCh = 0; iCh <= DirSeqCh_Max; iCh++) {
		DirSeqCh_SendTrig(iCh, iTrig);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_SendTrig(int iCh, int iTrig) {
	if(stDirSeq.pActBuf) {
		_DirSeqCh_SendTrig(iCh, iTrig);
	} else {
		_DirSeq_SetActBuf(_DirSeqCh_SendTrig, iCh, iTrig);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_SendTrig(int iCh, int iTrig) {
	ST_DirSeqCh* pDirSeqCh;
	const uint8_t* p;
	int i, c1, c2;
	//演出シーケンサチャネルを取得する。
	if((unsigned)iCh > (unsigned)DirSeqCh_Max) { DIE(); }	//バグ
	pDirSeqCh = &stDirSeq.TBL_DirSeqCh[iCh];
	//演出テーブルを走査して…
	p = pDirSeqCh->pDirTbl;
	while(p) {
		//1バイト目を取得する。
		c1 = *p++;
		switch(c1) {
		case 0x00://end
			p = NULL;
			break;
		case 0x06://on
		case 0x07://on unlock
			//トリガ番号を取得する。
			c2 = *p++;
			//トリガ番号が一致したら…
			if(c2 == iTrig) {
				//ロック解除する命令(on unlock)ならば…
				if(c1 == 0x07/*on unlock*/) {
					//ロック解除する。
					DirSeqCh_Unlock(iCh);
				}
				//ロックバッファに空きが無ければ…
				if(pDirSeqCh->TBL_LockBuf[(DirSeqCh_LockBufSize - 1)]) {
					//現在のロック時間を強制的にクリアする。
					pDirSeqCh->LockCnt = 0;
					//最低一個以上の演出シーケンスを開始し、空きを確保する。
					_DirSeqCh_Exec2(iCh, 0/*bUnlock*/);
					if(pDirSeqCh->TBL_LockBuf[(DirSeqCh_LockBufSize - 1)]) { DIE(); }	//バグ
				}
				//ロックバッファが空でなければ…
				if(pDirSeqCh->TBL_LockBuf[0]) {
					//ロックバッファの最初の空き要素の位置を検索する。
					//上の処理で空きを確保済みなので、少なくとも最終要素が空きである。
					for(i = 0; i < DirSeqCh_LockBufSize - 1/*上記コメント参照*/; i++) {
						if(!pDirSeqCh->TBL_LockBuf[i]) { break; }
					}
					//ロックバッファの最初の空き要素の位置に演出シーケンスを格納する。
					pDirSeqCh->TBL_LockBuf[i] = p;
				//ロックバッファが空ならば…
				} else {
					//ロック時間が残っていたら…
					if(pDirSeqCh->LockCnt) {
						//ロックバッファの先頭に演出シーケンスを格納する。
						pDirSeqCh->TBL_LockBuf[0] = p;
					//ロック時間が残っていなければ…
					} else {
						//演出シーケンスを開始する。
						_DirSeqCh_Start(iCh, p);
					}
				}
			}
			//相対位置とロック時間を読み飛ばす。
			p += (3/*相対位置*/ + 2/*ロック時間*/);
			break;
		default:DIE();
		}
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Unlock() {
	if(stDirSeq.pActBuf) {
		_DirSeq_Unlock();
	} else {
		_DirSeq_SetActBuf(_DirSeq_Unlock, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Unlock() {
	int iCh;
	for(iCh = 0; iCh <= DirSeqCh_Max; iCh++) {
		DirSeqCh_Unlock(iCh);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_Unlock(int iCh) {
	if(stDirSeq.pActBuf) {
		_DirSeqCh_Unlock(iCh);
	} else {
		_DirSeq_SetActBuf(_DirSeqCh_Unlock, iCh, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Unlock(int iCh) {
	_DirSeqCh_Exec2(iCh, 1/*bUnlock*/);
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeq_Stop() {
	if(stDirSeq.pActBuf) {
		_DirSeq_Stop();
	} else {
		_DirSeq_SetActBuf(_DirSeq_Stop, 0/*dummy*/, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeq_Stop() {
	int iCh;
	for(iCh = 0; iCh <= DirSeqCh_Max; iCh++) {
		DirSeqCh_Stop(iCh);
	}
}
/****************************************************************************
 *	
 ****************************************************************************/
void DirSeqCh_Stop(int iCh) {
	if(stDirSeq.pActBuf) {
		_DirSeqCh_Stop(iCh);
	} else {
		_DirSeq_SetActBuf(_DirSeqCh_Stop, iCh, 0/*dummy*/);
	}
}
/*--------------------------------------------------------------------------*/
static void _DirSeqCh_Stop(int iCh) {
	_DirSeqCh_Exec1(iCh, 1/*bStop*/);
}
/****************************************************************************
 *	dDirSeqC.exeが出力したバイナリ形式を扱うためのユーティリティ関数
 ****************************************************************************/
#ifdef  USE_DIRSEQ_BIN
void DirSeqCh_SetTblNo(int iCh, const void* pDirSeqBin, int iDirTblNo) {
	const int* const TBL_DirTblNo = pDirSeqBin;						//バイナリの先頭にアドレステーブルが有る。アドレスはデータ本体(BYTE配列)のインデクスである。
	const int nDirTblNo = TBL_DirTblNo[0];							//アドレステーブルの先頭[0]に要素数が入っている。要素数はそれ自身([0])も含む。
	const unsigned char* const TBL_DirSeq = (const unsigned char*)&TBL_DirTblNo[nDirTblNo];	//アドレステーブルの直後からデータ本体(BYTE配列)が開始する。
	if((iDirTblNo <= 0) || (iDirTblNo >= nDirTblNo)) { DIE(); }
	DirSeqCh_SetTbl(iCh, &TBL_DirSeq[TBL_DirTblNo[iDirTblNo]]);
}
#endif//USE_DIRSEQ_BIN
