module kyojintati4d.component.rhythm.keyconfigdialon;

private import y4d_aux.widestring;
private import y4d_draw.fontloader;
private import y4d_draw.fontrepository;
private import y4d_draw.screen;
private import y4d_draw.texture;
private import y4d_draw.drawbase;
private import y4d_math.sintable;
private import y4d_input.mouse;

private import y4d_timer.fixtimer;

private import yamalib.gui.guibutton;
private import yamalib.gui.keygroup;
private import yamalib.auxil.properties;
private import yamalib.counterfsp;
private import yamalib.log.log;


enum HitStatus {
	GREAT,
	GOOD,
	BAD,
	POOR,
	ERROR,
	NONE
};

// ヒットしたときのイベント
alias bool delegate(Screen, TextObj, long, HitStatus) HitEvent;

public class TimingData {
	this(long hitTime, HitEvent[] event) {
		m_hitTime = hitTime;
		m_event = event;
	}

	this(long hitTime, HitEvent[] event, bool hidden) {
		m_hidden = hidden;
		this(hitTime, event);
	}
	
	/// ヒットするミリ秒
	long hitTime() {
		return m_hitTime;
	}
	/// ヒット成功したときのイベント
	HitEvent[] hitEvent() {
		return m_event;
	}
	
	bool isHidden() {
		return m_hidden;
	}
	bool isHidden(bool value) {
		return m_hidden = value;
	}

private:
	long m_hitTime;
	HitEvent[] m_event;
	bool m_hidden;
}

/// テンポデータ
public class TempoData {
public:

	/// コンストラクタ
	this(wchar[] text, TimingData[] timing) 
	in
	{
		assert(text !is null);
	}
	body	
	{		
		m_text = text;
		m_timing = timing;
	}
	
	wchar[] text() {
		return m_text;
	}
	TimingData[] timing() {
		return m_timing;
	}
	
	wchar getNextLetter() {
		if (m_text.length > letterIndex) {
			return m_text[letterIndex++];
		}
		return 'A';
	}
	

private:
	wchar[] m_text;
	TimingData[] m_timing;
	// 制御用変数
	int letterIndex = 0;
}


/// テキストオブジェクト
public class TextObj {
	this(FixTimer timer) {
		m_timer = timer;
		m_startTime = timer.get();
	}
	this(FixTimer timer, long startTime) {
		m_timer = timer;
		m_startTime = startTime;
	}
	
	/// たたけるデータか
	bool hitable() {
		return m_hitable;
	}
	bool hitable(bool value) {
		return m_hitable = value;
	}

	/// 描画終了
	bool isDrawEnd() {
		return m_drawEndFlg;
	}
	bool isDrawEnd(bool value) {
		return m_drawEndFlg = value;
	}

	/// 終了したか　描画およびデリゲートが終了		
	bool isEnd() {
		return m_endFlg;
	}
	bool isEnd(bool value) {
		return m_endFlg = value;
	}
	
	bool isStart() {
		return m_timer.get() > m_startTime;
	}
	
	/// 画像
	Texture img() {
		return m_img;
	}
	Texture img(Texture value) {
		return m_img = value;
	}

	/// 描画位置
	PointInt* pos() {
		return &m_pos;
	}
	
	/// 拡大率
	float rate() {
		return m_rate;
	}
	float rate(float value) {
		return m_rate = value;
	}
	
	/// 透過
	int alpha() {
		return m_alpha;
	}
	int alpha(int value) {
		return m_alpha = value;
	}
	
	/// 生起してからの経過時間
	long time() {
		return m_timer.get() - m_startTime;
	}
	
	int rad() {
		return m_rad;
	}
	int rad(int value) {
		return m_rad = value;
	}
	
	/// ターゲットにしているヒットのタイム(ms)
	long targetTime() {
		return m_targetTime;
	}
	long targetTime(long value) {
		return m_targetTime = value;
	}
	
	/// 動作開始ミリ秒
	long startTime() {
		return m_startTime;
	}
	
