/*	
 *	cliplotr.c
 *
 *	抽選表ROM形式
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015 Naoyuki Sawa
 *
 *	* Wed Feb 18 21:49:13 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 当モジュールは、cliplot.cモジュールのラッパーとして動作し、以下の機能を追加します。
 *	  1. 抽選表を、アドレスでなく抽選表番号で指定する、関数インターフェイスを追加します。
 *	  2. 各抽選表の各列の各当選値が、何回当選したかをログとして記録する機能を追加します。
 *	     ログ機能は、LotRom_BeginLog()〜LotRom_EndLog()の間のみ有効となります。
 *	     LotRom_BeginLog()を呼び出さなければ、余分なメモリ消費も処理負荷も生じません。
 *	* Mon Mar 02 21:47:20 JST 2015 Naoyuki Sawa
 *	- ST_LotRom構造体のm_LotTblNo_Max,m_TBL_LotTblフィールドを削除しました。LotRom_Init()の_LotTblNo_Max引数を削除しました。
 *	  やや大幅な変更になので、変更前のコードは残していません。変更前のコードを見返したい場合は、前回のアーカイブを参照してください。
 *	  dLotTblC.exeがTBL_LotTblNo[0]に(LotTblNo_Max+1)を格納するようなったので、アプリケーションから明示的に指定する必要が無くなったからです。
 *	  詳細は/clip/tool/dLotTblC/winapp.hの同日のコメントを参照してください。
 */
#include "clip.h"
/****************************************************************************
 *	ローカル変数定義
 ****************************************************************************/
static ST_LotRom stLotRom;
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static const unsigned short* LotRom_GetLotTbl(int LotTblNo);
#ifdef  USE_LOTROM_LOG
static void LotRom_AddLog(int LotTblNo, const int* pid, int hitval);
#endif//USE_LOTROM_LOG
/****************************************************************************
 *	
 ****************************************************************************/
//抽選表番号に対応する、抽選表へのポインタを取得する。
static const unsigned short* LotRom_GetLotTbl(int LotTblNo) {
	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
	const unsigned short* pLotTbl;
	if((LotTblNo < 1) || (LotTblNo >= nLotTblNo)) { DIE(); }			//抽選表番号が有効範囲である事を確認する。
	pLotTbl = (const unsigned short*)&stLotRom.m_TBL_LotTblNo[nLotTblNo];		//抽選表データへのポインタを求める。(抽選表アドレステーブルの直後に抽選表データを配置する前提)
	return &pLotTbl[stLotRom.m_TBL_LotTblNo[LotTblNo]];				//抽選表へのポインタを返す。
}
/*--------------------------------------------------------------------------*/
void LotRom_Init(const int* _TBL_LotTblNo/*[LotTblNo_Max+1]*/, int (*_fnRnd)(/*void* fnRndArg*/), void* _fnRndArg) {
	LotRom_Free();									//既に、抽選表ROM管理が開始していたら、一旦、抽選表ROM管理を終了する。
	stLotRom.m_TBL_LotTblNo	= _TBL_LotTblNo;					//抽選表アドレステーブルへのポインタを格納する。
	stLotRom.m_fnRnd	= _fnRnd;						//乱数ジェネレータ関数へのポインタを格納する。
	stLotRom.m_fnRndArg	= _fnRndArg;						//乱数ジェネレータ関数への引数を格納する。
}
/*--------------------------------------------------------------------------*/
//抽選表ROM管理を終了する。
void LotRom_Free() {
#ifdef  USE_LOTROM_LOG
	LotRom_EndLog();								//確実に、ログを終了する。
#endif//USE_LOTROM_LOG
	memset(&stLotRom, 0, sizeof stLotRom);						//抽選表ROM管理をクリアする。
}
/*--------------------------------------------------------------------------*/
//抽選表の、指定された抽選列を用いて、抽選を行います。
int LotRom_draw_v(int LotTblNo, const int* pid) {
	int retval = LOT_draw_table_v(LotRom_GetLotTbl(LotTblNo), pid, (unsigned short)(*stLotRom.m_fnRnd)(stLotRom.m_fnRndArg));	//乱数ジェネレータ関数は32bit乱数を返しても良い。当関数が16bit乱数に切り詰める。
#ifdef  USE_LOTROM_LOG
	LotRom_AddLog(LotTblNo, pid, retval);						//抽選表ROM管理が開始していたら、抽選結果をログに記録する。
#endif//USE_LOTROM_LOG
	return retval;
}
int LotRom_draw_a(int LotTblNo, va_list ap) {
	int id[(7+1)/*調整可*/], i = 0;							//実用上、抽選列のid行数が7以下(終端含め8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((id[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(id)) { DIE(); }					//ここで停止した場合は、id[]不足か,又は,呼出し側が終端を忘れている。おそらく後者だろう。
	}
	return LotRom_draw_v(LotTblNo, id);						//ログはこの中で行う。
}
int LotRom_draw_l(int LotTblNo, ...) {
	int retval;
	va_list ap;
	va_start(ap, LotTblNo);
	retval = LotRom_draw_a(LotTblNo, ap);						//ログはこの中で行う。
	va_end(ap);
	return retval;
}
/*--------------------------------------------------------------------------*/
//抽選表の、指定された抽選列の、指定された当選値の、重み値を取得します。
int LotRom_get_weight_v(int LotTblNo, const int* pid, int hitval_sel) {
	return LOT_get_table_weight_v(LotRom_GetLotTbl(LotTblNo), pid, hitval_sel);
}
int LotRom_get_weight_a(int LotTblNo, va_list ap, int hitval_sel) {
	return LOT_get_table_weight_a(LotRom_GetLotTbl(LotTblNo), ap, hitval_sel);
}
int LotRom_get_weight_l(int LotTblNo, int hitval_sel, ...) {
	int retval;
	va_list ap;
	va_start(ap, hitval_sel);
	retval = LotRom_get_weight_a(LotTblNo, ap, hitval_sel);
	va_end(ap);
	return retval;
}
/*--------------------------------------------------------------------------*/
//抽選表の、列数を取得します。
int LotRom_get_size(int LotTblNo) {
	return LOT_get_table_size(LotRom_GetLotTbl(LotTblNo));
}
/*--------------------------------------------------------------------------*/
//抽選表の、抽選列のid行数を取得します。
int LotRom_get_id_size(int LotTblNo) {
	return LOT_get_table_id_size(LotRom_GetLotTbl(LotTblNo));
}
/*--------------------------------------------------------------------------*/
//抽選表の、(i_column)番目の抽選列の、idの組合せを取得します。
void LotRom_get_column_id(int LotTblNo, int i_column, int* pid) {
	LOT_get_table_column_id(LotRom_GetLotTbl(LotTblNo), i_column, pid);
}
/****************************************************************************
 *	ログ機能
 ****************************************************************************/
