module yamalib.draw.camerawork;

private import std.string;
private import std.c.math;

private import y4d_draw.drawbase;
private import y4d_draw.texture;
private import y4d_draw.screen;
private import y4d_math.sintable;
private import y4d_draw.texturebase;
private import y4d_aux.filesys;
private import y4d_aux.lineparser;
private import y4d_input.mouse;
private import y4d_input.mouse;
private import y4d_timer.fixtimer;
private import y4d_draw.textureloader;
private import y4d_input.key;
private import y4d_math.rand;


private import yamalib.draw.conbinetexture;
private import yamalib.counterfsp;
private import yamalib.math.spline;
private import yamalib.log.log;

/**
	登録した対象を被写体として、カメラワークを実現します
*/
class CameraWork {
	static const char[] C_PREFIX_POS = "POS:";
	static const char[] C_PREFIX_ROT = "ROT:";
	static const char[] C_PREFIX_RATE = "RATE:";
	static const char[] C_PREFIX_WAIT = "WAIT:";
	
	static const int C_SCALE = 1000;

	/// 背景画像を設定します
	void setBaseTexture(Texture t) {
		texBase = t;
	}
	
	/// 再生スピードを設定する
	void setSpeed(int n) {
		this.speed = n;
	}

	/// 移動処理を行う
	void onMove(Screen screen) {
		for (int i = 0; i < this.speed; ++i) {
			if ( innerOnMove(screen) ) {
				break;
			}
		}
	}
	
	/// 繰り返す内部処理
	bool innerOnMove(Screen screen) {
		if ( cameraFlow is null || isEnd() ) return true;
		
		int x,y;
		
		x = cameraFlow.x[count];
		y = cameraFlow.y[count];
		
		// rotateの検索
		for (int i = 0; i < cameraFlow.rotPoints.length; ++i) {
			if ( cameraFlow.rotPoints[i] == count ) {
				rot =  cameraFlow.nextRot();
				break;
			}
		}

		// rateの検索
		for (int i = 0; i < cameraFlow.ratePoints.length; ++i) {
			if ( cameraFlow.ratePoints[i] == count ) {
				rate =  cameraFlow.nextRate();
				Log.print("new Rate");
				break;
			}
		}
		
		// 回転移動
		if ( !(rot is null) ) {
			rot.inc();
		}
		
		// 拡大移動
		if ( !(rate is null) ) {
			rate.inc();
		}
		
		// カウンタ増分
		if ( cameraFlow.x.length-1 > count) {
			++count;
		} else {
			return true;
		}
		
		return false;
	}
	
	/// カメラフローが終了したか
	bool isEnd() {
		return cast(bool) (cameraFlow.x.length-1 <= count);
	}
	
	/// 描画処理を行う
	void onDraw(Screen screen) {
		if ( cameraFlow is null ) return;
		
		int x,y;
		
		x = cameraFlow.x[count];
		y = cameraFlow.y[count];
		
		
		float rate = 1.0f;
		int rot = 0;
		
		if ( !(null is this.rate) ) {
			rate =( cast(float) this.rate.get() ) / C_SCALE;
//printf("rate %f\n", rate);
		}
		
		if ( !(null is this.rot) ) {		
			rot = this.rot.get() / C_SCALE;
		}
		
		// 背景の描画
		ct.onDrawRotateFix( screen , x, y, rot, rate );
		
		// ここでのx,yは中心点であるから、左上座標に変換する
		int top  = cast(int) (y - 240);
		int left = cast(int) (x - 320);
		foreach(inout Subject s; subjects) {
			screen.bltRotate( s.getTexture(), 
				cast(int) ( ( s.x - x ) * rate ), 
				cast(int) ( ( s.y - y ) * rate ), 
				rot, rate, 0 );
//printf("sx %d, sy%d, left:%d, top:%d\n", s.x, left, s.y, top);
		}
		
	}
	