	/// ヒットしたときのイベント
	HitEvent[] hitEvent() {
		return m_event;
	}
	/// ヒットしたときのイベント
	HitEvent[] hitEvent(HitEvent[] value) {
		return m_event = value;
	}
	
	/// 隠れタイミング
	bool isHidden() {
		return m_hidden;
	}
	bool isHidden(bool value) {
		return m_hidden = value;
	}
	
	/// ヒット結果
	HitResult hitResult() {
		return m_hitResult;
	}
	HitResult hitResult(HitResult value) {
		return m_hitResult = value;
	}
	
	void onDraw(Screen screen) {
		if (!isEnd) {
			if (m_hitable && !m_hidden) {
				screen.setColor(255,0,0,alpha);
			} else {
				screen.setColor(255,255,255,alpha);
			}
			screen.bltRotate(m_img, m_pos.x, m_pos.y, m_rad, m_rate, 4);
		}
	}

	/// デリゲートを起動	
	void onDrawDelegate(Screen screen) {
		if (!isEnd) {
			if (hitEvent is null) {
				isEnd = m_drawEndFlg;
				return;
			}
			
			if (hitResult !is null) {
				bool invoking = false;
				for(int i = 0; i < hitEvent.length; ++i) {
					if (hitEvent[i] is null) {
						continue;
					}
					
					bool result = hitEvent[i](screen, this, hitResult.hitTime, hitResult.hitStatus);
					if (result) {
//Log.print("INVOKE");
						hitEvent[i] = null;
					} else {
						invoking = true;
					}
				}
				//どれかのデリゲートが起動中
				isEnd = m_drawEndFlg && !invoking;
			}
		}
	}
	
	/// オブジェクトのシャローコピーを生成する
	TextObj clone() {
		auto obj = new TextObj(m_timer, m_startTime);
		obj.hitEvent = m_event;
		obj.hitResult = m_hitResult;
		obj.img = m_img;
		obj.rate = m_rate;
		obj.rad = m_rad;
		obj.m_alpha = alpha;
		obj.pos.x = m_pos.x;
		obj.pos.y = m_pos.y;
		obj.hitable = m_hitable;
		obj.isEnd = m_endFlg;
		obj.isDrawEnd =  m_drawEndFlg;
		obj.targetTime = m_targetTime;
		return obj;
	}
	
private:

	/// ヒット結果
	static class HitResult {
		this(long hitTime, HitStatus hitStatus) {
			this.hitTime = hitTime;
			this.hitStatus = hitStatus;
		}
		
		long hitTime;
		HitStatus hitStatus;
	}

	HitResult m_hitResult;
	HitEvent[] m_event; 
	FixTimer m_timer;
	long m_startTime = 0L;
	Texture m_img;
	float m_rate;
	int m_rad;
	int m_alpha;
	PointInt m_pos = {x:0, y:0};
	bool m_hitable;
	bool m_endFlg;
	bool m_drawEndFlg;
	bool m_hidden;
	long m_targetTime;
	
}

public class TextuaryTempo {
public:

	/// コンストラクタ
	this() {
		m_timer = new FixTimer();
		m_alphaCounter = new RootCounterS(255,255,1);
		// 中心点を求めておく
		calcCenterPos();
	}
	
