module yamalib.draw.directivityobject;

private import std.math;

private import y4d;
private import y4d_thread.gametask;

private import yamalib.auxil.properties;
private import yamalib.counter;
private import yamalib.log.log;


alias objectPool!(DirectivityObject) ObjectPool;

/**
	指向性物体の実装
	プロパティアクセス風メソッドにしてみる
*/
class DirectivityObject : GameTaskBase {
	
	/// 自分自身のマネージャを設定する
	static void setManager(DirectivityObjectManager manager_) {
		manager = manager_;
	}

	/// テクスチャの設定
	void setTexture(Texture tex_) {
		this.m_texture = tex_;
		this.m_tWidth = cast(int) tex_.getWidth();
		this.m_tHeight = cast(int) tex_.getHeight();
	}
	
	/// リングテクスチャの設定
	void setTextureRing(Texture tex_) {
		this.m_texRing = tex_;
	}
	
	/// テクスチャの取得
	Texture getTexture() {
		return this.m_texture;
	}
	/// インターフェースを渡すようにすること
	void setTarget(IDirectivityTarget target_) {
		m_target = target_;
	}
	
	/// このオブジェクトを指定位置に収束させる
	void setConvergence(bool flg, int x, int y, Rect* rc=null) {
		this.m_convX = x;
		this.m_convY = y;
		this.m_convergence = flg;
		this.m_convRc = rc;
		
		// 収束させるために、今のベクトルを弱める
		this.m_dx /= 5.0f;
		this.m_dy /= 5.0f;
	}

	/// 好きな位置に発散	
	void setBreakOut(int x, int y) {
		this.m_convX = x;
		this.m_convY = y;
		this.m_convergence = true;
	}
	
	/// 位置の設定
	float x(float x_) {
		return this.m_x = x_;
	}
	/// 位置の設定
	float y(float y_) {
		return this.m_y = y_;
	}
	/// 位置の取得
	float x() {
		return this.m_x;
	}
	/// 位置の取得
	float y() {
		return this.m_y;
	}
	/// 加速度の設定
	float acc(float acc_) {
		return this.m_acc = acc_;
	}
	/// 加速度の取得
	float acc() {
		return this.m_acc;
	}
	/// 現在の速度を取得
	float speed() {
		return this.m_speed;
	}
	/// Ｘベクトル成分の設定
	float dx(float dx_) {
		return this.m_dx = dx_;
	}
	/// Ｘベクトル成分の取得
	float dx() {
		return this.m_dx;
	}
	/// Ｙベクトル成分の取得
	float dy(float dy_) {
		return this.m_dy = dy_;
	}
	/// Ｙベクトル成分の取得
	float dy() {
		return this.m_dy;
	}
	/// フェードを設定する
	void setFadeCounter(int start, int end, int step) {
		m_alphaCounter.set(start, end, step);
	}
	
	/// フラッシュアウトさせる
	void setFlushOut(int step) {
		this.m_flushOut = true;
		this.m_alphaCounter.set( m_alphaCounter.get(), 0, step); 
	}
	
	/// 移動
	override int onMove(Object o) {
		Screen screen = cast(Screen) o;

		m_target.onMove(null);
		
		m_alphaCounter++;
		
		int tx,ty;
		if ( this.m_convergence ) {
			// 収束中ならば収束点に向かう
			tx = this.m_convX;
			ty = this.m_convY;
			
		} else {
			tx = m_target.getX();
			ty = m_target.getY();
		}
		
		onMoveToTarget( tx, ty );
		
		// 収束中し終わったら、消える
		if ( this.m_convergence && !this.m_flushOut) {
			
			if ( !( m_convRc is null ) ) {
				if ( ptInRect( this.m_x, this.m_y, 
						m_convRc.left, m_convRc.top, m_convRc.right, m_convRc.bottom ) ) {
					convergenceOut();
				}
			} else {
				if ( ptInRect( this.m_convX, this.m_convY, 
						this.m_x, this.m_y, this.m_x + this.m_tWidth, this.m_y + this.m_tHeight) ) {
					convergenceOut();
				}
			}
			
		}
		
		if ( this.m_flushOut ) {
			m_ringSizeCounter++;
			if ( m_alphaCounter.isEnd() ) {
				// 死んだ
				return 1;
			}
		}

		return 0;
	}
	
