module yamalib.counterfsp;

private import y4d_draw.drawbase;
private import y4d_math.sintable;

private import yamalib.math.spline;

/**
　ステップレートを設定できるCounterパッケージ。
　このパッケージに記述されているクラスはすべて、レートによってステップ比率が操作される。
*/
abstract class ICounter {
	
	/// すべてのステップ数を操作するための比率
	static void setStepRate( int n_ ) {
		if ( 0 == n_ ) {
			n_ = 1;
		}
		STEP_RATE = n_;
	}
	
	void	set(int start_,int end_,int step_);
	void	setStep(int step_);
	void	setStart(int start_);
	void	setEnd(int end_);

	//	取得
	int		getStep();
	int		getStart();
	int		getEnd();
	
	bool	isEnd() { return false; }

	void	reset();
	int		get ();
	int		getSined();
	
	void	inc();

protected:
	static int STEP_RATE = 3;
}

/// ゲームで使うカウンタ
class RootCounter : ICounter {
public:
	//	設定
	void	set(int start_,int end_,int step_) {
		setStep( step_ );
		start	= start_;
		end		= end_;
	}

	void	setStep(int step_) {
		step = step_ > 0 ? step_ * STEP_RATE : step_ / STEP_RATE;
	}

	void	setStart(int start_) {
		start = start_;
	}

	void	setEnd(int end_) {
		end = end_;
	}

	//	取得
	int		getStep() { return step; }
	int		getStart() { return start; }
	int		getEnd() { return end; }
	////	オートリバースカウンタ
	void	setReverse(bool b){ reverse = b;}	//	リバースカウンタにする
	bool	getReversing(){ return reversing; }		//	反転カウント中か？
	////	初期値
	void	setInit(int n) { init = true; initNum = n; }

	//	カウンタのリセット
	void	reset() {
		rate			= 0;
		lapAround	= false;
		lapAroundI	= false;
		reversing	= false;
		if (!init) {
			rootCount	= start;
		} else {
			rootCount	= initNum;
		}
	}

	//	カウンタのインクリメント(終端まで達すると、再度、初期値に戻る)
	void	inc() {
		if (reverse){
			if (!reversing) {
				if (step>0) {
					rootCount+=step;
					if (rootCount>=end) {
						rootCount = end;
						lapAround = true;
						lapAroundI = false;
						reversing = true;	//	ダウンカウントの開始
					} else {
						lapAround = false;
						lapAroundI = false;
					}
				} else {
					rate++;
					if (rate==-step) {
						rate = 0;
						rootCount++;
						if (rootCount>=end) {
							rootCount = start;
							lapAround = true;
							lapAroundI = false;
							reversing = true;	//	ダウンカウントの開始
						} else {
							lapAround = false;
							lapAroundI = false;
						}
					}
				}
			} else {
				if (step>0) {
					rootCount-=step;
					if (rootCount<=start) {
						rootCount = start;
						lapAround = true;
						lapAroundI = true;
						reversing = false;	//	アップカウントの開始
					} else {
						lapAround = false;
						lapAroundI = false;
					}
				} else {
					rate++;
					if (rate==-step) {
						rate = 0;
						rootCount--;	//	ダウンカウント
						if (rootCount<=start) {
							rootCount = start;
							lapAround = true;
							lapAroundI = true;	//	この時に限りtrue
							reversing = false;	//	アップカウントの開始
						} else {
							lapAround = false;
							lapAround = false;
						}
					}
				}
			}
		} else {
		//	リバースカウンタでないならば、リバース状態であるかに関わらず前方加算
			if (step>0) {
				rootCount+=step;
				if (rootCount>=end) {
					rootCount = start;
					lapAround = true;
				} else {
					lapAround = false;
				}
			} else {
				rate++;
				if (rate==-step) {
					rate = 0;
					rootCount++;
					if (rootCount>=end) {
						rootCount = start;
						lapAround = true;
					} else {
						lapAround = false;
					}
				}
			}
		}
	}//	加算

	RootCounter opPostInc () { inc(); return this; }
	RootCounter opPostInc(int n) { RootCounter _Tmp = this; inc(); return (_Tmp); }