	/// 移動処理
	void onMove(Screen screen) {
		if (m_tempoData is null) {
			return;
		}
		m_timer.update();
		if (m_stop) {
			return;
		}
		
		// アルファ
		m_alphaCounter.inc();
		
		// 文字移動
		onMoveTextObj(screen);
		
		// ヒットされたか
		if (m_mouse !is null) {
			
			m_invalidHit = false;
			
			bool mouseLDown = m_mouse.isLButtonDown;
			bool mouseLNewlyDown = mouseLDown && !m_mouseLBtPush;
			// どれかのオブジェクトに対応するヒットが存在したか
			bool hitObject = !mouseLNewlyDown;
			long now = m_timer.get();
			foreach(obj; textObj) {
				if (!obj.isStart || obj.isEnd || !obj.hitable) {
					continue;
				}
				long time = obj.targetTime;
				
				if (m_autoPlay) {
					if ((time - now) < 50 || now > time) {
						// ヒット結果保存
						if (!obj.isHidden) {
							obj.hitResult = createHitResult(time, now);
							obj.hitable = false;
						} else {
							// 隠しデータ
							obj.hitResult = new TextObj.HitResult(time, HitStatus.NONE);
							obj.hitable = false;
						}
						// ノートを叩いたのでOK
						hitObject = true;
					}
				}
				// マウスが押された時刻が誤差内
				else if (mouseLNewlyDown && isInRange(time - m_hitMargin, time + m_hitMargin, now)) {
//printf("OK: %d\n", time - now);

					// ヒット結果保存
					if (!obj.isHidden) {
						obj.hitResult = createHitResult(time, now);
						obj.hitable = false;
					} else {
						// 隠しデータ
						obj.hitResult = new TextObj.HitResult(time, HitStatus.NONE);
						obj.hitable = false;
					}
					// ノートを叩いたのでOK
					hitObject = true;
				} 
				else if (now > time + m_hitMargin)
				{ 
					// ヒットされなかった
					obj.hitResult = new TextObj.HitResult(long.min, 
							!obj.isHidden ? HitStatus.POOR : HitStatus.NONE);
					obj.hitable = false;
					// 叩いたことにする
					hitObject = true;
				}
			}
			
			if (!hitObject) {
				// ノートを叩いていないのにヒットしたので無関係なヒット
				m_invalidHit = true;
				Log.print("HIT ERROR!!");
			}
			
			m_mouseLBtPush = mouseLDown;
		}
	}
	
	bool isInvalidHit() {
		return m_invalidHit;
	}
	
	
	static TextObj.HitResult createHitResult(long targetTime, long hitTime) {
		int errorMargin = std.math.abs(targetTime - hitTime);
		
		// Great!
		if (50 >= errorMargin) {
			return new TextObj.HitResult(hitTime, HitStatus.GREAT);
		}
		// Good!
		if (75 >= errorMargin) {
			return new TextObj.HitResult(hitTime, HitStatus.GOOD);
		}
		// Bad!
		if (100 >= errorMargin) {
			return new TextObj.HitResult(hitTime, HitStatus.BAD);
		}
		// poor!
		return new TextObj.HitResult(hitTime, HitStatus.POOR);
	}
	

	/// 動作処理	
	void onMoveTextObj(Screen screen) {
		// 起点
		calcCenterPos();
		float rateBreadth = m_endRate - m_startRate;
		TextObj[] aliveObj;

		auto alphaRate = fadeOut ? 1.0f - ((m_timer.get() - fadeStartTime) / cast(float) fadeTime) 
				: 1.0f;
		if (alphaRate < 0.0f) {
			alphaRate = 0.0;
			m_stop = true;
		}
		auto nowAlpha = fadeOut ? m_alphaCounter.get() * alphaRate : m_alphaCounter.get();
		foreach(obj; textObj) {

			if (obj.isStart && !obj.isEnd) {
				// 描画が終了していないなら継続
				if (!obj.isDrawEnd) {
					
					// 描画位置・角度・拡大率 を求める
					float timeProgress = obj.time / cast(float) m_arrivalTime;
					obj.pos.x = m_startPos.x + cast(int) (dxCenter * timeProgress);
					obj.pos.y = m_startPos.y + cast(int) (dyCenter * timeProgress);
					obj.rate = m_startRate + (rateBreadth * timeProgress);
					obj.rad = rad;
		
					if (timeProgress < 1.0f) {
						obj.alpha = cast(int) (nowAlpha * timeProgress);
					} else {
						obj.alpha = cast(int) (nowAlpha - (nowAlpha * (timeProgress - 1.0f)));
		
						// アルファが0になったら終了
						if (obj.alpha < 0) {
							obj.isDrawEnd = true;
						}
					}
				}
				obj.onDrawDelegate(screen);
			}
		}
	}
	