#ifdef  USE_LOTROM_LOG
//抽選表ログ
typedef struct _ST_LotRom_TblLog {
	struct _ST_LotRom_ColLog*	TBL_ColLog/*[抽選表の列数]*/;			//抽選列ログ
} ST_LotRom_TblLog;
//抽選列ログ
typedef struct _ST_LotRom_ColLog {
	unsigned			    LotCnt;					//抽選回数
	unsigned*			TBL_HitCnt/*[抽選列の要素数]*/;			//当選回数	(LotCnt-SUM(TBL_HitCnt))=非当選回数
} ST_LotRom_ColLog;
/*--------------------------------------------------------------------------*/
//ログを開始する。
void LotRom_BeginLog() {
	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
	int LotTblNo, table_size, id_size, i_column, column_size;
	LotRom_EndLog();								//既に、ログが開始していたら、一旦、ログを終了する。
	stLotRom.m_TBL_TblLog = calloc(nLotTblNo, sizeof(ST_LotRom_TblLog*));		//抽選表ログのメモリを確保する。
	if(!stLotRom.m_TBL_TblLog) { DIE(); }						//メモリ不足
	for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
		table_size = *table++;							//抽選表の列数
		id_size    = *table++;							//抽選列のid行数
		pTblLog->TBL_ColLog = calloc(table_size, sizeof(ST_LotRom_ColLog));	//抽選列ログのメモリを確保する。
		if(!pTblLog->TBL_ColLog) { DIE(); }					//メモリ不足
		for(i_column = 0; i_column < table_size; i_column++) {
			ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
			table += id_size;						//抽選列のid
			column_size = *table++;						//抽選列の要素数
			table += column_size * 2/*{hitval,weight}*/;			//抽選列の要素
			pColLog->TBL_HitCnt = calloc(column_size, sizeof(unsigned));	//当選回数のメモリを確保する。
			if(!pColLog->TBL_HitCnt) { DIE(); }				//メモリ不足
		}
	}
}
/*--------------------------------------------------------------------------*/
//ログを出力する。
void LotRom_DumpLog() {
	const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];				//(抽選表番号の最大値+1)を取得する。
	int LotTblNo, table_size, id_size, i_column, column_size, i;
	if(!stLotRom.m_TBL_TblLog) { DIE(); }						//ログが開始していない時に、当関数を呼び出してはいけない。
	for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
		table_size = *table++;							//抽選表の列数
		id_size    = *table++;							//抽選列のid行数
		for(i_column = 0; i_column < table_size; i_column++) {
			ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
			table += id_size;						//抽選列のid
			column_size = *table++;						//抽選列の要素数
			printf("%d", pColLog->LotCnt);					//出力:抽選回数
			for(i = 0; i < column_size; i++) {
				printf("\t%d\t%d", *table, pColLog->TBL_HitCnt[i]);	//出力:(TAB)当選値(TAB)当選回数
				table += 2/*{hitval,weight}*/;				//要素を飛ばす。
			}
			printf("\n");							//出力:抽選列の改行
		}
		if(LotTblNo != (nLotTblNo - 1)) { printf("\n"); }			//出力:抽選表と抽選表の間の空行
	}
}
/*--------------------------------------------------------------------------*/
//ログを終了する。
void LotRom_EndLog() {
	if(stLotRom.m_TBL_TblLog) {		//ログが開始していたら…							//┐この順序を逆にしないよう注意せよ。逆にすると、最初にLotRom_Init()⇒LotRom_Free()⇒LotRom_EndLog()の
		const int nLotTblNo = stLotRom.m_TBL_LotTblNo[0];			//(抽選表番号の最大値+1)を取得する。	//┘経路で呼び出された時に、stLotRom.m_TBL_LotTblNoがまだNULLであるため、一般保護例外が発生してしまう。
		int LotTblNo, table_size, id_size, i_column, column_size;
		for(LotTblNo = 1; LotTblNo < nLotTblNo; LotTblNo++) {
			ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];
			const unsigned short* table = LotRom_GetLotTbl(LotTblNo);
			table_size = *table++;						//抽選表の列数
			id_size    = *table++;						//抽選列のid行数
			for(i_column = 0; i_column < table_size; i_column++) {
				ST_LotRom_ColLog* pColLog = &pTblLog->TBL_ColLog[i_column];
				table += id_size;					//抽選列のid
				column_size = *table++;					//抽選列の要素数
				table += column_size * 2/*{hitval,weight}*/;		//抽選列の要素
				free(pColLog->TBL_HitCnt);				//当選回数のメモリを開放する。
			}
			free(pTblLog->TBL_ColLog);					//抽選列ログのメモリを開放する。
		}
		free(stLotRom.m_TBL_TblLog);						//抽選表ログのメモリを開放する。
		stLotRom.m_TBL_TblLog = NULL;						//ログが開始していない事を示す。
	}
}
/*--------------------------------------------------------------------------*/
//抽選結果をログに記録する。
static void LotRom_AddLog(int LotTblNo, const int* pid, int hitval) {
	if(stLotRom.m_TBL_TblLog) {		//ログが開始していたら…
		ST_LotRom_TblLog* pTblLog = &stLotRom.m_TBL_TblLog[LotTblNo];		//抽選表ログへのポインタを取得する。
		ST_LotRom_ColLog* pColLog = pTblLog->TBL_ColLog;			//最初の列の抽選列ログへのポインタを取得する。
		const unsigned short* table = LotRom_GetLotTbl(LotTblNo);		//抽選表番号に対応する、抽選表へのポインタを取得する。
	    //{{この範囲はcliplot.cのLOT_seek_and_call()とほぼ同じアルゴリズムです。処理内容のコメントはLOT_seek_and_call()を参照してください。
		int table_size, id_size, column_size, n_id, diff;
		table_size = *table++;							//抽選表の列数
		id_size    = *table++;							//抽選列のid行数
		for(;;) {
			if(--table_size < 0) { DIE(); }					//指定されたidの組合せを持つ抽選列が見つからない。	LOT_draw_table_v()が成功したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
			n_id = id_size;							//抽選列のidを比較する。
			diff = 0;
			while(--n_id >= 0) { diff |= (*table++ - *pid++); }
			column_size = *table++;						//抽選列の要素数
			if(!diff) { break; }						//指定されたidの組合せを持つ抽選列が見つかったら、抜ける。
			pid -= id_size;							//指定されたidの組合せのポインタを戻す。
			table += column_size * 2/*{hitval,weight}*/;			//抽選列を飛ばす。
			pColLog++;							//抽選列ログへのポインタを次の列へ進める。
		}
		if(*pid != -1) { DIE(); }						//指定された抽選列のidの組合せの、終端を確認する。	LOT_draw_table_v()が成功したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
	    //}}この範囲はcliplot.cのLOT_seek_and_call()とほぼ同じアルゴリズムです。処理内容のコメントはLOT_seek_and_call()を参照してください。
		pColLog->LotCnt++;							//抽選回数を増やす。
		if(hitval != -1) {							//非当選でなければ…
			unsigned* pHitCnt = pColLog->TBL_HitCnt;			//最初の要素の当選回数へのポインタを取得する。
			for(;;) {
				if(--column_size < 0) { DIE(); }			//指定された当選値を持つ要素が見つからない。		LOT_draw_table_v()が当選したのにここで失敗する事は無いはず。もしここで停止したらバグです。(データバグではなく当モジュールのPGバグである可能性が高い)
				if(*table == hitval) { break; }				//指定された当選値を持つ要素が見つかったら、抜ける。
				table += 2/*{hitval,weight}*/;				//要素を飛ばす。
				pHitCnt++;						//当選回数へのポインタを次の要素へ進める。
			}
			(*pHitCnt)++;							//当選回数を増やす。
		}
	}
}
#endif//USE_LOTROM_LOG