	/// カメラワークの設定フローを読み込みます
	void loadFlow(char[] filename) {
		
		ubyte[] mem = cast(ubyte[]) FileSys.read(filename);
		if ( mem is null ) return;
		std.stream.MemoryStream m = new std.stream.MemoryStream(mem);
		cameraFlow = new CameraFlow();
		LineParser lp = new LineParser();
		while (!m.eof) {
			ICounter counter;
			long start,end;
			int step;
			
			char[] linebuf = cast(char[]) m.readLine();
			lp.setLine(linebuf);

			if ( lp.isMatch(cast(char[]) C_PREFIX_POS) ) {
				cameraFlow.x ~= cast(int) lp.getNum(0);
				cameraFlow.y ~= cast(int) lp.getNum(0);
				
			} else if ( lp.isMatch(cast(char[]) C_PREFIX_ROT) ) {
				counter = new FreeLineCounter();
				// start
				start = lp.getNum(0);
				
				// end
				end = lp.getNum(0);
				
				// stap
				step = cast(int) lp.getNum(0);

				counter.set( cast(int) (start), cast(int) (end), step );

				cameraFlow.rotPoints ~= cameraFlow.x.length -1;
				cameraFlow.rot ~= counter;
			} else if ( lp.isMatch(cast(char[]) C_PREFIX_RATE) ) {
				
				counter = new FreeLineCounter();
				// start
				start = lp.getNum(0);
				
				// end
				end = lp.getNum(0);
				
				// stap
				step = cast(int) lp.getNum(0);
				
				counter.set( cast(int) (start), cast(int) (end), step );
				
				cameraFlow.ratePoints ~= cameraFlow.x.length -1;
				cameraFlow.rate ~= counter;
			} else {
				continue;
			}
		}

		Log.print("CameraFlow : %s frames", cameraFlow.x.length);
	}
	
	
	/// 被写体を登録する
	int addSubjectSimple(Texture t, int x, int y) {
		Subject s = new Subject();
		s.setTexture(t);
		s.x = x;
		s.y = y;
		subjects ~= s;
		return subjects.length;
	}
	
	/// 番号で指定して被写体データを取得する
	Subject getSubject(int no) {
		return subjects[no];
	}
	
	/**
		被写体データの属性
	*/
	static class Subject {
		int x;				// 描画位置x
		int y;				// 描画位置y
		float rate;			// 拡大レート
		float rot;			// 回転角

		/// テクスチャを設定する
		void setTexture(Texture t) {
			texture = t;
			sx = cast(int) t.getWidth();
			sy = cast(int) t.getHeight();		
		}
		
		/// 被写体に付属するサブオブジェクトを追加する
		void addSubSubject(Texture t, int x_, int y_) {
			Subject s = new Subject();
			s.setTexture(t);
			s.x = x_;
			s.y = y_;
			
			sub ~= s;
		}
		
		/// テクスチャの取得
		Texture getTexture() {
			return texture;
		}
	
	private :
		Texture texture;	// テクスチャ
		Subject[] sub;
		int sx;
		int sy;
	}
	
	/**
		カメラのワークフロー属性
	*/
	static class CameraFlow {
		int[] x;
		int[] y;
		
		uint[] ratePoints;	// 拡大を反映する位置
		uint[] rotPoints;	// 回転を反映する位置
		
		ICounter[] rate;	// 拡大
		ICounter[] rot;	// 回転

		ICounter nextRot() {
			return rot[rotCount++];
		}
		
		ICounter nextRate() {
			return rate[rateCount++];
		}
		
		private :
			int rotCount;
			int rateCount;			
	}
	
	/// コンストラクタ
	this(char[] filename, int column, int row) {
		ct = new ConbineTexture();
		TextureLoader tl = new TextureLoader();
		
		tl.setCacheSize(-1);
		tl.loadDefRW( FileSys.read(filename) );
		Texture[][] mat;
		
		int i;
		for (int j = 0; j < column; ++j) {
			Texture[] imgRow;
			for (int k = 0; k < row; ++k) {
				imgRow ~= tl.get(i++);
				assert ( !(imgRow[length-1] is null) );
			}
			mat ~= imgRow;
		}
		
		ct.setTextureMatrix(mat);
	}

