module kyojintati4d.component.titledial;

private import std.math;	// abs

private import y4d_draw.drawbase;
private import y4d_draw.texture;
private import y4d_math.sintable;
private import y4d_draw.screen;
private import ytl.vector;

private import kyojintati4d.val.y4dconst;

private import yamalib.counterfsp;
private import yamalib.log.log;


/**
	タイトルタスク用
	ダイアルオブジェクトクラス
*/
class TitleDial {
	
	/// 表示中心位置の設定
	void setXY( int x_, int y_ ) {
		x = x_;
		y = y_;
	}
	
	/// ダイアル表示中心位置の取得
	void getXY( out int x_, out int y_ ) {
		x_ = x;
		y_ = y;
	}

	/// 基本ダイアルテクスチャの設定
	void setDialTexture(Texture tex) {
		
		assert( !(tex is null) );
		
		t_dial = tex;
		sx = cast(int) t_dial.getWidth();
		sy = cast(int) t_dial.getHeight();
		
		Log.print("setDialTexture sx: %s, sy : %s", sx, sy);
	}
	
	/// ダイアル項目テクスチャの追加
	void addItem(Texture t_) {
		vItems.push_back(t_);
	}
	
	/// 項目を名前付きで追加する
	/// 現在何を選択されているか名前をgetNameで取得することができる
	void addItemName( Texture t_ , char[] name ) {
		vNames.push_back(name);
		addItem(t_);
	}
	
	/// 選択いしている項目に関連づけられた名前を返却します
	char[] getSelectedItemName() {
		if ( selectedPos == 0 ) {
			return vNames[0];
		}
		
		return vNames[vNames.size()-selectedPos];
	}
	
	/// 選択済み番号を取得
	int getSelectedPos() {
		return selectedPos;
	}
	
	/// 項目に関連している名前を指定して、そのアイテムを選択させる
	/// 指定した名前が存在しなければfalseが返る
	bool selectByName(char[] str) {
		int selected = -1;	// not found marker
		foreach(int i,inout char[] c; vNames) {
			if (c == str) {
				selected = vItems.size()-i;
				break;
			}
		}
		if (-1 == selected) {
			return false;
		}
		selectedPos = selected;
		int dstAngle = cast(int) ((512.0f / vItems.size()) * (selectedPos));
		angle.set(dstAngle, dstAngle, rotateSpeed);
		return true;
	}
	
	/// ダイアル項目を全消去します
	void clearItems() {
		vItems.clear();
	}
	
	/// 指定したアイテムに直行する
	void turnTo(int itemID) {
		if (selectedPos == itemID) {
			//何もしない
			return;
		}
		if (itemID >= vItems.size()) {
			//何もしない
			return;
		}
		
		// 左まわりか、右周りを決める
		int dstAngle = cast(int) ((512.0f / vItems.size()) * itemID);
		m_angleCtl.turnTo(dstAngle);
		selectedPos = itemID;
	}
	

	/// 右回り（時計回り）させる
	void turnLeft() {

		// 項目もないのにすることないのだ...
		if ( !vItems.size() ) return;
		
		// 回転中であれば、なにもしない
		if (rotate) return;

		// 選択位置を一つ増やす
		++selectedPos;
		
		if (selectedPos >= vItems.size()) {
			selectedPos = 0;
		}
		
		// 現在の角度
		int nowAngle = angle.get();
		int dstAngle = cast(int) ((512.0f / vItems.size()) * selectedPos);
		
		if (nowAngle == Y4dConst.C_RAD_MAX) {
			// 一周後の値。０にリセットする
			nowAngle = 0;
		}
		
		if (dstAngle == 0) {
			// これは一周したということ。
			dstAngle = Y4dConst.C_RAD_MAX;
		}
		 
		angle.set(nowAngle, dstAngle, rotateSpeed);
		
		Log.print("turnLeft : src %s, dst : %s, pos : %s", nowAngle, dstAngle, selectedPos );
		
		rotate = true;

	}

