/*
 *	clipevtm.c
 *
 *	イベントタイマ
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2014 Naoyuki Sawa
 *
 *	* Mon Jun 09 17:53:50 JST 2014 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "clip.h"

/****************************************************************************
 *	アプリケーション用関数
 ****************************************************************************/
/* イベントタイマを初期化する。
 * [in]
 *	nEvtTmrCh	イベントタイマチャネル数
 *	pData		各イベントタイマチャネルのデータ (Wait,Event,Wait,Event,…の並び)
 *	onEvent		アプリケーション定義のイベント処理関数
 * [note]
 *	- 本関数は、各イベントタイマチャネルのデータをコピーせず、ポインタを格納することに注意せよ。
 *	  アプリケーションは、本関数が処理を返した後も、各イベントタイマチャネルのデータを変更したり破棄してはいけない。
 */
ST_EvtTmr* EvtTmr_Init(int nEvtTmrCh, const uint16_t* const* pData/*[nEvtTmrCh]*/, void (*onEvent)(int iEvtTmrCh, int iEvent)) {
	ST_EvtTmr* pEvtTmr;
	/* イベントタイマ構造体のメモリを確保する。 */
	pEvtTmr = malloc(sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) * nEvtTmrCh));
	if(!pEvtTmr) { DIE(); } /* メモリ不足 */
	/* イベントタイマ構造体を初期化する。 */
	EvtTmr_InitStatic(pEvtTmr, nEvtTmrCh, pData, onEvent);
	/* イベントタイマ構造体のメモリを返す。 */
	return pEvtTmr;
}
/*--------------------------------------------------------------------------*/
/* 静的に確保したメモリを使用して、イベントタイマを作成します。
 * [in]
 *	pEvtTmr		静的に確保したメモリ
 *	nEvtTmrCh	イベントタイマチャネル数
 *	pData		各イベントタイマチャネルのデータ (Wait,Event,Wait,Event,…の並び)
 *	onEvent		アプリケーション定義のイベント処理関数
 * [note]
 *	- 本関数は、各イベントタイマチャネルのデータをコピーせず、ポインタを格納することに注意せよ。
 *	  アプリケーションは、本関数が処理を返した後も、各イベントタイマチャネルのデータを変更したり破棄してはいけない。
 * [使用例]
 *	│struct {
 *	│  ST_EvtTmr   evtTmr;
 *	│  ST_EvtTmrCh evtTmrCh[3];
 *	│} stEvtTmr;
 *	│EvtTmr_InitStatic(&stEvtTmr.evtTmr, 3, pData, onEvent);
 */
void EvtTmr_InitStatic(ST_EvtTmr* pEvtTmr, int nEvtTmrCh, const uint16_t* const* pData/*[nEvtTmrCh]*/, void (*onEvent)(int iEvtTmrCh, int iEvent)) {
	/* イベントタイマ構造体をクリアする。 */
	memset(pEvtTmr, 0, sizeof(ST_EvtTmr) + (sizeof(ST_EvtTmrCh) * nEvtTmrCh));
	/* イベントタイマ構造体に、イベントタイマチャネル数,各イベントタイマチャネルのデータ,アプリケーション定義のイベント処理関数を格納する。 */
	pEvtTmr->nEvtTmrCh = nEvtTmrCh;	/* イベントタイマチャネル数 */
	pEvtTmr->pData     = pData;	/* 各イベントタイマチャネルのデータ (Wait,Event,Wait,Event,…の並び) */
	pEvtTmr->onEvent   = onEvent;	/* アプリケーション定義のイベント処理関数 */
}
/*--------------------------------------------------------------------------*/
/* イベントタイマチャネルを開始する。
 * [in]
 *	pEvtTmr		イベントタイマ
 *	iEvtTmrCh	イベントタイマチャネル番号
 * [note]
 *	- イベントタイマチャネルが動作中だった場合、リスタートする。
 */
