module y4d_draw.texturevector;

private import y4d_draw.texture;
private import y4d_draw.texturebase;
private import y4d_draw.drawcontext;
private import y4d_draw.drawbase;

///	テクスチャーのvector
/**
	ひとつの文字がひとつのテクスチャーであるとき、
	文字列は、textureのvectorとなる。これを管理するのがこのクラスである。
*/
class TextureVector : ITextureBase {

	///	テクスチャーの構造体(TextureInfo)を更新する
	/**
		このメソッドを呼び出さない限り、情報は更新されないので
		正しく表示されない。Texture情報を書き換えた場合、必ず最後に
		呼び出すようにしてください。
	*/
	void	update() {
		//	ローカル変数に代入したほうが速い(だろう)
		float width_  = width;
		float height_ = height;
		foreach(TextureInfo info;infos){
			info.left  = info.posX / width_;
			info.leftR = 1 - info.left;
			info.top   = info.posY / height_;
			info.topR  = 1 - info.top;
			info.right = (info.posX+info.width) / width_;
			info.rightR= 1 - info.right;
			info.bottom  = (info.posY+info.height) / height_;
			info.bottomR = 1 - info.bottom;
		}
		//	update完了
	}

	///	テクスチャを連結する
	/**
		他のテクスチャを (x,y) の位置に連結する
		連結後の TextureVector のサイズは、被覆部分を考慮しての最大矩形。
		例)
			横50×縦30のTextureVectorに、横100×縦200のTextureVectorを
			(0,20)の位置の連結した場合、連結後のTextureVectorは、
			横100×縦220のサイズ。

		ITextureBase は、Texture , TextureVector の元となるクラスなので、
		通常のTexture でも TextureVector でも連結できることを意味する。

		x,yは負を指定しても良いが、その場合はwidth,heightには反映されない。
	*/
	void	add(ITextureBase texture,int x,int y)
	{
		TextureInfo info = new TextureInfo;

		info.texture= texture;
		info.posX	= x;
		info.posY	= y;
		info.width	= texture.getWidth();
		info.height = texture.getHeight();

		infos ~= info;

		/* 連結後のtextureサイズを計算しなくては．． */
		float w = x + texture.getWidth();
		float h = y + texture.getHeight();

//		width  = max(width,w);
//		height = max(height,h);
		if (width < w) width = w;
		if (height < h) height = h;
	}

	///	テクスチャの幅を得る
	float	getWidth() { return width*rate; }

	///	テスクチャの高さを得る
	float	getHeight() { return height*rate; }

	///	倍率を取得する
	float	getRate() { return rate; }

	///	倍率を設定する(default:1.0)
	/**
		getWidth / getHeight で返される値は、
		setWidth / setHeihgt や 連結等によって得られたテクスチャサイズ
		から、ここで設定した倍率の掛かった値になる。

		結果として描画するときにこの倍率の掛かったサイズで描画される。
		連結時には、ここで設定された倍率は考慮されない。

		※　よって連結後、update前までにsetRateを呼び出して使うと良いだろう。
	*/
	void	setRate(float rate_) { rate = rate_; }

	///	それぞれのテクスチャーの存在する位置を表す構造体。
	/**
		(left,top),(right,bottom)は、あるひとつのテクスチャの
		存在する位置(矩形)を(sizeX,sizeY)との比率で表したもの。<BR>

		なお、leftR = 1-leftである。<BR>
<PRE>
		例) 矩形(x1,y1)-(x2,y2)に、bltしたい場合、
			矩形(left*x1 + leftR*x2 , top*y1 + topR*y2)
				- (right*x1 + rightR*x2 , bottom*y1 + bottomR*y2)
		で求まる。
</PRE>
	*/
	class TextureInfo {
		///	このテクスチャの幅
		float	width,height;

		///	描画位置
		/**
			TextureVector.updateでは、このwidth,height,posX,posYと
			TextureVector.widthとheightから
			left,leftR,top,topR,right,rightR,bottom,bottomRを算出します。
		*/
		float	posX,posY;

		///	描画すべきテクスチャー
		ITextureBase texture;

		float	left,leftR;
		float	top,topR;
		float	right,rightR;
		float	bottom,bottomR;
	}

	///	リセット
	/**
		内部的に保持しているテクスチャをリセットする。<BR>

		width = 0 , height = 0; <BR>
		rate = 1.0; <BR>
		infos = null;
	*/
	void	reset() { width = height = 0; rate = 1.0; infos = null; }

	///	内部的に保持しているテクスチャを返す
	TextureInfo[] getTextureInfo() { return infos; }