	/// 左回り（反時計回り）させる
	void turnRight() {

		// 項目もないのにすることないのだ...
		if ( !vItems.size() ) return;
		
		// 回転中であれば、なにもしない
		if (rotate) return;
		
		// 選択位置を一つ減らす
		--selectedPos;
		
		if (selectedPos < 0) {
			selectedPos = vItems.size()-1;
		}
		
		// 現在の角度
		int nowAngle = angle.get();
		int dstAngle = cast(int) ((512.0f / vItems.size()) * selectedPos);
		
		if (nowAngle == 0) {
			// 一周後の値。０にリセットする
			nowAngle = Y4dConst.C_RAD_MAX;
		}
		
		if (dstAngle == Y4dConst.C_RAD_MAX) {
			// これは一周したということ。
			dstAngle = 0;
		}
		
		angle.set(nowAngle, dstAngle, rotateSpeed);
		 

		Log.print("turnRight : src %s, dst : %s, pos : %s", nowAngle, dstAngle, selectedPos );
		
		rotate = true;
	}
	
	/// ダイアル回転中かどうか
	bool isAround() {
		return rotate;
	}
	
	/// 目標となる角度に達したとき、停止する
	void targetAngleThenStop(bool b) {
		m_angleCtl.targetAngleThenStop(b);
	}

	/// 毎回よびだすといいなり
	void onMove(Screen scree) {

		// 回転中ならば回転処理を行う。
//		if (rotate) {
//			// 角度を進める
//			angle++;
//			if (angle.isEnd()) {
//				rotate = false;
//				m_rotate = ROTATE.STOP;
//			}
//		}
		m_angleCtl.onMove();		
	}
	
	/// できるだけ毎回呼び出すなり
	void onDraw(Screen screen) {
		
		// x, yを画像の中心点として描画する 
		screen.bltRotate( t_dial, x, y, cast(int) m_angleCtl.getNowAngle(), 1.0f, 4);
		
		/*
		// 出力用変数
		int[] posX;
		int[] posY;
		int[] angles;
		
		// 描画位置を計算する
		calcItemPos( posX, posY, angles );
		
		// 項目パネルを描画する
		for (int i = 0; i < vItems.size(); ++i) {
			
			screen.bltRotate( 
				vItems[i],			// テクスチャ 
				posX[i], posY[i], 	// 位置
				angles[i], 			// 角度
				1.0f, 				// 拡大率
				3 );				// 左辺の中点を位置とする
		}
		*/
	}	
	
	/// コンストラクタ
	this() {
		vItems = new vector!(Texture);
		vNames = new vector!(char[]);
		angle = new RootCounterS();
		angle.set(0, Y4dConst.C_RAD_MAX, 2);
		rotateSpeed = 2;
		m_angleCtl = new AngleControl();
	}
	
	/// 静的コンストラクタ
	static this() {
		sin = SinTable.get();
	}
	
private :
	/// アイテムを描画する位置を取得します
	void calcItemPos(out int[] xs, out int[] ys, out int[] angles) {
		// 現在の角度
		int agl = angle.get();
		int itemCnt = vItems.size();
		
		xs = new int[itemCnt];
		ys = new int[itemCnt];
		angles = new int[itemCnt];
		
		// ダイアルテクスチャの半径
		int r = (sy / 2) - LC_ITEM_L_MARGIN;
		
		foreach(int i, inout Texture t; vItems ) {
			int baseAngle = 256 + agl + (Y4dConst.C_RAD_MAX/itemCnt)*i ;
			// ここが基点
			xs[i] = x + sin.cos( baseAngle, r );
			ys[i] = y + sin.sin( baseAngle, r );
			angles[i] = baseAngle - 256;
		}
	}

	/* 項目描画位置の端からのマージン */
	static final const int LC_ITEM_L_MARGIN = 20; 
	
	static SinTable sin;

	int x;	//!< ダイアルの表示中心位置x
	int y;	//!< ダイアルの表示中心位置y
	int sx;	//!< ダイアルテクスチャサイズX
	int sy; //!< ダイアルテクスチャサイズY

	int 			selectedPos;	//!< 選択中項目インデックス
	int 			rotateSpeed;	//!< 回転速度
	bool 			rotate;			//!< 回転中かどうかを表すフラグ
	RootCounterS 	angle;			//!< 角度
	
	Texture 			t_dial;		//!< ダイアルの基本テクスチャ
	vector!(Texture)	vItems; 	//!< こいつはダイアルにつけるボタン。追加削除を行いたいのでVector
	vector!(char[]) 	vNames; 	//!< 項目に対する名前付けリスト
	PointInt[] 			itemPoints;	//!< アイテム描画基本位置を保持する
	