	//	カウンタのサチュレーションインクリメント（終端まで達すると、そこで停止する）
	void	incS() {
	//	if (m_bReverse){
		//	↑リバースカウンタかどうかにかかわらず、
		//	　　　↓これを見て判断すべき
			if (!reversing) {
				if (step>0) {
					rootCount+=step;
					if (rootCount>=end) {
						rootCount = end;
						lapAround = true;
					//	m_bReversing = true;	//	ダウンカウントの開始
					} else {
						lapAround = false;
					}
				} else {
					rate++;
					if (rate==-step) {
						rate = 0;
						rootCount++;
						if (rootCount>=end) {
							rootCount = end;
							lapAround = true;
						//	m_bReversing = true;	//	ダウンカウントの開始
						} else {
							lapAround = false;
						}
					}
				}
			} else {
				if (step>0) {
					rootCount-=step;
					if (rootCount<=start) {
						rootCount = start;
						lapAround = true;
					//	m_bReversing = false;	//	アップカウントの開始
					} else {
						lapAround = false;
					}
				} else {
					rate++;
					if (rate==-step) {
						rate = 0;
						rootCount--;	//	ダウンカウント
						if (rootCount<=start) {
							rootCount = start;
							lapAround = true;
					//		m_bReversing = false;	//	アップカウントの開始
						} else {
							lapAround = false;
						}
					}
				}
			}
	//	}
	}

	//	intとの相互変換
	int opCall() { return rootCount; }
	int opAssign(int n) { rootCount = n; return n; }
	int		get () { return rootCount; }
	int getSined() { 
		int w = end - start;
		int offset = rootCount - w;
		return start + sin.sin( cast(int) ((cast(float)w)/offset*128.0f), offset ); 
	}

	//	Incした結果、周回したか？／IncSした結果、終値になったか？
	bool	isLapAround() { return lapAround; }
	//	Incした結果、初期値に戻ったか？
	bool	isLapAroundI() { return lapAroundI; }

	this() {
		set(0,int.max,1);
		reset();
	}

	this(int end_) {
		set(0,end_,1);
		reset();
	}

	this(int start_,int end_,int step_) {
		set(start_,end_,step_);
		reset();
	}

	static this() {
		sin = SinTable.get();
	}
	
protected:
	static SinTable sin;

	int		rootCount;
	int		start;
	int		end;
	int		step;
	int		rate;	//	nStep<0のときは、ｎ回のInc()で+1される
	bool	lapAround;
	bool	lapAroundI;

	bool	reverse;		//	リバースカウンタ
	bool	reversing;		//	リバース中か？
	bool	init;			//	初期値が設定されているか？
	int		initNum;		//	設定されている初期値
}

//	こちらは、nStart≦nEndでなくて良い
class RootCounterS : ICounter {
public:
	//	nStepは一回の増分の絶対値。マイナスは1/nStepの意味
	void	set(int start_,int end_,int step_) { 
		start=start_; 
		end=end_; 
		setStep( step_ );
		reset(); 
	}
	void	setStep(int step_) { 
		step = step_ > 0 ? step_ * STEP_RATE : step_ / STEP_RATE;
	}
	void	setStart(int start_) { start = start_; }
	void	setEnd(int end_) { end = end_; }

	//	取得
	int		getStep()  { return step; }
	int		getStart()  { return start; }
	int		getEnd()  { return end; }

	//	カウンタのリセット
	void	reset() { rootCount= start; rate=0; }

	//	property..
	bool	isEnd()  { 
		return cast(bool) (rootCount == end); 
	}

	this() {
		set(0,int.max,1);
		reset();
	}

	this(int end_) {
		set(0,end_,1);
		reset();
	}

	this(int start_,int end_,int step_) {
		set(start_,end_,step_);
		reset();
	}

	//	intとの相互変換
//	operator int (void) { return m_nRootCount; }
	int opAssign(int n) { rootCount = n; return n; }
	int		get () { return rootCount; }

	int getSined() { 
		int w = end - start;
		int offset = rootCount - start;
		float f = cast(float) offset / cast(float) w;
		offset = sin.sin( cast(int) (f*128.0f), offset );
		return start + sin.sin( cast(int) (f*128.0f), offset ); 
	}

