//
//	clipcntm.cs
//
//	汎用カウンタ管理
//
//	CLiP - Common Library for P/ECE
//	Copyright (C) 2017 Naoyuki Sawa
//
//	* Wed Mar 08 22:12:10 JST 2017 Naoyuki Sawa
//	- 1st リリース。
//
using System;
using System.Collections.Generic;
namespace org.piece_me {
	public static partial class libclip {
		//*****************************************************************************
		//	構造体
		//*****************************************************************************
		//汎用カウンタ管理チャネル構造体
		public class ST_CntMgrCh {
			public int	ValNow;		//現在値	INT_MIN～INT_MAX
			public int	ValTo;		//目標値	INT_MIN～INT_MAX
			public int	Rate;		//変化速度	0,1～INT_MAX		CntMgr_Exec()毎に現在値が(Rate/Scale)の速度で変化する。
			public int	Scale;		//変化速度	0,1～INT_MAX		CntMgr_Exec()毎に現在値が(Rate/Scale)の速度で変化する。
			public int	ErrTerm;	//誤差項	0～(Scale-1)
		}
		//-----------------------------------------------------------------------------
		//汎用カウンタ管理構造体
		public class ST_CntMgr {
			public int		nCntMgrCh { get { return TBL_CntMgrCh.Length; } }	//汎用カウンタ管理チャネル数
			public ST_CntMgrCh[]	TBL_CntMgrCh;						//汎用カウンタ管理チャネル構造体配列
		}
		//*****************************************************************************
		//	ローカル関数
		//*****************************************************************************
		//汎用カウンタ管理チャネル構造体を取得する。
		private static ST_CntMgrCh CntMgr_GetCntMgrCh(ST_CntMgr pCntMgr, int iCntMgrCh) {
			if((uint)iCntMgrCh >= (uint)pCntMgr.nCntMgrCh) { throw new ApplicationException(); }	//チャネル番号が範囲外
			return pCntMgr.TBL_CntMgrCh[iCntMgrCh];
		}
		//*****************************************************************************
		//	アプリケーション用関数
		//*****************************************************************************
		public static ST_CntMgr CntMgr_New(int nCntMgrCh) {
			ST_CntMgr pCntMgr;
			//汎用カウンタ管理構造体のメモリを確保する。
			pCntMgr = new ST_CntMgr();
			//汎用カウンタ管理構造体を初期化する。
			CntMgr_Init(pCntMgr, nCntMgrCh);
			return pCntMgr;
		}
		//-----------------------------------------------------------------------------
		public static void CntMgr_Init(ST_CntMgr pCntMgr, int nCntMgrCh) {
			//汎用カウンタ管理チャネル構造体配列を作成する。
			pCntMgr.TBL_CntMgrCh = new ST_CntMgrCh[nCntMgrCh];
			for(int iCntMgrCh = 0; iCntMgrCh < nCntMgrCh; iCntMgrCh++) {
				pCntMgr.TBL_CntMgrCh[iCntMgrCh] = new ST_CntMgrCh();
			}
		}
		//-----------------------------------------------------------------------------
		public static void CntMgr_Exec(ST_CntMgr pCntMgr) {
			int iCntMgrCh;
			for(iCntMgrCh = 0; iCntMgrCh < pCntMgr.nCntMgrCh; iCntMgrCh++) {
				//汎用カウンタ管理チャネル構造体を取得する。
				ST_CntMgrCh pCntMgrCh = CntMgr_GetCntMgrCh(pCntMgr, iCntMgrCh);
				//現在値と目標値を取り出しておく。
				int ValNow = pCntMgrCh.ValNow;	//現在値
				int ValTo  = pCntMgrCh.ValTo;	//目標値
				//現在値と目標値が違うならば(=変化中ならば)…
				if(ValNow != ValTo) {
					//誤差項に変化速度を加算し、今回変化量を求める。
					uint ErrTerm = (uint)(pCntMgrCh.ErrTerm + pCntMgrCh.Rate);	//pCntMgrCh.ErrTerm=0～(INT_MAX  -1) ＋ pCntMgrCh.Rate =1～INT_MAX ⇒ ErrTerm=1～(INT_MAX*2-1)
					uint quot    = ErrTerm / (uint)pCntMgrCh.Scale;			//ErrTerm          =1～(INT_MAX*2-1) ÷ pCntMgrCh.Scale=1～INT_MAX ⇒ quot   =0～(INT_MAX*2-1)
					uint rem     = ErrTerm % (uint)pCntMgrCh.Scale;			//ErrTerm          =1～(INT_MAX*2-1) ％ pCntMgrCh.Scale=1～INT_MAX ⇒ rem    =0～(INT_MAX  -1)
					//今回変化量を除いた残りを、誤差項に書き戻す。
					pCntMgrCh.ErrTerm = (int)rem;					//pCntMgrCh.ErrTerm=0～(INT_MAX  -1)
					//変化方向が増加の場合…														//┐
					if(ValNow < ValTo) {															//│
						uint diff = (uint)(ValTo - ValNow);			//0～UINT_MAX	//残り変化量を求める。					//│
						ValNow += (int)((quot < diff) ? quot : diff);		//今回変化量,又は,残り変化量のうち、小さい方で現在値を増加する。	//│
					//変化方向が減少の場合…														//├もしValToにINT_MIN,又は,INT_MAX付近の値が設定されても、この方法ならばオーバーフローせずに正しく処理出来ます。
					} else {																//│
						uint diff = (uint)(ValNow - ValTo);			//0～UINT_MAX	//残り変化量を求める。					//│
						ValNow -= (int)((quot < diff) ? quot : diff);		//今回変化量,又は,残り変化量のうち、小さい方で現在値を減少する。	//│
					}																	//┘
					//現在値を書き戻す。
					pCntMgrCh.ValNow = ValNow;
				}
			}

		}
		//-----------------------------------------------------------------------------
		public static void CntMgr_SetVal(ST_CntMgr pCntMgr, int iCntMgrCh, int ValTo, int Rate, int Scale) {
			ST_CntMgrCh pCntMgrCh;
			if((Rate < 0) || (Scale < 0)) { throw new ApplicationException(); }	//引数が不正
			//汎用カウンタ管理チャネル構造体を取得する。
			pCntMgrCh = CntMgr_GetCntMgrCh(pCntMgr, iCntMgrCh);
			//前の変化の変化中ならば、即完了させる。
			// - 前の変化が既に完了していたら、既に(ValNow==ValTo)になっているから、この処理はダミー処理となる。
			pCntMgrCh.ValNow  = pCntMgrCh.ValTo;	//現在値
			//新しい変化のパラメータを格納する。
			pCntMgrCh.ValTo   = ValTo;		//目標値
			pCntMgrCh.Rate    = Rate;		//変化速度
			pCntMgrCh.Scale   = Scale;		//変化速度
			pCntMgrCh.ErrTerm = 0;			//誤差項
			//変化速度のパラメータが片方でも0(=即反映)ならば、目標値を現在値へ即反映する。
			if((Rate == 0) || (Scale == 0)) { pCntMgrCh.ValNow = ValTo; }
		}
		//-----------------------------------------------------------------------------
		public static int CntMgr_GetVal(ST_CntMgr pCntMgr, int iCntMgrCh) {
			ST_CntMgrCh pCntMgrCh;
			//汎用カウンタ管理チャネル構造体を取得する。
			pCntMgrCh = CntMgr_GetCntMgrCh(pCntMgr, iCntMgrCh);
			//現在値を返す。
			return pCntMgrCh.ValNow;
		}
		//-----------------------------------------------------------------------------
		public static int[] CntMgr_GetValAll(ST_CntMgr pCntMgr) {	//C言語版では呼び出し側が指定した配列に現在値を格納していましたが、C#版では当関数が配列を確保して現在値を格納するように変更しました。
			int[] ValNow = new int[pCntMgr.nCntMgrCh];
			int iCntMgrCh;
			//各汎用カウンタ管理チャネルについて…
			for(iCntMgrCh = 0; iCntMgrCh < pCntMgr.nCntMgrCh; iCntMgrCh++) {
				//汎用カウンタ管理チャネル構造体を取得する。
				ST_CntMgrCh pCntMgrCh = CntMgr_GetCntMgrCh(pCntMgr, iCntMgrCh);
				//現在値を取得する。
				ValNow[iCntMgrCh] = pCntMgrCh.ValNow;
			}
			return ValNow;
		}
		//-----------------------------------------------------------------------------
		public static double CntMgr_GetErrTerm(ST_CntMgr pCntMgr, int iCntMgrCh) {
			double dErrTerm = 0.0;	//変化中でなかった場合のために、戻り値を0.0としておく。	//0.0
			//汎用カウンタ管理チャネル構造体を取得する。
			ST_CntMgrCh pCntMgrCh = CntMgr_GetCntMgrCh(pCntMgr, iCntMgrCh);
			//変化中ならば…
			if(pCntMgrCh.ValNow != pCntMgrCh.ValTo) {
				dErrTerm = (double)pCntMgrCh.ErrTerm / (double)pCntMgrCh.Scale;		//0.0⇒ (1.0-ε)
				//変化方向が減少ならば…
				if(pCntMgrCh.ValNow > pCntMgrCh.ValTo) {
					//戻り値をマイナスにする。
					dErrTerm = -dErrTerm;						//0.0⇒-(1.0-ε)
				}
			}
			return dErrTerm;
		}
	}
}
//*****************************************************************************
//	使用例
//*****************************************************************************
#if false
using System;
using System.Threading;
using static org.piece_me.libclip;
class Program {
 //汎用ｶｳﾝﾀ管理ﾁｬﾈﾙ定義
 const int CntMgrCh_MdGet=0;//獲得ﾒﾀﾞﾙ数
 const int CntMgrCh_GmLft=1;//残りｹﾞｰﾑ数
 const int CntMgrCh_GmCnt=2;//消化ｹﾞｰﾑ数
 const int CntMgrCh_SIZE =3;
 static ST_CntMgr pCntMgr;//汎用ｶｳﾝﾀ管理
 static int[] TBL_Val = new int[CntMgrCh_SIZE];//内部値
 static void Main() {//ﾃｽﾄ関数
  pCntMgr = CntMgr_New(CntMgrCh_SIZE);//汎用ｶｳﾝﾀ管理を初期化
  CntMgr_SetVal(pCntMgr, CntMgrCh_GmLft, TBL_Val[CntMgrCh_GmLft] = 50, 0, 0);//残りｹﾞｰﾑ数(即反映)
  for(;;) {
   ConsoleKey key = default(ConsoleKey);
   if(Console.KeyAvailable) { key = Console.ReadKey(true).Key; }
   if(key == ConsoleKey.A) {//遊技開始想定
    CntMgr_SetVal(pCntMgr, CntMgrCh_MdGet, Math.Min(Math.Max(0, TBL_Val[CntMgrCh_MdGet] -= 3), 999), 1, 2);//獲得ﾒﾀﾞﾙ数(1/2[f]の頻度で変化)
    CntMgr_SetVal(pCntMgr, CntMgrCh_GmLft, Math.Min(Math.Max(0, TBL_Val[CntMgrCh_GmLft]--), 99), 1, 2);//残りｹﾞｰﾑ数(1/2[f]の頻度で変化)
    CntMgr_SetVal(pCntMgr, CntMgrCh_GmCnt, Math.Min(Math.Max(0, TBL_Val[CntMgrCh_GmCnt]++), 99), 1, 2);//消化ｹﾞｰﾑ数(1/2[f]の頻度で変化)
   }
   if(key == ConsoleKey.B) {//小役入賞想定
    CntMgr_SetVal(pCntMgr, CntMgrCh_MdGet, Math.Min(Math.Max(0, TBL_Val[CntMgrCh_MdGet] += 15), 999), 1, 2);//獲得ﾒﾀﾞﾙ数(1/2[f]の頻度で変化)
   }
   CntMgr_Exec(pCntMgr);//汎用ｶｳﾝﾀ管理の周期処理
   int[/*CntMgrCh_SIZE*/] ValNow = CntMgr_GetValAll(pCntMgr);//汎用ｶｳﾝﾀ管理の現在値を取得
   Console.WriteLine("| 獲得ﾒﾀﾞﾙ数 内部値 {0,6} 表示値 {1,6} | 残りｹﾞｰﾑ数 内部値 {2,6} 表示値 {3,6} | 消化ｹﾞｰﾑ数 内部値 {4,6} 表示値 {5,6} |",
                     TBL_Val[CntMgrCh_MdGet], ValNow[CntMgrCh_MdGet], TBL_Val[CntMgrCh_GmLft], ValNow[CntMgrCh_GmLft], TBL_Val[CntMgrCh_GmCnt], ValNow[CntMgrCh_GmCnt]);
   Thread.Sleep(100);
  }
 }
}
#endif