	///	静的コンストラクタ
	static this() {
		sin = SinTable.get();
	}
	
private:
	static SinTable sin;	// 角度を求める
	
	Subject[] subjects;		// 被写体配列
	CameraFlow cameraFlow;	// カメラのワークフロー
	Texture texBase;
	ConbineTexture ct;
	FixTimer timer;		// カメラを停止している時間
	
	ICounter rot;
	ICounter rate;
	
	int		speed = 1;
	float 	d_rate;
	int		d_rot;
	int		d_x;
	int		d_y;
	
	uint count;		// 現在のフレームカウント
}



class WarkCreater {
	
	bool m_bCatch;		// ポイントをつかんでいるか


	/// 録画処理
	void onMove(Screen screen) {
		
		mouse.update();
		
		/* 拡大率の変更 */
		if ( mouse.isPress( MouseInput.button.wheel_up ) ) {
			rate += 0.01f;
		} else if ( mouse.isPress( MouseInput.button.wheel_down ) ) {
			rate -= 0.01f;
		}
		
		/* ダブルクリックされたら記録 */
		if ( mouse.isLButtonDClick() ) {
			mouse.getPos(mx,my);
			
			inputx ~= mx;
			inputy ~= my;
			
			calcSpline();
		} else {

			if ( m_bCatch ) {
				mouse.getPos(mx,my);
				inputx[catchIndex] = mx;
				inputy[catchIndex] = my;
			}

			/* 新規押し下げ */
			if ( !bPush && mouse.isPress( MouseInput.button.left ) ) {
				bPush = true;
				
				m_bCatch = getNearPoint( mx, my );

			} else if ( !mouse.isPress( MouseInput.button.left ) ) {
				bPush = false;

				/* 点を捕捉していたなら再計算 */
				if ( m_bCatch ) {
					calcSpline();
				}

				m_bCatch = false;
			}
			
			// キー入力
			key.update();
			if ( !bPressKey && key.isPush(8) ) {
				bPressKey = true;
				mouse.getPos(mx,my);
				
				for (int i = 0; i < 5; ++i) {
					inputx ~= mx + (rand.get(6) - 3);
					inputy ~= my + (rand.get(6) - 3);
				}

				calcSpline();
				
				Log.print("camera move");
			
				
			} else if ( !key.isPush(8) ) {
				bPressKey = false;
			}

		}
		
	}
	
	// 軌跡描画
	void onDraw(Screen screen) {

		screen.setColor(255, 255, 255);
		
		screen.blt(baseTex, 0, 0);
		
		mouse.getPos(mx,my);
		
		float rateW = cast(float) screen.getWidth() / cast(float) (ox);
		screen.bltRotate( flame, mx, my, 0, rateW, 4 );
		
		// フレーム描画

		if (inputx.length < 1) return;

		/* スプライン描画 */
		screen.setColor(255, 255, 255);
		for (int i = 0; i < spliney.length; ++i) {
			screen.drawPolygon(
				splinex[i]-LC_RECT_SIZE_SPLINE, spliney[i]-LC_RECT_SIZE_SPLINE,
				splinex[i]+LC_RECT_SIZE_SPLINE, spliney[i]-LC_RECT_SIZE_SPLINE,
				splinex[i]+LC_RECT_SIZE_SPLINE, spliney[i]+LC_RECT_SIZE_SPLINE,
				splinex[i]-LC_RECT_SIZE_SPLINE, spliney[i]+LC_RECT_SIZE_SPLINE
				);
		}

		/* サンプル点描画 */
		screen.setColor(255, 0, 0);
		for (int i = 0; i < inputx.length; ++i) {
			screen.drawPolygon(
				inputx[i]-LC_RECT_SIZE, inputy[i]-LC_RECT_SIZE,
				inputx[i]+LC_RECT_SIZE, inputy[i]-LC_RECT_SIZE,
				inputx[i]+LC_RECT_SIZE, inputy[i]+LC_RECT_SIZE,
				inputx[i]-LC_RECT_SIZE, inputy[i]+LC_RECT_SIZE
				);
		}

	}
	