	//	カウンタのインクリメント(終端まで達すると、そこで停止する)
	//	加算（＝End方向へインクリメント）／減算（＝Start方向へのインクリメント）
	void	inc(bool add) {
		bool bInc = cast(bool)((start > end) ^ add); // 逆方向カウンタ？
		if (bInc) {
		//	インクリメント
			if (step>0) {
			//	整数インクリメント
				rootCount += step;
			} else {
			//	分数インクリメント
				rate++; if (rate>=(-step)) { rate = 0; rootCount++; }
			}
			//	サチュレートしたのか？
			int max = start < end ? end : start;
			if (rootCount > max) rootCount = max;
		} else {
		//	デクリメント
			if (step>0) {
			//	整数デクリメント
				rootCount -= step;
			} else {
			//	分数デクリメント
				rate--; if (rate<=(step)) { rate = 0; rootCount--; }
				//	⇒　rate++でないことに注意。
				//	++のあと--して、数の整合性がとれなくてはならない
				//	かつ、nStep<0のとき最初の１回目の--でRootCounterが
				//	1減ってはいけない。よってこういう実装になる
			}
			//	サチュレートしたのか？
			int min = start < end ? start : end;
			if (rootCount < min) rootCount = min;
		}
	}
	
	// intarface 互換
	void inc() {
		inc(true);
	}

	RootCounterS opPostInc() { inc(true); return this; }
	RootCounterS opPostInc(int n) { RootCounterS _Tmp = this; inc(true); return (_Tmp); }
	RootCounterS opPostDec() { inc(false); return this; }
	RootCounterS opPostDec(int n) { RootCounterS _Tmp = this; inc(false); return (_Tmp); }

	static this() {
		sin = SinTable.get();
	}

protected:
	static SinTable sin;

	int		rootCount;
	int		start;
	int		end;
	int		step;
	int		rate;	//	nStep<0のときは、ｎ回のInc()で+1される
};

/// 内分カウンタ
class InteriorCounter : ICounter {

	this() {
	}

	this(int start_,int end_,int frames_) {
		this();
		set(start_,end_,frames_);
	}

	/// 加算
	void	inc() {

		// errorやん...
		if (frames==0) return;

		if (framesNow >= frames) {
			now = end;
			return ;
		}
		framesNow++;
		//	内分処理
		now =  start + framesNow * (end-start) / frames;
	}

	/// 減算
	void	dec() {

		// errorやん...
		if (frames==0) return;

		//	カウンタは初期値か？
		if (framesNow == 0) return ;
		framesNow--;
		//	内分処理
		now =  start + framesNow * (end-start) / frames;
	}

	/// 設定
	void	set(int start_,int end_,int frames_) {
		start = start_;
		end = end_;
		frames = frames_ / STEP_RATE;
		framesNow = 0;
	}

	/// 現在の値の設定
	void	set(int now_) { now = now_; }
	
	/// 現在のフレームの設定
	void	setFlame(int now_) {
		if ( frames >= now_ ) {
			framesNow = now_;
		}
	}

	/// 現在の値の取得
	int get() { return now; }

	int getSined() { 
		float temp1 = (cast(float) (now-start));
		if (temp1 == 0) {
			return 0;
		}
		
		float nowRad = temp1 / (cast(float) (end-start)); 
		return sin.sin( cast(int) (nowRad * 128.0f), now ); 
	}

	/// 終値の取得
	int		getEnd(){
		return end;
	}

	/// 終了したか？
	bool	isEnd() {
		return cast(bool) (now == end);
	}
	
	// interface互換
	void setStep(int n) { 
		frames = n / STEP_RATE;
	}
	void setStart(int n) {}
	void setEnd(int n) {}
	int getStep() { 
		return frames * STEP_RATE;
	}
	int getStart() { return 0; }
	void reset() {
		framesNow = 0;
	}

	/// 演算子のオーバーロード
	InteriorCounter opPostInc() { inc(); return this; }
	InteriorCounter opPostInc(int n) { InteriorCounter _Tmp = this; inc(); return (_Tmp); }
	InteriorCounter opPostDec() { inc(); return this; }
	InteriorCounter opPostDec(int n) { InteriorCounter _Tmp = this; inc(); return (_Tmp); }

	static this() {
		sin = SinTable.get();
	}

private:
	static SinTable sin;