	/// 描画
	override int onDraw(Object o) {
		Screen screen = cast(Screen) o;
		
		Color4ub colorOrg = screen.getColor4ub();

		screen.setColor(m_r, m_g, m_b, this.m_alphaCounter.get() );
		screen.blt(this.m_texture, cast(int) this.m_x, cast(int) this.m_y);
		
		if ( this.m_flushOut ) {
			float rate = 0.2f * m_ringSizeCounter.get(); 
			screen.bltRotate( this.m_texRing, cast(int) this.m_outx, cast(int) this.m_outy, 
				0, rate, 4); 
		}
		
		screen.setColor( colorOrg );
		
		return 0;
	}

	/// 古いヤツ
	int task(Object o) {
		return 0;
	}
	
	/// オブジェクトプールにはいるのでリセットする。
	void reset() {
		// 必要な処理を追記すること
		m_alphaCounter.set(255,255,0);
	}
	
	/// コンストラクタ
	this() {
		int alpha = cast(int) prop.getPropertyNum( LC_PROP_KEY_OBJ_ALPHA, 128 );
		m_alphaCounter = new RootCounterS();
		m_alphaCounter.set(alpha, alpha, 0);
		
		int ringStep = cast(int) prop.getPropertyNum(LC_PROP_KEY_RING_SIZE_STEP, -1);
		
		m_ringSizeCounter = new RootCounter();
		m_ringSizeCounter.set( 1, int.max, ringStep );
		
		this.acc = cast(int) prop.getPropertyDecimal(LC_PROP_KEY_ACC, 0.2f);

		this.m_r = cast(int) prop.getPropertyNum(LC_PROP_KEY_OBJ_R, 255);
		this.m_g = cast(int) prop.getPropertyNum(LC_PROP_KEY_OBJ_G, 255);
		this.m_b = cast(int) prop.getPropertyNum(LC_PROP_KEY_OBJ_B, 255);
	}
	
	/// 静的コンストラクタ
	static this() {
		st = SinTable.get();
		rand = new Rand;
		rand.randomize();

		prop = Properties.getInstance("img/bookmark/divobj.txt");
//		prop.load("img/bookmark/divobj.txt");
		LC_MAX_SPEED = prop.getPropertyDecimal(LC_PROP_KEY_MAX_SPEED, 7.5f);
	}

	
private:

	/// 絶対値
	static final float abs(float f) {
		return f < 0 ? -f : f;
	}
	
	/// 点が矩形の内側にあるかどうか
	static final bool ptInRect(float px, float py, float left, float top, float right, float bottom) {
		return cast(bool) (px >= left && px < right
			&& py >= top && py < bottom);
	}
	
	/// ターゲットのXY位置より、移動処理を行う
	void onMoveToTarget(int tx, int ty) {
		
		// 自分の位置と、ターゲットの位置から、単純な方向を求める。
		int aDx = cast(int) (tx - this.m_x);
		int aDy = cast(int) (ty - this.m_y);
		
		if ( 0 == aDx && 0 == aDy ) {
			// 現状維持
			return;
		}
		
		// 512 に丸め込む
		int rad = st.atan(aDx, aDy) >> 7;
		
		// 求めた角度より、ベクトル修正
		this.m_dx += st.cos_d(rad) * m_acc;
		this.m_dy += st.sin_d(rad) * m_acc;
		
		// 現在の速度
		this.m_speed = sqrt(pow(cast(real) m_dx,2) + pow(cast(real) m_dy,2));
		
		// 速度規制をかける
		if (LC_MAX_SPEED < this.m_speed ) {
			rad = st.atan(cast(int) (m_dx*LC_SCALER), cast(int) (-m_dy*LC_SCALER) ) >> 7;
			this.m_dx = st.cos_d(rad) * LC_MAX_SPEED;
			this.m_dy = -st.sin_d(rad) * LC_MAX_SPEED;
			this.m_speed = LC_MAX_SPEED;
		}

		// 修正ベクトルより移動
		m_x += m_dx;
		m_y += m_dy;
	}
	
	/// 収束して終了した
	void convergenceOut() {
		int outStep = cast(int) prop.getPropertyNum(LC_PROP_KEY_FADE_STEP, 10);
		setFlushOut(10);
		
		this.m_outx = cast(int) this.m_x;
		this.m_outy = cast(int) this.m_y;
	}
	
