module y4d_draw.font;

private import SDL;
private import SDL_ttf;
private import ytl.y4d_result;
private import std.string;
private import ytl.singleton;
private import y4d_draw.surface;
private import y4d_aux.filesys;
private import y4d_aux.cacheobject;

private import std.utf;

version(Win32) {
/+
// フォントフォルダの取得メソッド
//	win95の場合はIE4.0以降をインストールしてないとダメ
//	win98以降は使える。
extern(Windows) export /*BOOL*/ int SHGetSpecialFolderPath(
	/*HWND*/ uint hwndOwner,
	/*LPTSTR*/ char* lpszPath,
	int nFolder,
	/*BOOL*/ int fCreate
	);
	//	このAPI使うとShell32.libをlinkせにゃいかん
+/

//	↑使えねぇぇぇぇ
extern(Windows) export uint GetWindowsDirectoryA(
  /*LPTSTR*/ char* lpBuffer,  // buffer for system directory
  uint uSize		// size of directory buffer
);

}

///	文字の描画用クラス
/**
	文字の描画にはttf(true type font)を使用します。
	文字の描画ごとに、ttfを展開して、テクスチャーorサーフェースを
	再構築していたのではすごく遅いので、何らかの方法でキャッシュして
	使うことをお勧めします。

<PRE>
	Screen screen = new Screen;
	screen.setVideoMode(640,480,0);

	char[] fontname = "GT200001.TTF";
	Font font = new Font;
	font.open(fontname,30);
	Texture fontTxt = new Texture;
	fontTxt.setSurface(font.drawBlended("hoge hoge 天才にょ"));

	while (GameFrame.pollEvent()==0){
		screen.blt(fontTxt,0,0);
		screen.update();
	}

</PRE>
*/
class Font : ICacheObject {
	/// フォントをオープンする
	/**
		指定するのは、フォント名。
		indexは、ttc(true type collection＝ttfが複数入ったフォントファイル)の
		ときに、何番目のものかを指定する。0番から始まる番号。<BR>

		Windowsならフォルダは決まっているので、カレントとパスの通っている
		ところに無ければ、そこから取得する。<BR>

		Linuxのほうは、カレント、あとパスの通っているところから取得する。
<PRE>
		例)
		Windowsでは、
			msgothic.ttc : MSゴシック
			msmincho.ttc : MS明朝
</PRE>

		Linuxの場合、日本語フォントを用いる場合は、
		GT書体 (東京大学多国語処理研究会) 
		http://www.l.u-tokyo.ac.jp/GT/<BR>
		などから持ってきて、msgothic.ttcやmsmincho.ttcとリネームして
		カレントフォルダに配置しておくようにしてください。

		フォントフォルダの検索に用いているAPIは、
		win95の場合はIE4.0以降をインストールしてないとダメ
		win98以降は使える。<BR>
*/
	y4d_result open(char[] fontname,int fontsize,int index) {
		close();
		y4d_result hr = open(FileSys.readRW(fontname),fontsize,index);
		if (hr==0) return hr;

version (Win32) {
		//	カレントにないので、SHGetSpecialFolderPath関数を使って
		//	フォントディレクトリを取得して頑張ってみる。
		scope char[] fontpath = new char[255];

		//	max_pathについては、こちら：
		//	http://risky-safety.org/~zinnia/doc/maxpath.html

		/*
		// CSIDL_FONTS : 0x0014
		フォントフォルダは仮想フォルダなので取得不可(;´Д`)
		http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/enums/csidl.asp

		*/
		int l = GetWindowsDirectoryA(cast(char*)&fontpath[0],255);
		fontpath.length = l;
		if (l==0) {
			return y4d_result.win32api_error; // フォルダ取得に失敗..
		}
		fontpath ~= r"\fonts";
		hr = open(FileSys.readRW(FileSys.makeFullName(fontpath,fontname)),
			fontsize,index);
}
		return hr;
	}

	/// フォントをオープンする
	/** openのindex=0決め打ちバージョン。 */
	y4d_result	open(char[] fontname,int fontsize) {
		return open(fontname,fontsize,0);
	}