	/// 描画処理
	void onDraw(Screen screen) {
		if (m_tempoData is null) {
			return;
		}
		if (m_stop) {
			return;
		}
		
		// テンポテキスト描画
		for (int i = textObj.length - 1; i >= 0; --i) {
			if (textObj[i].isEnd() || !textObj[i].isStart) {
				continue;
			}
			textObj[i].onDraw(screen);
		}
		
		// ターゲット画像
//		screen.blendSubColorAlpha();
		screen.blendSrcAlpha();
		screen.setColor(255,255,255,200);
		screen.bltRotate(m_scopeImg, centerPos.x, centerPos.y, rad, 1.5f, 4);
//		screen.blendSrcAlpha();
	}
	
	FixTimer timer() {
		return m_timer;
	}
	FixTimer timer(FixTimer value) 
	in
	{
		assert(value !is null);
	}
	body
	{
		return m_timer = value;
	}
	
	/// タイマをリセットして、動作準備する
	void reset() {
		textObj = null;
		preGenTime = long.min;
		letterIndex = 0;
		
		// タイマをリセット
		m_timer.reset();
		textObj ~= craatTextObjAll(m_tempoData[m_textIndex]);
		preGenTime = m_timer.get();
		m_timer.reset();
	}
	
	/// 残りのヒットデータ
	int restHitData() {
		uint nowTime = m_timer.get();
		int count;
		foreach(obj; textObj) {
			if (obj.hitable && obj.targetTime > nowTime) {
				++count;
			}
		}
		return count;
	}
	
	/** 表示を終了する */
	void stop() {
		m_stop = true;
	}
	
	/** アルファフェードしながら表示を終了する */
	void fadeStop(uint fadeTime) {
		fadeStartTime = m_timer.get();
		this.fadeTime = fadeTime;
		fadeOut = true;
	}
	
	
	// プロパティ
	
	MouseInput mouse() {
		return m_mouse;
	}
	MouseInput mouse(MouseInput value) {
		return m_mouse = value;
	}
	
	/// オートプレイ
	bool autoPlay() {
		return m_autoPlay;
	}
	bool autoPlay(bool value) {
		return m_autoPlay = value;
	}

	/// フォントリポジトリの設定／取得
	FontRepository fontRepository() {
		return m_fontRep;
	}
	FontRepository fontRepository(FontRepository value) {
		return m_fontRep = value;
	}
	
	Texture scopeImg() {
		return m_scopeImg;
	}
	Texture scopeImg(Texture value) {
		return m_scopeImg = value;
	}
	
	/// テンポの文字列
	TempoData[] tempoData() {
		return m_tempoData;
	}
	TempoData[] tempoData(TempoData[] value) {
		return m_tempoData = value;
	}
	
	/// 使用する文字列インデックス
	int textIndex() {
		return m_textIndex;
	}
	int textIndex(int value) {
		return m_textIndex = value;
	}
	
	/// 開始位置
	PointInt* startPos() {
		return &m_startPos;
	}
	/// 終端位置
	PointInt* endPos() {
		return &m_endPos;
	}
	
	/// 誤差許容値 (ミリ秒)
	long hitMargin() {		
		return m_hitMargin;
	}
	long hitMargin(long value) {
		return m_hitMargin = value;
	}
	
	///　開始文字サイズ
	float startRate() {
		return m_startRate;
	}
	float startRate(float value) {
		return m_startRate = value;
	}

	/// 終端文字サイズ
	float endRate() {
		return m_endRate;
	}
	float endRate(float value) {
		return m_endRate = value;
	}

	/// 文字生成間隔(ms)	許容間隔より小さくとること
	long generateInterval() {
		return m_generateInterval;
	}
	long generateInterval(long value) {
		return m_generateInterval = value;
	}
	
	/// ヒットポイントまでの到達時間 (ms)
	long arrivalTime() {
		return m_arrivalTime;
	}
	long arrivalTime(long value) {
		return m_arrivalTime = value;
	}
	
	RootCounterS alphaCounter() {
		return m_alphaCounter;
	}

	
private:
	