	/* スプライン計算 */
	void calcSpline() {
		PointInt[] pts;
		Point[] ptSpline;

		for ( int i = 0; i < inputx.length; ++i) {
			PointInt pt;
			pt.x = inputx[i];
			pt.y = inputy[i];
			pts ~= pt;
		}
		
		ptSpline = BSpline.calcBSpline( pts, 20 );
		
		/* サンプルが少ないとまともな計算結果は返さないのだ */
		if ( ptSpline is null ) return;
		
		splinex = null;
		spliney = null;
		for ( int i = 0; i < ptSpline.length; ++i) {
			splinex ~= cast(int) ptSpline[i].x;
			spliney ~= cast(int) ptSpline[i].y;
		}
		
	}
	
	/// 保存する
	// screenW,screenHは実際に使用する際の画面サイズ
	void saveConv(int screenW, int screenH) {
		static bool flag = false;
		
		if (inputx.length <= 4 || flag) return; 
		char[] buffer;
		int[] x;
		int[] y;
		PointInt[] pts;
		Point[] ptSpline;

/*		// 補完
		int x0,x1,x2;
		int y0,y1,y2;
		int tx,ty;
		for ( int i = 0; i < inputx.length-2; ++i) {
			x0 = inputx[i];
			x1 = inputx[i+1];
			x2 = inputx[i+2];
			y0 = inputy[i];
			y1 = inputy[i+1];
			y2 = inputy[i+2];
			tx = x0 + ( ( (x1-x0) + (x2-x1) ) / 2);
			ty = y0 + ( ( (y1-y0) + (y2-y1) ) / 2);
			
			printf("org x:%d, org y:%d\n", tx, ty);

			inputx[i+1] = tx;
			inputy[i+1] = ty;
		}
*/

		int xHalf = ox / 2;
		int yHalf = oy / 2;
		float rateW = cast(float) (ox) / cast(float) (wWidth);  
		float rateH = cast(float) (oy) / cast(float) (wHeight);  
		
		for ( int i = 0; i < inputx.length-1; ++i) {
			PointInt pt;
			x ~= screenW/2 + cast(int) ( xHalf - (inputx[i]*rateW) );
			y ~= screenH/2 + cast(int) ( yHalf - (inputy[i]*rateH) );
			
			
			pt.x = x[length-1];
			pt.y = y[length-1];
			
			pts ~= pt;
		}
		
		ptSpline = BSpline.calcBSpline( pts, 20 );
		
		inputx = null;
		inputy = null;
		for ( int i = 0; i < ptSpline.length; ++i) {
			inputx ~= cast(int) ptSpline[i].x;
			inputy ~= cast(int) ptSpline[i].y;
			buffer ~= CameraWork.C_PREFIX_POS;
			buffer ~= std.string.toString( cast(int) ptSpline[i].x  );	
			buffer ~= ",";	
			buffer ~= std.string.toString( cast(int) ptSpline[i].y );	
			buffer ~= "\r\n";	
		}

		FileSys.write(cast(char[]) "mouse.txt", cast(void[]) buffer);
		flag = true;
		
	}
	