	///	フォントをオープンする
	/**
		MSゴシックとMS明朝をオープンするバージョン。<BR>

		0 :	msgothic.ttc : MSゴシック <BR>
		1 :	msmincho.ttc : MS明朝 <BR>
		2 :	msgothic.ttc : MSPゴシック <BR>
		3 :	msmincho.ttc : MSP明朝 <BR>

		をオープンします。<BR>
	*/
	y4d_result open(int openNo,int fontsize) {

		switch(openNo) {
		case 0 : return open("msgothic.ttc",fontsize,0);
		case 1 : return open("msmincho.ttc",fontsize,0);
		case 2 : return open("msgothic.ttc",fontsize,1);
		case 3 : return open("msmincho.ttc",fontsize,1);
		default: throw new Exception("Font#open. No such option.");
		//	それ以外だと例外が投げられる
		}
	//	このttcの中身は、
	//	index : 0=普通の , 1=プロポーショナルフォント , 2 = UI用。
		assert(false);
	}

	///	フォントをオープンする(RWopsから)
	/**
		indexは、ttc(true type collection＝ttfが複数入ったフォントファイル)の
		ときに、何番目のものかを指定する。0番から始まる番号。<BR>
	*/
	y4d_result	open(FileSys.RWops rw,int fontsize,int index){
		close();
		if (fontsize<=0) return y4d_result.invalid_parameter;

		//	23pt未満であれば倍率掛けて拡大しておく
		rate = 1;
		while (fontsize * rate < 23) ++rate;

		if (rw.filename){
			font = TTF_OpenFontIndex(std.string.toStringz(rw.filename),
				fontsize*rate,index);
		} else if (rw.rwops) {
			font = TTF_OpenFontIndexRW(rw.rwops,1,fontsize*rate,index);
		} else {
			return y4d_result.file_not_found;	//	読み込めていない
		}

		if (font) {
			//	読みこめたので、これをファイルサイズとする。
			bufferSize = rw.data.length;
		}

		return font ? y4d_result.no_error : y4d_result.file_read_error;
	}

	/// フォントをオープンする
	/** openのindex=0決め打ちバージョン。 */
	y4d_result	open(FileSys.RWops rw,int fontsize){
		return open(rw,fontsize,0);
	}

	///	フォントを閉じる(解放する)
	void	close() {
		if (bOpen) {
			TTF_CloseFont(font);
			bOpen = false;
			font = null;
			bufferSize = 0;
		}
	}

	///	文字の色を設定する
	/**
		各、RGB、0～255までで指定する。
		drawを呼び出すまでに設定すればok。<BR>

		設定しなければ、(255,255,255) すなわち、白が指定されている状態になる。
		設定された値は、再設定されるまで有効。
	*/
	void	setColor(int r_,int g_,int b_){
		color.r = cast(ubyte) r_; 
		color.g = cast(ubyte) g_; 
		color.b = cast(ubyte) b_;
	}