void EvtTmr_Start(ST_EvtTmr* pEvtTmr, int iEvtTmrCh) {
	ST_EvtTmrCh* pEvtTmrCh;
	/* イベントタイマチャネル番号が正常であることを確認する。 */
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	/* イベントタイマチャネルを取得する。 */
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	/* イベントタイマチャネルを開始する。 */
	pEvtTmrCh->pData = pEvtTmr->pData[iEvtTmrCh];
	pEvtTmrCh->Wait  = *pEvtTmrCh->pData++;		//最初のイベントの待ち時間を取得する。
}
/*--------------------------------------------------------------------------*/
/* イベントタイマチャネルを確実に停止する。
 * [in]
 *	pEvtTmr		イベントタイマ
 *	iEvtTmrCh	イベントタイマチャネル番号
 * [out]
 *	戻り値		イベントタイマチャネルが動作中だった場合、イベントタイマチャネルを停止し、0以外を返す。
 *			イベントタイマチャネルが停止中だった場合、何もせず、0を返す。
 * [note]
 *	* 「イベントタイマチャネルを確実に停止する前の状態を返す」ようにした理由は、以下のとおりです。
 *	- 実際のアプリケーションの中で、下記のようなパターンが多く現れることに気付きました:
 *	  <例>
 *	    EvtTmr_Start(pEvtTmr,EvtTmrCh_xxx);
 *	    …
 *	    if(EvtTmr_Stat(pEvtTmr,EvtTmrCh_xxx)){//ﾀｲﾑｱｳﾄしていなければ…
 *	        EvtTmr_Stop(pEvtTmr,EvtTmrCh_xxx);//停止する
 *	        {{ﾀｲﾑｱｳﾄ時に行うはずだった処理を､ここで行う}}
 *	    }else{//ﾀｲﾑｱｳﾄしていたら…
 *	        {{ﾀｲﾑｱｳﾄ時に行った処理の後始末を､ここで行う}}//<例>ﾀｲﾑｱｳﾄで減音した場合､ここで減音復帰する､等
 *	    }
 *	- 「イベントタイマチャネルを確実に停止する前の状態を返す」の戻り値を利用すると、下記のように書けます:
 *	  <例>
 *	    EvtTmr_Start(pEvtTmr,EvtTmrCh_xxx);
 *	    …
 *	    if(EvtTmr_Stop(pEvtTmr,EvtTmrCh_xxx)){//確実に停止する｡停止前にﾀｲﾑｱｳﾄしていなければ…
 *	        {{ﾀｲﾑｱｳﾄ時に行うはずだった処理を､ここで行う}}
 *	    }else{//ﾀｲﾑｱｳﾄしていたら…
 *	        {{ﾀｲﾑｱｳﾄ時に行った処理の後始末を､ここで行う}}//<例>ﾀｲﾑｱｳﾄで減音した場合､ここで減音復帰する､等
 *	    }
 */
int EvtTmr_Stop(ST_EvtTmr* pEvtTmr, int iEvtTmrCh) {
	int iResult;
	ST_EvtTmrCh* pEvtTmrCh;
	/* イベントタイマチャネル番号が正常であることを確認する。 */
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	/* イベントタイマチャネルを取得する。 */
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	/* イベントタイマチャネルの状態を取得する。 */
	iResult = (int)pEvtTmrCh->pData;
	/* イベントタイマチャネルを確実に停止する。 */
	pEvtTmrCh->pData = NULL;
//不要	pEvtTmrCh->Wait  = 0;
	/* イベントタイマチャネルが動作中だったならば0以外、停止中だったならば0を返す。 */
	return iResult;
}
/*--------------------------------------------------------------------------*/
/* イベントタイマチャネルを全て停止する。
 * [in]
 *	pEvtTmr		イベントタイマ
 */
void EvtTmr_StopAll(ST_EvtTmr* pEvtTmr) {
	memset(pEvtTmr->EvtTmrCh, 0, sizeof(ST_EvtTmrCh) * pEvtTmr->nEvtTmrCh);
}
/*--------------------------------------------------------------------------*/
/* イベントタイマチャネルの状態を取得する。
 * [in]
 *	pEvtTmr		イベントタイマ
 *	iEvtTmrCh	イベントタイマチャネル番号
 * [out]
 *	戻り値		イベントタイマチャネルが動作中ならば0以外、停止中ならば0を返す。
 */
int EvtTmr_Stat(ST_EvtTmr* pEvtTmr, int iEvtTmrCh) {
	ST_EvtTmrCh* pEvtTmrCh;
	/* イベントタイマチャネル番号が正常であることを確認する。 */
	if((unsigned)iEvtTmrCh >= (unsigned)pEvtTmr->nEvtTmrCh) { DIE(); }
	/* イベントタイマチャネルを取得する。 */
	pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
	/* イベントタイマチャネルが動作中ならば0以外、停止中ならば0を返す。 */
	return (int)pEvtTmrCh->pData;
}
/*--------------------------------------------------------------------------*/
/* イベントタイマの周期処理を行う。
 * [in]
 *	pEvtTmr		イベントタイマ
 * [note]
 *	- アプリケーション定義のイベント処理関数の中で、任意のチャネル(自身含む)に対して、
 *	  EvtTmr_Start(),及び,EvtTmr_Stop()を呼び出すことも、安全です。
 */
