/*
 *	clipdlyq.c
 *
 *	遅延コールバックキュー
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2017-2018 Naoyuki Sawa
 *
 *	* Mon Jan 30 21:41:16 JST 2017 Naoyuki Sawa
 *	- 1st リリース。
 *	* Wed Apr 11 23:59:59 JST 2018 Naoyuki Sawa
 *	- DlyQue_AddArg_l(),DlyQue_AddArg_a(),DlyQue_AddArg_v()を追加しました。
 *	  遅延コールバックキューをアドホックに利用するための関数です。
 *	  clipcbkq.cモジュールにも実装可能なのですが、遅延コールバックキューでしか使わないと思ったので、clipdlyq.cモジュールに実装する事にしました。
 *	  もし今後、clipcbkq.cモジュールでもこのような使い方が出来た方が便利だと判断した場合は、clipcbkq.cモジュールにも同等の関数を追加して下さい。
 */
#include "clip.h"
/*****************************************************************************
 *	構造体
 *****************************************************************************/
/*typedef*/ struct _ST_DlyQue {
	int		t;		//現在時刻									DlyQue_New()が呼び出された時点を0として、DlyQue_Exec()が呼び出される度に+1します。単なるカウンタであり、time()やGetTickCount()等が返す時刻とは関係有りません。
	GTree*		tree;		//(時刻⇒コールバックキュー)のツリー	key=(pDlyQue->t+dly),value=pCbkQue
	GSList*		list;		//解放したコールバックキューのプール	data=pCbkQue				DlyQue_ReleaseCbkQue()にて、解放したコールバックキューをプールに戻す前に、CbkQue_Clear()を実行してコールバックキューを空にしています。従って、プールが保持しているコールバックキューは全て、空の状態のコールバックキューです。
} /*ST_DlyQue*/;
/*---------------------------------------------------------------------------*/
typedef struct _ST_DlyQue_Param {
	ST_DlyQue*	pDlyQue;
	void		(*fn)(ST_DlyQue* pDlyQue, int pri, void* param);
	void*		param;
    //	char		mem[memBytes];
} ST_DlyQue_Param;
/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/
static int DlyQue_KeyCompare(const void* a, const void* b, void* user_data) {
	if(a < b) { return -1; }
	if(a > b) { return  1; }
	          { return  0; }
}
/*---------------------------------------------------------------------------*/
static ST_CbkQue* DlyQue_LookupCbkQue(ST_DlyQue* pDlyQue, int dly) {
	//(現在時刻+遅延時間)をキーとする。
	void* const key = (void*)(intptr_t)(pDlyQue->t + dly);
	//このキーに対応するコールバックキューが既に登録されているか?
	ST_CbkQue* pCbkQue = g_tree_lookup(pDlyQue->tree, key);
	//このキーに対応するコールバックキューがまだ登録されていなければ…
	if(!pCbkQue) {
		//解放したコールバックキューのプールが空ならば…
		if(!pDlyQue->list) {
			//コールバックキューを一つ作成して、解放したコールバックキューのプールに追加する。
			pDlyQue->list = g_slist_prepend(pDlyQue->list, DynamicCbkQue_New(0));
#ifdef  DEBUG
			syslog(LOG_DEBUG, "DlyQue_LookupCbkQue: New Callback queue is allocated.");	//一般的な使い方では、ある程度アプリケーションの実行を継続したら、プール内のコールバックキューだけでまかなわれるようになり、新たなコールバックキューは作成されなくなるはずです。いつまでもこの行が実行され続ける場合、バグの可能性が有ります。
#endif//DEBUG
		}
		//解放したコールバックキューのプールから、コールバックキューを一つ取り出す。
		pCbkQue = pDlyQue->list->data;
		pDlyQue->list = g_slist_delete_link(pDlyQue->list, pDlyQue->list);
		//このキーに対応するコールバックキューを登録する。
		g_tree_insert(pDlyQue->tree, key, pCbkQue);
	}
	//コールバックキューを返す。
	return pCbkQue;
}
/*---------------------------------------------------------------------------*/
static void DlyQue_ReleaseCbkQue(ST_DlyQue* pDlyQue, ST_CbkQue* pCbkQue) {
	//コールバックキューを空にする。
	CbkQue_Clear(pCbkQue);
	//コールバックキューを、解放したコールバックキューのプールに追加する。
	pDlyQue->list = g_slist_prepend(pDlyQue->list, pCbkQue);
}
/*---------------------------------------------------------------------------*/
static void DlyQue_fnStub(ST_CbkQue* pCbkQue, int pri, void* param) {
	ST_DlyQue_Param* pParam = param;
	(*pParam->fn)(pParam->pDlyQue, pri, pParam->param);
}
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//遅延コールバックキューを作成する。
//[out]
//	戻り値			作成した遅延コールバックキューのポインタ。
ST_DlyQue* DlyQue_New() {
	//構造体のメモリを確保する。
	ST_DlyQue* pDlyQue = calloc(1, sizeof(ST_DlyQue));
	if(!pDlyQue) { DIE(); }
	//(時刻⇒コールバックキュー)のツリーを作成する。
	pDlyQue->tree = g_tree_new_full(
		DlyQue_KeyCompare,		//key_compare_func
		NULL,				//key_compare_data
		NULL,				//key_destroy_func
		(void (*)(void*))CbkQue_Free);	//value_destroy_func	ここで指定するCbkQue_Free()は、ツリーに未実行のコールバックキューが残っている状態で、DlyQue_Free()が呼ばれた場合の解放処理です。DlyQue_Exec()にてコールバックキューを取り外す際は、ここで指定したCbkQue_Free()が呼ばれないよう、g_tree_remove()ではなくg_tree_steal()を使って取り外しています。
	return pDlyQue;
}
/*---------------------------------------------------------------------------*/
//遅延コールバックキューを解放する。
//[out]
//	戻り値			解放する遅延コールバックキューのポインタ。
void DlyQue_Free(ST_DlyQue* pDlyQue) {
	//(時刻⇒コールバックキュー)のツリーを削除する。
	g_tree_unref(pDlyQue->tree);
	//解放したコールバックキューのプールを削除する。
	g_slist_free_full(pDlyQue->list,
		(void (*)(void*))CbkQue_Free);	//free_func_
	//構造体のメモリを解放する。
	free(pDlyQue);
}
/*---------------------------------------------------------------------------*/
//関数を登録する。
//[in]
//	pDlyQue			遅延コールバックキューのポインタ。
//	dly			遅延時間。	0未満の遅延時間を指定した場合、動作上は(unsigned)値として解釈されます。
//				0ならば、次にDlyQue_Exec()を呼んだ時にコールバックされる。
//				1ならば、次の次にDlyQue_Exec()を呼んだ時にコールバックされる。
//				2ならば、次の次の次にDlyQue_Exec()を呼んだ時にコールバックされる。
//				…
//	pri			同じ時刻の中での優先度。
//				詳細は、clipcbkq.cモジュールを参照して下さい。
//				同じ時刻，同じ優先度ならば、登録順序と同じ順序でコールバックされる事も、clipcbkq.cモジュールの動作と同じです。
//	fn,param,memBytes	CbkQue_Add(),及び,CbkQue_AddMem()と同じです。
void DlyQue_Add(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(ST_DlyQue* pDlyQue, int pri, void* param), void* param) {
	//(現在時刻+遅延時間)に対応するコールバックキューを取得する。
	ST_CbkQue* pCbkQue = DlyQue_LookupCbkQue(pDlyQue, dly);
	//コールバックキューに関数を登録する。
	ST_DlyQue_Param* pParam = CbkQue_AddMem(pCbkQue, pri, DlyQue_fnStub, sizeof(ST_DlyQue_Param));
	pParam->pDlyQue = pDlyQue;
	pParam->fn = fn;
	pParam->param = param;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void* DlyQue_AddMem(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(ST_DlyQue* pDlyQue, int pri, void* param), int memBytes) {
	//(現在時刻+遅延時間)に対応するコールバックキューを取得する。
	ST_CbkQue* pCbkQue = DlyQue_LookupCbkQue(pDlyQue, dly);
	//コールバックキューに関数を登録する。
	ST_DlyQue_Param* pParam = CbkQue_AddMem(pCbkQue, pri, DlyQue_fnStub, sizeof(ST_DlyQue_Param) + memBytes);
	pParam->pDlyQue = pDlyQue;
	pParam->fn = fn;
	pParam->param = pParam + 1;
	return pParam->param;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{2018/04/11追加:DlyQue_AddArg_l(),DlyQue_AddArg_a(),DlyQue_AddArg_v()を追加しました。
#define DlyQue_AddArg_MaxArgc	8	//調整可
typedef struct _ST_DlyQue_AddArg_Param {
	void	(*fn)();
	void*	argv[DlyQue_AddArg_MaxArgc];
} ST_DlyQue_AddArg_Param;
static void DlyQue_AddArg_fn(ST_DlyQue* pDlyQue, int pri, void* param) {
	ST_DlyQue_AddArg_Param* p = param;
	static_assert((sizeof p->argv / sizeof p->argv[0]) == 8, "DlyQue_AddArg_MaxArgcの値が想定と異なります。");
	(*p->fn)(p->argv[0], p->argv[1], p->argv[2], p->argv[3], p->argv[4], p->argv[5], p->argv[6], p->argv[7]);	//余分な引数はcdecl呼出規約によって無視されるので、常に最大の引数を引き渡して問題有りません。
}
#ifndef PIECE
void DlyQue_AddArg_l(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(), int argc, ...) {
	va_list ap;
	va_start(ap, argc);
//不要	if((unsigned)argc > DlyQue_AddArg_MaxArgc) { DIE(); }	//ここで停止した場合は、DlyQue_AddArg_MaxArgcの値を増やせ。	⇒DlyQue_AddArg_a()で検査を行うのでここでは検査不要です。
	DlyQue_AddArg_a(pDlyQue, dly, pri, fn, argc, ap);
	va_end(ap);
}
void DlyQue_AddArg_a(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(), int argc, va_list ap) {
	void* argv[DlyQue_AddArg_MaxArgc];
	int i;
	if((unsigned)argc > DlyQue_AddArg_MaxArgc) { DIE(); }	//ここで停止した場合は、DlyQue_AddArg_MaxArgcの値を増やせ。
	for(i = 0; i < argc; i++) { argv[i] = va_arg(ap, void*); }
	DlyQue_AddArg_v(pDlyQue, dly, pri, fn, argc, argv);
}
#else //PIECE
void DlyQue_AddArg_l(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(), int argc, ...);
void DlyQue_AddArg_a(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(), int argc, va_list ap);
asm("
		.code
		.align		1
		.global		DlyQue_AddArg_l
		.global		DlyQue_AddArg_a
DlyQue_AddArg_l:
		xsub		%sp, %sp, 8
		;//%r12     := pDlyQue
		;//%r13     := dly
		;//%r14     := pri
		;//%r15     := fn
		;//[%sp+ 8] := retp
		;//[%sp+12] := argc
		;//[%sp+16] := ...
		xadd		%r9, %sp, 16		;//%r9     :=                                   ap = &...
		xld.w		[%sp+4], %r9		;//[%sp+4] :=                                   ap
		xld.w		%r9, [%sp+12]		;//%r9     :=                             argc
		xld.w		[%sp+0], %r9		;//[%sp+0] :=                             argc
		call		DlyQue_AddArg_a		;//DlyQue_AddArg_a(pDlyQue, dly, pri, fn, argc, ap)
		xadd		%sp, %sp, 8
		ret
DlyQue_AddArg_a:
		;//%r12     := pDlyQue
		;//%r13     := dly
		;//%r14     := pri
		;//%r15     := fn
		;//[%sp+ 0] := retp
		;//[%sp+ 4] := argc
		;//[%sp+ 8] := ap
		xjp		DlyQue_AddArg_v
");
#endif//PIECE
void DlyQue_AddArg_v(ST_DlyQue* pDlyQue, int dly, int pri/*SHRT_MIN〜SHRT_MAX*/, void (*fn)(), int argc, void* argv[/*argc*/]) {
	ST_DlyQue_AddArg_Param* p;
	if((unsigned)argc > DlyQue_AddArg_MaxArgc) { DIE(); }
	p = DlyQue_AddMem(pDlyQue, dly, pri, DlyQue_AddArg_fn, sizeof(ST_DlyQue_AddArg_Param));
	p->fn = fn;
	memcpy(p->argv, argv, sizeof(void*) * argc);
}
#if 0
//□使用例
int cpp_main(int argc, char* argv[]) {
	if(!setjmp(env_exit)) {
		for(;;) {
			schedule();
			if(keyTrig[KEY_INPUT_1]) {
				//1秒後にサウンドを鳴らす。
				DlyQue_AddArg_l(pDlyQue, SEC(1), 0, (void (*)())TapLogCh_Play,
					6,		//argc
					pTapSeq,	//argv[0]
					TapLogCh_Bgm,	//argv[1]
					PhrNo_RINGIN,	//argv[2]
					1,		//argv[3]
					0,		//argv[4]
					0);		//argv[5]
			}
			if(keyTrig[KEY_INPUT_2]) {
				//1秒後にサウンドを止める。
				DlyQue_AddArg_l(pDlyQue, SEC(1), 0, (void (*)())TapLogCh_Stop,
					2,		//argc
					pTapSeq,	//argv[0]
					TapLogCh_Bgm);	//argv[1]
			}
		}
	}
	return 0;
}
#endif
#undef  DlyQue_AddArg_MaxArgc
//}}2018/04/11追加:DlyQue_AddArg_l(),DlyQue_AddArg_a(),DlyQue_AddArg_v()を追加しました。
/*---------------------------------------------------------------------------*/
//遅延コールバックキューを実行する。
//[in]
//	pDlyQue			遅延コールバックキューのポインタ。
//[note]
//	- 単位時間ごとに一回、呼び出して下さい。
//	  通常は、schedule()の中で一回呼び出す事になると思います。
//	- 残り遅延時間が0である関数をコールバックします。
//	  おおよそ、clipcbkq.cモジュールのCbkQue_Invoke()と同じですが、以下の二点が異なる点に注意して下さい。
//	  ・CbkQue_InvokePri()に相当する関数は有りません。
//	    遅延コールバックキューは、実行後に自動的にクリアするので(下記参照)、優先度の範囲を指定して実行する事は意味が無いと思ったからです。
//	  ・DlyQue_Exec()は、コールバックした関数を自動的に登録解除します。
//	    CbkQue_Invoke()に対するCbkQue_Clear()呼び出しのような明示的な登録解除の操作は必要有りません。
//	  上記の通り、CbkQue_Invoke()とは少し使い方が違うので、混同しないようにあえて関数名を変えました。(DlyQue_Invoke()ではなくDlyQue_Exec()としました。)
//	- コールバック関数の中で'DlyQue_Add(pDlyQue,0,pri,fn,param)'を実行する事も可能です。(CbkQue_Invoke()では不可でしたが、DlyQue_Exec()では可能です。)
//	  その場合、既に登録されていた現時刻の関数を全て実行した後に、改めて優先度ソートして呼び出します。
//	  詳細は、下記の実装部のコメントを参照して下さい。
void DlyQue_Exec(ST_DlyQue* pDlyQue) {
	//現在時刻をキーとする。
	void* const key = (void*)(intptr_t)pDlyQue->t;
	//このキーに対応するコールバックキューが存在する間…
	// - 繰り返す理由は、以下の通りです。
	//   呼び出した関数の中で'DlyQue_Add(pDlyQue,0,pri,fn,param)'が実行された場合、現時刻での実行要求と見なします。
	//   しかし、いま処理しているコールバックキューはソート済みであり、処理に割り込んで再ソートする事は難しいです。
	//   そこで、呼び出した関数の中で'DlyQue_Add(pDlyQue,0,pri,fn,param)'が実行された場合、いま処理しているコールバックキューとは別の、新たなコールバックキューに保持しておいて、現時刻で実行する事にしました。
	//   そのために、以下の処理の中では、最初に'g_tree_steal(pDlyQue->tree,key)'を実行している事に注意して下さい。
	// - 上記の方法で処理すれば、処理中のコールバックキューが途中で変更されてしまう問題は防げます。
	//   ただし、DlyQue_Exec()の実行前に登録されていた(普通の使い方ではこちらが主です)関数の優先度と、DlyQue_Exec()の実行中に登録された関数の優先度は、マージされない事に注意して下さい。(理由は上記の説明参照)
	ST_CbkQue* pCbkQue;
	while((pCbkQue = g_tree_lookup(pDlyQue->tree, key))) {
		//このキーに対応するコールバックキューを取り外す。	//g_tree_remove()ではなくg_tree_steal()を使って取り外す事に注意して下さい。もしg_tree_remove()を使うと、value_destroy_funcとしてCbkQue_Free()が呼ばれてしまいます。
		if(!g_tree_steal(pDlyQue->tree, key)) { DIE(); }
		//コールバックキューに登録されている関数を呼び出す。	//関数の中で'DlyQue_Add(pDlyQue,0,pri,fn,param)'が実行された場合、現時刻での実行要求と見なして、新たなコールバックキューに保持する。(上記の説明参照)
		CbkQue_Invoke(pCbkQue);
		//コールバックキューを空にして、解放したコールバックキューのプールに追加する。
		DlyQue_ReleaseCbkQue(pDlyQue, pCbkQue);
		//'CbkQue_Invoke(pCbkQue)'の中で'DlyQue_Add(pDlyQue,0,pri,fn,param)'が実行された可能性が有るので、このキーに対応するコールバックキューが無くなるまで繰り返す。
	}
	//現在時刻を進める。
	pDlyQue->t++;
}
/*****************************************************************************
 *	使用例
 *****************************************************************************/
#if 0
//※TODO:これは開発中に適当に書いた使用例です。もっとちゃんとした使用例を書け。
static void fn1(ST_DlyQue* pDlyQue, int pri, void* param) {
	printf("fn1: %s\n", param);
}
int app_main(int argc, char* argv[]) {
	ST_DlyQue* pDlyQue;
	pDlyQue = DlyQue_New();
	{
		int i;
		DlyQue_Add(pDlyQue, 5, 0, fn1, "foo");
		DlyQue_Add(pDlyQue, 5, -1, fn1, "bar");
		DlyQue_Add(pDlyQue, 1, 0, fn1, "baz");
		{
			char* mem = DlyQue_AddMem(pDlyQue, 3, 0, fn1, 5);
			strcpy(mem, "qux");
		}
		for(i = 0; i < 10; i++) {
			printf("%d ----------------\n", i);
			DlyQue_Exec(pDlyQue);
		}
	}
	{
		int i;
		DlyQue_Add(pDlyQue, 5, 0, fn1, "2foo");
		DlyQue_Add(pDlyQue, 5, -1, fn1, "2bar");
		DlyQue_Add(pDlyQue, 1, 0, fn1, "2baz");
		DlyQue_Add(pDlyQue, 7, 0, fn1, "2quux");
		{
			char* mem = DlyQue_AddMem(pDlyQue, 3, 0, fn1, 5);
			strcpy(mem, "2qux");
		}
		for(i = 0; i < 10; i++) {
			printf("%d ----------------\n", i);
			DlyQue_Exec(pDlyQue);
		}
	}
	DlyQue_Free(pDlyQue);
	return 0;
}
#endif