	/// 引数に指定したテンポ情報より、描画オブジェクトを作成する
	TextObj createTextObj(TempoData data, long startTime) {
		wchar ch = data.getNextLetter();
		auto img = m_fontRep.getTexture(ch);
		auto obj = new TextObj(m_timer, startTime);
		obj.img = img;
		obj.rate = m_startRate;
		obj.alpha = 0;
		obj.pos.x = m_startPos.x;
		obj.pos.y = m_startPos.y;
//		setHitable(obj, data, startTime);
		return obj;
	}

	static long min(long x, long y) {
		return x < y ? x : y;
	}
	
	static bool isInRangeAbs(long just, long margin, long myValue) {
		return (just - margin) <= myValue && (just + margin) >= myValue;
	}
	
	static bool isInRange(long min, long max, long target) {
		return min <= target && max >= target;
	}
	
	/// すべてのオブジェクトデータを作成する
	TimingData[TextObj] checkMap;	

	TextObj[] craatTextObjAll(TempoData data) {
		TextObj[] result;
		for (int i = 0; i < data.text.length; ++i) {
			result ~= createTextObj(data, generateInterval * i);
		}

		// 作ったTextObj[]に TimingData を割り当てる
		TimingData[] rangeData;
		foreach(timingData; data.timing) {
			long time = timingData.hitTime;
			TextObj minData;
			long minTime = long.max;
			// 範囲内のtextObj からもっとも精度のよいものを抽出
			foreach(textObj; result) {
				if (isInRangeAbs(time, m_hitMargin, textObj.startTime + m_arrivalTime)) {
					auto aMinTime = min(minTime, std.math.abs(time - (textObj.startTime + m_arrivalTime)));
					if (minTime > aMinTime) {
						minTime = aMinTime;
						minData = textObj;
					}
				}
			}
			
			assert (minData !is null);
			if (minData in checkMap) {
				Log.print("over rite");
			}

			checkMap[minData] = timingData;
			minData.hitable = true;
			minData.targetTime = time;
			minData.hitEvent = timingData.hitEvent;
			minData.isHidden = timingData.isHidden;
		}
		
		
		return result;
	}

	//------- 非公開メソッド
	
	// 開始終点より中間位置を求める
	void calcCenterPos() {
		dxCenter = (m_endPos.x - m_startPos.x) / 2;
		centerPos.x = m_startPos.x + dxCenter;
		
		dyCenter = (m_endPos.y - m_startPos.y) / 2;
		centerPos.y = m_startPos.y + dyCenter;

		// こいつは内部でシングルトン
		rad = SinTable.get().atan(m_startPos.x - m_endPos.x, m_startPos.y - m_endPos.y) >> 7;
	}

	//------- フィールド変数
	
	// 移動タイミング用タイマ
	FixTimer m_timer;
	
	// フェードアウト用
	uint fadeStartTime;
	uint fadeTime;
	bool fadeOut = false;
	bool m_stop = false;
	
	// ヒットフラグ
	bool m_invalidHit;
	// auto play falg
	bool m_autoPlay;
	
	// ターゲット範囲
	Texture m_scopeImg;
	
	// ヒット用デバイス
	MouseInput m_mouse;
	bool m_mouseLBtPush;
	
	// フォントリポジトリ
	FontRepository m_fontRep;
	// 表示もととなる文字列
	TempoData[] m_tempoData;
	// 使用する文字列
	int m_textIndex = 0;
	// 開始する文字列サイズ
	float m_startRate = 0.2f;
	// 終端での文字サイズ
	float m_endRate = 1.5f;
	// 描画開始位置
	PointInt m_startPos = {x:10, y:30};
	// 描画終了位置
	PointInt m_endPos = {x:610, y:450};
	// OK誤差
	long m_hitMargin = 400L;
	// 生成スピード(ms)
	long m_generateInterval = 100L;
	// hit pointに到達するまでの時間 (ms)
	long m_arrivalTime = 1500L;
	// フェード・インフェード・アウト用カウンタ
	RootCounterS m_alphaCounter;

	// プログラム制御用
	TextObj[] textObj;
	long preGenTime = long.min;
	int letterIndex = 0;
	PointInt centerPos;
	int dxCenter;
	int dyCenter;
	int rad;
}