	static final char[] LC_PROP_KEY_ACC = "acceleration";
	static final char[] LC_PROP_KEY_MAX_SPEED = "max_speed";
	static final char[] LC_PROP_KEY_FADE_STEP = "fade_step";
	static final char[] LC_PROP_KEY_RING_SIZE_STEP = "ring_size_step";
	static final char[] LC_PROP_KEY_OBJ_ALPHA = "obj_alpha";
	static final char[] LC_PROP_KEY_OBJ_R = "obj_r";
	static final char[] LC_PROP_KEY_OBJ_G = "obj_g";
	static final char[] LC_PROP_KEY_OBJ_B = "obj_b";
	
	
	static float LC_MAX_SPEED = 7.5f;	//!< 最大速度で規制する
	static float LC_SCALER = 1024;
	static SinTable st;
	static Rand rand;
	static Properties prop;
	static DirectivityObjectManager manager;

	Texture m_texture;
	Texture m_texRing;	//!< リング用のテクスチャ
	int m_tWidth;
	int m_tHeight;
	float m_x = 0.0f;	//!< 描画開始位置
	float m_y = 0.0f;	//!< 描画開始位置
	int   m_outx;
	int   m_outy;
	float m_dx = 0.0f;	//!< 現在のベクトル
	float m_dy = 0.0f;	//!< 現在のベクトル
	float m_rate = 1.0f;	//!< テクスチャの拡大率
	float m_acc = 1.0f; //!< 加速度
	float m_speed = 0.0f;	//!< 現在の速度
	int m_r;
	int m_g;
	int m_b;

	IDirectivityTarget m_target;

	bool m_flushOut;	//!< フラッシュアウトするのか
	RootCounterS m_alphaCounter;	//!< アルファカウンタ
	RootCounter  m_ringSizeCounter;	//!< リングのサイズ
	
	bool m_convergence;	//!< 収束中か？
	int m_convX;	// 収束点
	int m_convY;
	Rect* m_convRc;	//!< 収束許容領域
	
}

/**
	
*/
class DirectivityObjectManager {

	/// オブジェクトプールを取得する（デバッグ
	ObjectPool getPool() { return pool; }
	
	/// 設定ファイルから初期化処理を行う
	void initDefFile(IDirectivityTarget[] targets_) {
		init( cast(int) prop.getPropertyNum("obj_num", 150), targets_);
	}
	
	/// 初期化処理
	void init(int num, IDirectivityTarget[] targets_) {
		
		setTarget(targets_[0]);
		
		// 球画像の読込
		this.texSphere = new Texture();
		this.texSphere.load("img/bookmark/sphere.png");
		
		this.texRing = new Texture();
		this.texRing.load("img/bookmark/ring.png");
		
		pool.setMax(800);
		foreach(inout DirectivityObject s; pool) {
			s = new DirectivityObject();
			s.setTexture( this.texSphere );
			s.setTextureRing( this.texRing );
			
			int rad = rand.get(512);
			s.y = st.sin(rad, 580);
			s.x = st.cos(rad, 740);
			
			// ここの値を変えて、動作を制御する
			s.setTarget( targets_[rand.get(length)] );
		}
		
		// マネージャの設定
		DirectivityObject.setManager(this);

		for(int i=0; i<num; ++i) {
			DirectivityObject s = pool.pop();
			if (s) {
				controller.addTask(s,0);
			}
		}
	}
	
	/// 現在使用しているターゲットを取得
	IDirectivityTarget getTarget() {
		return this.m_target;
	}
	
	/// ターゲットの設定
	void setTarget(IDirectivityTarget target_ ) {
		this.m_target = target_;
	}

	/// すべてのオブジェクトのベクトル成分を乱数で初期化する
	void randomizeAll(float max) {
		foreach(inout DirectivityObject s; pool) {
			float dx = (rand.get(1000) / 1000.0f) * max;
			float dy = (rand.get(1000) / 1000.0f) * max;
			s.dx = rand.get(2) ? dx : -dx;
			s.dy = rand.get(2) ? dy : -dy;
		}
	}

	/// 収束させる	
	void convergenceAll( int x, int y, Rect* rc ) {
		foreach(inout DirectivityObject s; pool) {
			s.setConvergence(true, x, y, rc);
		}
	}
	