	this() { reset(); }

protected:
	//	visitor パターンなのだ
	void	blt(DrawContext drawContext,float x,float y)
	{
		float w = width;
		float h = height;
		foreach(TextureInfo info;infos) {
			info.texture.blt(drawContext,x+info.left*w,y+info.top*h);
		}
	}

	void	blt(DrawContext drawContext,float x,float y,Rect* srcRect)
	{
		Point[4] points;
		float w,h;

		if (srcRect) {
			w = srcRect.right - srcRect.left;
			if (w<0) w = -w;
			h = srcRect.bottom - srcRect.top;
			if (h<0) h = -h;
		} else {
			w = width;
			h = height;
		}

		points[0].setPoint(x,y);
		points[1].setPoint(x+w,y);
		points[2].setPoint(x+w,y+h);
		points[3].setPoint(x,y+h);

		blt(drawContext,srcRect,points);
	}

	void	blt(DrawContext drawContext,float x,float y,
		Rect* srcRect,Size* dstSize){
		//	dstSize == nullは..サポートせんでもええですか..

		Point[4] points;

		points[0].setPoint(x,y);
		points[1].setPoint(x+dstSize.cx-1,y);
		points[2].setPoint(x+dstSize.cx-1,y+dstSize.cy-1);
		points[3].setPoint(x,y+dstSize.cy-1);

		blt(drawContext,srcRect,points);
	}