	///	文字を描画したサーフェースを返します。
	/**
		漢字は出ない。
		漢字を出したいときはUTF8かUnicodeバージョンを用いること。<BR>

		生成されるサーフェースは、αなし。
	*/
	Surface	drawSolid(char[] str) {
		Surface surface = new Surface;
		drawSolid(surface,str);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawSolid(Surface surface,char[]str){
		if (font) {
			SDL_Surface* image = TTF_RenderText_Solid(
				font,std.string.toStringz(str), color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	文字を描画したサーフェースを返します。
	/**
		画質はあらいです。そのかわり早いです。<BR>

		生成されるサーフェースは、αなし。
	*/
	Surface	drawSolidUTF8(char[] str) {
		Surface surface = new Surface;
		drawSolidUTF8(surface,str);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawSolidUTF8(Surface surface,char[]str){
		if (font) {
			SDL_Surface* image = TTF_RenderUTF8_Solid(
				font,std.string.toStringz(str), color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	文字を描画したサーフェースを返します。
	/**
		画質はあらいです。そのかわり早いです。<BR>

		生成されるサーフェースは、αなし。
	*/
	Surface	drawSolidUnicode(wchar[] str) {
		Surface surface = new Surface;
		drawSolidUnicode(surface,str);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawSolidUnicode(Surface surface,wchar[]str){
		if (font) {
			SDL_Surface* image = TTF_RenderUNICODE_Solid(
				font,cast(ushort*) str, color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	文字を描画したサーフェースを返します。
	/**
		drawSolid のα付きサーフェースを返すバージョン。
		綺麗なかわりに、遅い。
		漢字は出ない。漢字を出したいときはUTF8かUnicodeバージョンを用いること。<BR>
	*/
	Surface	drawBlended(char[] str) {
		Surface surface = new Surface;
		drawBlended(surface,str);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawBlended(Surface surface,char[]str){
		if (font) {
			SDL_Surface* image = TTF_RenderText_Blended(
				font,std.string.toStringz(str), color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	文字を描画したサーフェースを返します。
	/**
		drawSolid のα付きサーフェースを返すバージョン。
		綺麗なかわりに、遅い。
	*/
	Surface	drawBlendedUTF8(char[] str) {
		Surface surface = new Surface;
		drawBlendedUTF8(surface,str);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawBlendedUTF8(Surface surface,char[]str){
		if (font) {
			SDL_Surface* image = TTF_RenderUTF8_Blended(
				font,std.string.toStringz(str), color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	文字を描画したサーフェースを返します。
	/**
		drawSolid のα付きサーフェースを返すバージョン。
		綺麗なかわりに、遅い。
	*/
	Surface	drawBlendedUnicode(wchar[] str,bool conv=true) {
		Surface surface = new Surface;
		drawBlendedUnicode(surface,str,conv);
		return surface;
	}

	///	サーフェースに文字列を描画します。
	/**
		サーフェースをnewしたくないときは、こちらを使うよろし。
	*/
	void	drawBlendedUnicode(Surface surface,wchar[]str,bool conv=true){
		if (font) {
			ushort* text;
			if (conv) {
				text = cast(ushort*) toUTF32(str);
			} else {
				text = cast(ushort*) .toUTF16(str);
			}
			
			SDL_Surface* image = TTF_RenderUNICODE_Blended(
				font, text, color);
			ratecheck(image);
			surface.setSurface(image);
		} else {
			surface.setSurface(null);
		}
	}

	///	読み込んでいるフォントのファイルサイズを返す
	ulong getBufferSize() { return bufferSize; }

	this() { getInit().init(); setColor(255,255,255); }

	~this() { close(); }

protected:
	TTF_Font* font;		//!< フォント実体

private:
	bool	bOpen;
	SDL_Color color;

	/**
		フォント倍率

		(22pt以下のフォントを作成すると、SDL_ttfでは化けるので
		22pt以下の場合は、n倍して、23を超えるようにする。
		これは、そのための倍率である)
	*/
	int		rate;

	void	ratecheck(inout SDL_Surface* image) {
		//	rateが1でなければ、サーフェースを縮小しなければならない。
		if (rate==1) { return ; } // ok
		if (!image) { return ; } // surfaceの作成に失敗しちょる

		// 1/rateのサイズのサーフェースを作ることからはじめよう
		int x,y;
		x = image.w; y = image.h;
		x /= rate; y /= rate;

		//	これが rateで割り切れない時のことは知らネ
		if (x==0 || y==0) return ;

		bool bAlpha = cast(bool) (image.format.BitsPerPixel == 32);
		/*
			SDL_ttfの返すサーフェースは、αつきなら32bpp,αなしなら8bppと
			決まっちょる
		*/

		//	SDLには縮小する関数が無いので、
		//	DIBを作って、それを縮小することにする
		//	SDL_gfxを持ってきてもいいのだが、そこまで大がかりでもないので..

		SDL_Surface* image2;
		if (Surface.createDIBstatic(image2,image.w,image.h,bAlpha)!=0)
			return ; //	作成失敗

		if (bAlpha) {
			//	αを無効化しておかないとARGB→ARGBのbltで
			//	αを考慮して転送しやがる
			SDL_SetAlpha(image,0,0);
		}

		if (SDL_BlitSurface(image,null,image2,null)!=0) {
			SDL_FreeSurface(image2);
			return ; // 転送失敗
		}

		SDL_Surface* image2s;
		if (Surface.createDIBstatic(image2s,x,y,bAlpha)!=0) {
			SDL_FreeSurface(image2);
			SDL_FreeSurface(image2s);
			return ; //	作成失敗
		}

		//	縮小するためにlockする
		if (SDL_LockSurface(image2)!=0) {
			SDL_FreeSurface(image2);
			SDL_FreeSurface(image2s);
			return ; // lock失敗
		}
		if (SDL_LockSurface(image2s)!=0) {
			SDL_UnlockSurface(image2s);
			SDL_FreeSurface(image2);
			SDL_FreeSurface(image2s);
			return ; // lock失敗
		}

		//	縮小ルーチン
		if (bAlpha) {
			uint rt,gt,bt,at;
			uint rr = rate*rate;
			uint* pixels1 = cast(uint*)image2.pixels;
			uint* pixels2 = cast(uint*)image2s.pixels;
			for(int y1=0;y1<image2s.h;++y1){
				uint xx;
				for(int x1=0;x1<image2s.w;++x1){
					//	ピクセルの平均を求める
					uint r,g,b,a;
					uint* pixels1t = pixels1;
					for(int j=0;j<rate;++j){
						for(int i=0;i<rate;++i){
							uint p = pixels1t[xx+i];
							rt = p & image2.format.Rmask;
							rt >>= image2.format.Rshift;
							r+= rt;
							gt = p & image2.format.Gmask;
							gt >>= image2.format.Gshift;
							g+= gt;
							bt = p & image2.format.Bmask;
							bt >>= image2.format.Bshift;
							b+= bt;
							at = p & image2.format.Amask;
							at >>= image2.format.Ashift;
							a+= at;
						}
						pixels1t = cast(uint*)((cast(byte*)pixels1) + image2.pitch);
					}
					r /= rr; g /= rr; b /= rr; a /= rr;
					//	↑これだと切り捨てすぎかも..

					pixels2[x1] = (r << image2.format.Rshift)
						+		  (g << image2.format.Gshift)
						+		  (b << image2.format.Bshift)
						+		  (a << image2.format.Ashift);
					xx+=rate;

				}
				pixels1 = cast(uint*)((cast(byte*)pixels1) + image2.pitch*rate);
				pixels2 = cast(uint*)((cast(byte*)pixels2) + image2s.pitch);
			}
		} else {
		//	24bppのはず
			uint rr = rate*rate;
			ubyte* pixels1 = cast(ubyte*)image2.pixels;
			ubyte* pixels2 = cast(ubyte*)image2s.pixels;
			for(int y1=0;y1<image2s.h;++y1){
				uint xx,x2;
				for(int x1=0;x1<image2s.w;++x1){
					//	ピクセルの平均を求める
					uint r,g,b;
					ubyte* pixels1t = pixels1;
					for(int j=0;j<rate;++j){
						uint xxx = xx;
						for(int i=0;i<rate;++i){
							r += pixels1t[xxx+i+0];
							g += pixels1t[xxx+i+1];
							b += pixels1t[xxx+i+2];
							xxx += 3;
						}
						pixels1t = pixels1 + image2.pitch;
					}
					r /= rr; g /= rr; b /= rr;

					pixels2[x2+0] = cast(ubyte) r;
					pixels2[x2+1] = cast(ubyte) g;
					pixels2[x2+2] = cast(ubyte) b;

					x2 += 3;
					xx += 3*rate;
				}
				pixels1 = pixels1 + image2.pitch*rate;
				pixels2 = pixels2 + image2s.pitch;
			}
		}

		//	さすがにunlockは失敗しないでそ．．
		SDL_UnlockSurface(image2);
		SDL_UnlockSurface(image2s);

		SDL_FreeSurface(image);
		SDL_FreeSurface(image2);
		image = image2s; // 書き換えて返す
	}

	///	フォントの初期化用クラス
	FontInitializer getInit() { return initializer.get(); }
	singleton!(FontInitializer) initializer;
	
	static class FontInitializer {
		void init() { if (!bInit) { TTF_Init(); bInit=true; } }
		~this() { TTF_Quit(); }
	private:
		bool bInit;
	}

	ulong bufferSize;
}