	/// 発散させる
	void breakOutAll() {
		foreach(inout DirectivityObject s; pool) {
			int rad = rand.get(512);
			int y = st.sin(rad, 680);
			int x = st.cos(rad, 840);
			
			s.setBreakOut(x, y);
		}
	}
	
	/// 毎回呼び出すなり
	void onDraw(Screen screen) {
		this.controller.callTaskOnDraw(screen);
	}
	
	/// 移動処理
	void onMove(Screen screen) {
		this.controller.callTaskOnMove(screen);
	}

	/// オブジェクトの破棄
	void destroy() {
		if (this.pool) {
			this.pool.release();
			this.pool = null;
		}
		this.controller = null;
	}

	/// コンストラクタ
	this() {
		this.controller = new GameTaskController(1000);
		this.pool = new ObjectPool;
		this.rand = new Rand();
		rand.randomize();
		
		st = SinTable.get();
	}
	
	/// 静的コンストラクタ
	static this() {
		prop = Properties.getInstance("img/bookmark/divobj.txt");
//		prop.load("img/bookmark/divobj.txt");
	}
	
		
private:
	static SinTable st;
	static Properties prop;

	ObjectPool pool;	//!< オブジェクトプール
	GameTaskController controller;	//!< タスクコントローラ
	Rand rand;		//!< ランダム
	
	int m_x;	//!< この効果の描画位置
	int m_y;	//"< この効果の描画位置

	Texture texSphere;		// 球テクスチャ
	Texture texRing;		// リングテクスチャ
	IDirectivityTarget m_target;
}

/// ターゲット用のインターフェース
interface IDirectivityTarget {
	int getX();
	int getY();
	void onMove(Object o);
}


/**
  ターゲットにするデコイの実装<BR>
  めんどくさいから、このクラスにいろいろ実装してしまう
*/
class SimpleDecoy : IDirectivityTarget {
public:
	enum TYPE {
		FIX,	// 固定したまま動かない
		MOUSE,	// マウスカーソルの位置がターゲットなる
		RANDOM_FIX,	// 一定時間ごとに、固定の位置へ遷移する
	};
	
	/// タイプの設定
	void setType(TYPE type_) {
		this.m_type = type_;
	}

	/// Ｘ位置の設定
	void setX(int x_) {
		this.m_x = x_;
	}

	/// Ｙ位置の設定
	void setY(int y_) {
		this.m_y= y_;
	}

	/// X位置の取得
	int getX() {
		return this.m_x;
	}
	
	/// Y位置の取得
	int getY() {
		return this.m_y;
	}
	
	/// モードをランダムFixに設定する
	void setRandomFix(int maxX_,int maxY_, int interval_) {
		this.m_rndMaxX = maxX_;
		this.m_rndMaxY = maxY_;
		this.m_rndInterval = interval_;
	}
	
	/// マウスターゲット用のマウスを設定する
	void setMouse(MouseInput mouse_) {
		this.m_mouse = mouse_;
	}
	
	/// 位置移動処理
	void onMove(Object o) {
		switch( this.m_type ) {
		case TYPE.FIX:
			break;
		case TYPE.MOUSE:
			onMoveMouse();
			break;
		case TYPE.RANDOM_FIX:
			onMoveRandomFix();
			break;
		default:
			break;
		}
	}
	
	/// コンストラクタ
	this() {
		m_type = TYPE.FIX;
		m_rndMaxX = 640;
		m_rndMaxY = 480;
		rand = new Rand();
		rand.randomize();
	}
	
private:

	/// 移動処理（固定ランダム）
	void onMoveRandomFix() {
		m_intervalCnt++;
		if ( m_intervalCnt > m_rndInterval ) {
			m_intervalCnt = 0;
			this.m_x = rand.get(m_rndMaxX);
			this.m_y = rand.get(m_rndMaxY);
		}
	}
	
	/// 移動処理（マウス）
	void onMoveMouse() {
		m_mouse.getPos(this.m_x,this.m_y);
	}

private:
	Rand rand;

	TYPE m_type;
	MouseInput m_mouse;
	int m_x;
	int m_y;
	
	int m_rndMaxX;
	int m_rndMaxY;
	int m_rndInterval;
	int m_intervalCnt;
}