	int		now;			//	現在の値
	int		start;		//	初期値
	int		end;			//	終了値
	int		frames;		//	フレーム分割数（終了値になるまで何回Incをすればいいのか）
	int		framesNow;	//	現在、何フレーム目か？
};


/// ユーザ定義の自由曲線カウンタ
class FreeLineCounter : ICounter {
	
	void	set(int start_,int end_,int step_) {
		counter.set(start_, end_, step_);
		createPoints();
	}
	
	void	setStep(int step_) {
		counter.setStep(step_);
	}
	void	setStart(int start_) {
		counter.setStart(start_);
	}
	void	setEnd(int end_) {
		counter.setEnd(end_);
	}
	
	bool isEnd() {
		return counter.isEnd();
	}

	//	取得
	int		getStep() {
		return counter.getStep();
	}
	int		getStart() {
		return counter.getStart();
	}
	
	int		getEnd() {
		return counter.getEnd();
	}

	void	reset() {
		counter.reset();
	}
	
	int		get () {
		int index = counter.get();
		int st = counter.getStart();
		int ed = counter.getEnd();
		int offset = st > ed ? ed : st;

		index -= offset;
		if (index >= ptSpline.length) {
			index = ptSpline.length - 1;
		}
		return offset + getRound( ptSpline[index].y );
	}
	
	int		getSined() {
		return 0;
	}
	
	void	inc() {
		counter.inc();
	}
	
	///
	void setSamplePt(float[5] x_, float[5] y_) {
		sx = null;
		sy = null;

		for (int i = 0; i < LC_S_PRE_POINT.length; ++i) {
			sx ~= LC_S_PRE_POINT[i][0];
			sy ~= LC_S_PRE_POINT[i][1];
		}

		sx ~= x_;
		sy ~= y_;

		for (int i = 0; i < LC_S_POST_POINT.length; ++i) {
			sx ~= LC_S_POST_POINT[i][0];
			sy ~= LC_S_POST_POINT[i][1];
		}
	}
	
	/// 位置を生成する
	void createPoints() {
		int st = counter.getStart();
		int ed = counter.getEnd();
		int dataNum = ed - st;

		if (dataNum < 0) {
			dataNum = -dataNum;
			int tmp = st;
			st = ed;
			ed = tmp;
		}

		PointInt[] pts;		
		for (int i = 0; i < sx.length; ++i) {
			PointInt pt;
			pt.x = cast(int) (dataNum * sx[i]);
			pt.y = cast(int) (dataNum * sy[i]);
			
			pts ~= pt;
		}
		
		// サンプルのポイントを dataNum/5 の点で補完する
		ptSpline = BSpline.calcBSpline( pts, dataNum/5 );

/*		
		printf("PosNum %d\n", ptSpline.length);
		foreach (inout Point p ; ptSpline) {
			printf("x %f, y %f\n", p.x, p.y);
		}
*/
	}
	
	this() {
		for (int i = 0; i < LC_S_POINT.length; ++i) {
			sx ~= LC_S_POINT[i][0];
			sy ~= LC_S_POINT[i][1];
		}
	}
	
	/// 
	this(ICounter counter_) {
		this();
		this.counter = counter_;
	}
	
private:
	/// 丸め込みを行う
	static	int getRound(float f) {
		return cast(int)f;
//		return (f-cast(int)f) >= 0.5f ? cast(int)f+1 : cast(int)f;
	}

	ICounter counter;
	
	static final float[][] LC_S_PRE_POINT = [
		[0.0f ,  0.0f],	
	];

	static final float[][] LC_S_POST_POINT = [
		[1.0f, 1.0f],	
		[1.0f, 1.0f],	
		[1.0f, 1.0f],
	];
	
	static const float[][] LC_S_POINT = [
		[0.0f ,  0.0f],	
		[0.0f ,  0.0f],	//1 START
		[0.1f , 0.1f],	//2
		[0.2f , 0.2f],	//3
		[0.6f , 0.6f],	//4
		[0.8f, 0.8f],	//5
		[0.9f, 0.9f],	//6
		[1.0f, 1.0f],	//7 END
		[1.0f, 1.0f],	
		[1.0f, 1.0f],
	];
	
	float[] sx;	//　ユーザ定義曲線のサンプル点
	float[] sy;
	
	Point[] ptSpline;
	
}