	void	blt(DrawContext drawContext,Rect* srcRect,Point[4] dstPoint){

	//	まだバグあるかも知れないのでdebug用のprintは消さずにおこう..

// printf("dstpoint\n");
// debugPrint(dstPoint);
		float w = width;
		float h = height;

//printf("%f,%f : w,h\n",w,h);

		Rect sr;
		if (srcRect){
//printf("%f,%f,%f,%f : srcRect / srcRect.left,srcRect.right,srcRect.bottom,srcRect.top \n",srcRect.left,srcRect.right,srcRect.bottom,srcRect.top);
			sr.setRect(	srcRect.left/w,srcRect.top/h,
						srcRect.right/w,srcRect.bottom/h);
//printf("%f,%f,%f,%f : 0 / sr.right,sr.left,sr.bottom,sr.top \n",sr.right,sr.left,sr.bottom,sr.top);

//printf("%f,%f,%f : ok? \n",srcRect.right,w,srcRect.right/w);

		//	転送元は0.0～1.0にclipping
			if (sr.left<0.0) sr.left = 0.0;
			if (sr.right<0.0) sr.right = 0.0;
			if (sr.top<0.0) sr.top = 0.0;
			if (sr.bottom<0.0) sr.bottom = 0.0;
			if (sr.left>1.0) sr.left = 1.0;
			if (sr.right>1.0) sr.right = 1.0;
			if (sr.top>1.0) sr.top = 1.0;
			if (sr.bottom>1.0) sr.bottom = 1.0;
		} else {
			sr.setRect( 0,0, 1.0 , 1.0);
//printf("%f,%f,%f,%f : 1 / sr.right,sr.left,sr.bottom,sr.top \n",sr.right,sr.left,sr.bottom,sr.top);
		}

		/**
			左上がd[0],右上がd[1],右下がd[2],左下がd[3]のとき、
			u,vをpに返す。
		*/
		void calcUV(Point[4] d,float u,float v,Point* p){
			//	(x1,y1) = d0 + (d0_1)u 
			float x1 = d[0].x * (1-u) + d[1].x*(u);
			float y1 = d[0].y * (1-u) + d[1].y*(u);

			//	(x2,y2) = d3 + (d3_2)u 
			float x2 = d[3].x * (1-u) + d[2].x*(u);
			float y2 = d[3].y * (1-u) + d[2].y*(u);

			//	(x1,y1)-(x2,y2)をvで内分
			p.x = x1 * (1-v) + x2 * v;
			p.y = y1 * (1-v) + y2 * v;
		}

		bool bW,bH; // 水平方向の反転フラグ

		float www = sr.right - sr.left;
		float hhh = sr.bottom - sr.top;

//printf("%f,%f,%f,%f : 2 / sr.right,sr.left,sr.bottom,sr.top \n",sr.right,sr.left,sr.bottom,sr.top);
//printf("%f,%f :www,hhh \n",www,hhh);
		if (www == 0 || hhh == 0) return ;
		if (www<0) { www = -www; bW = true; }
		if (hhh<0) { hhh = -hhh; bH = true; }

//printf("%d,%d : bW,bH \n",bW,bH);

		foreach(TextureInfo info;infos) {
			Point[4] dp;	//	転送先

			Rect r;			//	転送元矩形
			Rect rr;		//	転送元矩形をテクスチャ座標で表したもの

//printf("%f,%f,%f,%f : left,right,top,bottom\n", info.left,info.right,info.top,info.bottom);

			rr.setRect(info.left,info.top,info.right,info.bottom);

			// sr のなかに含まれる矩形か?
			if (sr.left <= info.left && info.right	<= sr.right
			&&	sr.top	<= info.top  && info.bottom <= sr.bottom){
			//	含まれるのでそのまま描画
				r.setRect(0,0,info.width,info.height);
			} else {
				//	完全には含まれていないので、削り取る作業が必要
				r.setRect(0,0,info.width,info.height);

				float ww = info.width  / (info.right-info.left);
				float hh = info.height / (info.bottom-info.top);

				float t;

//printf("%f,%f,%f,%f : 1 : sr.left,info.left,r.left,r.right",sr.left,info.left,r.left,r.right);

				if (!bW){
					t = sr.left - info.left;
					if (t>0) {
						r.left += t * ww;
						rr.left = sr.left;
					}
					t = info.right - sr.right;
					if (t>0) {
						r.right -= t * ww;
						rr.right = sr.right;
					}

// printf("%f,%f,%f,%f : 1 : sr.left,info.left,r.left,r.right\n",sr.left,info.left,r.left,r.right);
// printf("%f : tL \n",tL);

					if (r.left > r.right) continue; // 表示エリアなし
				} else {
					t = sr.right - info.left;
					if (t>0) {
						r.left += t * ww;
						rr.left = sr.right;
					}
// printf("%f : t \n",t);
					t = info.right - sr.left;
					if (t>0) {
						r.right -= t * ww;
						rr.right = sr.left;
					}
// printf("%f,%f,%f,%f : 1 : sr.left,info.left,r.left,r.right\n",sr.left,info.left,r.left,r.right);
// printf("%f : t \n",t);
					if (r.left > r.right) continue; // 表示エリアなし
				}
				if (!bH){
					t = sr.top - info.top;
					if (t>0) {
						r.top += t * hh;
						rr.top = sr.top;
					}
					t = info.bottom - sr.bottom;
					if (t>0) {
						r.bottom -= t * hh;
						rr.bottom = sr.bottom;
					} else t = 0;
					if (r.top > r.bottom) continue; // 表示エリアなし
				} else {
					t = sr.bottom - info.top;
					if (t>0) {
						r.top += t * hh;
						rr.top = sr.bottom;
					} else t = 0;
					t = info.bottom - sr.top;
					if (t>0) {
						r.bottom -= t * hh;
						rr.bottom = sr.top;
					}
					if (r.top > r.bottom) continue; // 表示エリアなし
				}
			}

			float leftRM;
			float rightRM;
			if (!bW){
				leftRM	= (rr.left - sr.left) / www;
				rightRM = (rr.right - sr.left) / www;
			} else {
				leftRM	= (1 - (rr.left - sr.right)) / www;
				rightRM = (1 - (rr.right - sr.right)) / www;
			}
			float topRM;
			float bottomRM;
			if (!bH){
				topRM = (rr.top - sr.top) / hhh;
				bottomRM = (rr.bottom - sr.top) / hhh;
			} else {
				topRM = (1-(rr.top - sr.bottom)) / hhh;
				bottomRM = (1-(rr.bottom - sr.bottom)) / hhh;
			}

			calcUV(dstPoint,leftRM,topRM,&dp[0]);
			calcUV(dstPoint,rightRM,topRM,&dp[1]);
			calcUV(dstPoint,rightRM,bottomRM,&dp[2]);
			calcUV(dstPoint,leftRM,bottomRM,&dp[3]);

// printf("leftR,right : %f,%f \n",info.leftR,info.right);
// printf("RM : %f,%f,%f,%f:\n",leftRM,topRM,rightRM,bottomRM);

// printf("%f,%f,%f,%f:\n",tL,tR,tT,tB);
// debugPrint(dp);

			info.texture.blt(drawContext,&r,dp);
		}
	}

	//	これ、めちゃくちゃ面倒くさいですなぁ．．（´Д｀）
	/**
		未実装だ！　実装する予定は無い！
	*/
	void	blt(DrawContext src,Point[4] srcRect,Point[4] dstPoint){}

	float	width,height; //!<	このテクスチャベクター全体でのサイズ
	float	rate;		//!<	このテクスチャの倍率を設定する
	TextureInfo[] infos;//!<	テクスチャーの情報(updateを呼び出した時に更新される)

private:
	//	debug用の表示関数
	void	debugPrint(Point[4] dp){
		for(int i=0;i<4;++i){
			printf("i=%d : (%f,%f)\n",i,dp[i].x,dp[i].y);
		}
	}
}