	/** 角度制御クラス */
	static class AngleControl {
	public:
		/// 回転状態定義
		enum ROTATE {STOP,LEFT,RIGHT};
		/// 今のところ加速度は固定値で
		static final const float ACCELERATION = 0.025F;
		/// 今のところ加速度は固定値で
		static final const float MAX_SPEED = 1.0F;
		/// 減速を開始する位置
		static final const int STOPPING_DIST = 15;
		static final const float DEACCELERATION = 40.0F;
		
		/// 今の角度から、引数で指定された角度に回転する
		void turnTo(int rad) {
			if (rad == cast(int) m_nowAngle) {
				return;
			}
			m_rotate = getRoteTo(rad);
			m_dstAngle = rad;
			
			// 回転方向決定
			if (ROTATE.LEFT == m_rotate) {
				m_sgn = -1;
			} else if (ROTATE.RIGHT == m_rotate) {
				m_sgn = 1;
			}
			
			Log.print("AngleControl#turnTo : %s to %s with %s", cast(int) m_nowAngle, rad, m_sgn);
		}
		
		/// 動作処理
		void onMove() {
			// 回転中であること
			if (ROTATE.STOP != m_rotate) {

				m_speed += getAccel();
	
				// 回転方向に従って、速度制限			
				if(m_sgn < 0) {
					if (-MAX_SPEED > m_speed) {
						m_speed = -MAX_SPEED;
					}
				} else {
					if (MAX_SPEED < m_speed) {
						m_speed = MAX_SPEED;
					}
				}
				
				// 現在の速度で角移動
				m_nowAngle += m_speed;
				normalizeAngle(m_nowAngle);
				
				if (m_targetAngleStopFlg) {
					// 回転停止処理
					if(m_sgn < 0) {
						if (m_dstAngle >= m_nowAngle) {
							Log.print("AngleControl#onMove : STOP LEFT %s to %s", m_nowAngle, m_dstAngle);
							m_rotate = ROTATE.STOP; 
						}
					} else {
						if (m_dstAngle <= m_nowAngle) {
							Log.print("AngleControl#onMove : STOP LEFT %s to %s", m_nowAngle, m_dstAngle);
							m_rotate = ROTATE.STOP; 
						}
					}
				}
				
			} else {
				// ストップ初期化
				m_speed = 0.0f;	
			}
		}
		
		/// 現在の角度を取得
		float getNowAngle() {
			return m_nowAngle;
		}

		/// コンストラクタ
		this() {
			m_sgn = 1;
			m_stopping = true;
		}
	private:
		ROTATE getRoteTo(int rad) 
		in
		{
			assert(rad >= 0 && rad <= 512);
		}
		body
		{
			int now = cast(int) m_nowAngle;
			rad -= now;
			// 0 - 511 に補正
			if (rad < 0) {
				rad = 512 + now; 
			}
			return rad < 256 ? ROTATE.RIGHT : ROTATE.LEFT;
		}
		
		/// 引数の角を0 - 511 に正規化する
		static void normalizeAngle(inout float rad) {
			if (rad < 0) {
				rad = 511.0F -  rad;
			} else if (511.0F < rad) {
				rad -= 511.0f;
			}
		}
		
		/// 加速度を取得する
		float getAccel() {
			if (m_targetAngleStopFlg) {	// 停止処理を行うのであれば
				// 減速域であるか？
				if (getDistance() <= STOPPING_DIST) {
					// 逆制動を行う
					return m_speed / DEACCELERATION * -m_sgn;
				}
			}
			return m_accel * m_sgn;
		}
		
		/// 距離を取得する
		int getDistance() {
			return .abs(cast(int) m_nowAngle - cast(int) m_dstAngle);
		}
		
		/// 目標となる角度に達したとき停止する
		void targetAngleThenStop(bool b) {
			m_targetAngleStopFlg = b;
		}
		
	private:
		bool m_stopping;		//!< 停止中
		float m_accel = ACCELERATION; 	//!< 加速度
		float m_speed = 0.0f;	//!< 速度
		ROTATE m_rotate;	//!< 回転の状態
		float m_nowAngle = 0.0f;
		float m_dstAngle = 0.0f;
		int m_sgn;	//! サイン
		bool m_targetAngleStopFlg;
	}
	
	AngleControl m_angleCtl;
	
}