void EvtTmr_Exec(ST_EvtTmr* pEvtTmr) {
	int iEvtTmrCh, iEvent;
	ST_EvtTmrCh* pEvtTmrCh;
	/* 各イベントタイマチャネルについて… */
	for(iEvtTmrCh = 0; iEvtTmrCh < pEvtTmr->nEvtTmrCh; iEvtTmrCh++) {
		/* イベントタイマチャネルを取得する。 */
		pEvtTmrCh = &pEvtTmr->EvtTmrCh[iEvtTmrCh];
		/* イベントタイマチャネルが動作中である間… */
		while(pEvtTmrCh->pData) {
			/* 待ち時間の残数が有れば、次回のために待ち時間の残数を減らす。 */
			if(pEvtTmrCh->Wait) { pEvtTmrCh->Wait--; break; }	//ここまで
			/* イベント番号を取得する。 */
			iEvent = *pEvtTmrCh->pData++;
			/* イベント番号が'終了'ならば… */
			if(iEvent == EvtTmr_Event_End) {
				/* イベントタイマチャネルを停止する。 */
				EvtTmr_Stop(pEvtTmr, iEvtTmrCh);
			/* イベント番号が'ループ'ならば… */
			} else if(iEvent == EvtTmr_Event_Loop) {
				/* イベントタイマチャネルをリスタートする。 */
				EvtTmr_Start(pEvtTmr, iEvtTmrCh);
			/* イベント番号が'それ以外'(=アプリケーション定義の値)ならば… */
			} else {
			    //{{この処理順必須
				pEvtTmrCh->Wait = *pEvtTmrCh->pData++;		//次のイベントの待ち時間を取得する。onEvent()の中で、EvtTmr_Start(),又は,EvtTmr_Stop()が呼ばれた場合は、Waitが上書きされる。
				(*pEvtTmr->onEvent)(iEvtTmrCh, iEvent);		//この中でEvtTmr_Start(),又は,EvtTmr_Stop()が呼ばれて、Wait,及び,pDataが変化する可能性があるが、この処理順ならば安全である。
			    //}}この処理順必須
			}
		}
	}
}

/****************************************************************************
 *	サンプル
 ****************************************************************************/

//	const uint16_t Data0[]={//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#0のﾃﾞｰﾀ
//	SEC(0),10, //開始直後に､ｺﾏﾝﾄﾞ番号(10)を実行する｡
//	SEC(2),11, // 2秒待って､ｺﾏﾝﾄﾞ番号(11)を実行する｡
//	SEC(1),12, // 1秒待って､ｺﾏﾝﾄﾞ番号(12)を実行する｡
//	SEC(0),13, //その後すぐ､ｺﾏﾝﾄﾞ番号(13)を実行する｡
//	SEC(3),-1};// 3秒待って､先頭へﾙｰﾌﾟする｡
//	const uint16_t Data1[]={//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#1のﾃﾞｰﾀ
//	SEC(9),20, // 9秒待って､ｺﾏﾝﾄﾞ番号(20)を実行する｡
//	SEC(0), 0};//その後すぐ､終了する｡
//	const uint16_t* const TBL_Data[2]={Data0,Data1};//各ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙのﾃﾞｰﾀ
//	void onEvent(int iEvtTmrCh,int iEvent){//ｱﾌﾟﾘｹｰｼｮﾝ定義のｲﾍﾞﾝﾄ処理関数
//	  TRACE("%d:%d:%d\n",now/FPS,iEvtTmrCh,iEvent);}
//	void Test() {
//	  ST_EvtTmr* pEvtTmr=EvtTmr_Init(2,TBL_Data,onEvent);
//	  EvtTmr_Start(pEvtTmr,0);//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#0を開始する｡
//	  EvtTmr_Start(pEvtTmr,1);//ｲﾍﾞﾝﾄﾀｲﾏﾁｬﾈﾙ#1を開始する｡
//	  for(;;) {//ﾒｲﾝﾙｰﾌﾟ
//	    schedule();
//	    EvtTmr_Exec(pEvtTmr);//ｲﾍﾞﾝﾄﾀｲﾏの周期処理を行う｡
//	  }
//	}