	/// 保存する
	// screenW,screenHは実際に使用する際の画面サイズ
	void save(int screenW, int screenH) {
		static bool flag = false;
		
		if (inputx.length <= 4 || flag) return; 
		char[] buffer;
		int[] x;
		int[] y;
		PointInt[] pts;
		Point[] ptSpline;

/*		// 補完
		int x0,x1,x2;
		int y0,y1,y2;
		int tx,ty;
		for ( int i = 0; i < inputx.length-2; ++i) {
			x0 = inputx[i];
			x1 = inputx[i+1];
			x2 = inputx[i+2];
			y0 = inputy[i];
			y1 = inputy[i+1];
			y2 = inputy[i+2];
			tx = x0 + ( ( (x1-x0) + (x2-x1) ) / 2);
			ty = y0 + ( ( (y1-y0) + (y2-y1) ) / 2);
			
			printf("org x:%d, org y:%d\n", tx, ty);

			inputx[i+1] = tx;
			inputy[i+1] = ty;
		}
*/

		int xHalf = ox / 2;
		int yHalf = oy / 2;
		float rateW = cast(float) (ox) / cast(float) (wWidth);  
		float rateH = cast(float) (oy) / cast(float) (wHeight);  
		
		for ( int i = 0; i < inputx.length-1; ++i) {
			PointInt pt;
			x ~= cast(int) (inputx[i] * rateW);
			y ~= cast(int) (inputy[i] * rateH);
			
			
			pt.x = x[length-1];
			pt.y = y[length-1];
			
			pts ~= pt;
		}
		
		ptSpline = BSpline.calcBSpline( pts, 20 );
		
		inputx = null;
		inputy = null;
		for ( int i = 0; i < ptSpline.length; ++i) {
			inputx ~= cast(int) ptSpline[i].x;
			inputy ~= cast(int) ptSpline[i].y;
			buffer ~= CameraWork.C_PREFIX_POS;
			buffer ~= std.string.toString( cast(int) ptSpline[i].x  );	
			buffer ~= ",";	
			buffer ~= std.string.toString( cast(int) ptSpline[i].y );	
			buffer ~= "\r\n";	
		}

		FileSys.write(cast(char[]) "mouse.txt", cast(void[]) buffer);
		flag = true;
		
	}	
	
	/// オリジナル画像のサイズを設定する
	void setPicSize(int x_, int y_) {
		ox = x_;
		oy = y_;
	}
	
	/// サンプル画像を設定する
	void setBaseTexture(Texture t) {
		baseTex = t;
	}
	
	/// コンストラクタ
	this (MouseInput mouse_, int ww, int wh) {
		this.mouse = mouse_;
		mark = new Texture();
		mark.load(cast(char[]) "TEST/camera/mark.png");
		flame = new Texture();
		flame.load(cast(char[]) "TEST/camera/flame.png");
		key = new Key2();
		
		wWidth = ww;
		wHeight = wh;
	}
	
	static this() {
		rand = new Rand();
		rand.randomize();
	}
	
private :
	private static const int LC_RECT_SIZE = 3;
	private static const int LC_RECT_SIZE_SPLINE = 1;

	static Rand rand;

	float rate = 1.0f;
	bool bPush;
	
	int ox;		// 対象画像の大きさｘ
	int oy;		// 対象画像の大きさｙ
	
	int wWidth;		// 現在のウィンドゥサイズ
	int wHeight;
	
	Key2 key;
	bool bPressKey;

	int mx,my;
	Texture mark;
	Texture flame;
	Texture baseTex;
	MouseInput mouse;
	int[] inputx;
	int[] inputy;
	
	int[] splinex;
	int[] spliney;
	
	int catchIndex = -1;
	
	/// 近傍の点を取得する
	bool getNearPoint(int x_, int y_) {
		
		int absx,absy;
		int min = int.max;
		int index = -1;
		for (int i = 0; i < inputx.length; ++i) {
			absx = inputx[i] - x_;
			absy = inputy[i] - y_;
			
			if (absx<0) absx = -absx; 
			if (absy<0) absy = -absy; 
			
			if ( min > ( absx + absy ) ) {
				min = absx + absy;
				index = i;
			}
		}
		
		if ( min < 10 ) {
			catchIndex = index;
			return true;
		}
		
		return false;
	}
	
	
	
	
	
	
	
	

}