module util.hell2;
private import std.string;
private import std.file;
private import std.path;
private import std.cstream;
private import std.ctype;
private import std.math;
private import std.math2;
private import std.c.stdio;
private import std.format;
private import std.utf;
private import std.random;
private import SDL;
private import SDL_mixer;
private import opengl;
private import openglu;

private:
static int g_width;
static int g_height;
static int g_videoBpp;
static int g_videoFlags;
static SDL_Surface* g_screen;
static Texture [char[]] g_poolTexture;
static Texture g_fontTexture;
static Mix_Chunk* [char[]] g_poolChunk;
static Mix_Music* g_music = null;
static SDL_Surface* [int] g_poolFont;
static bool g_quit = false;
static bool g_init = false;
static ubyte g_pressMouseButtonPrev = 0;
static ubyte g_pressMouseButton = 0;
static bool[SDLK_LAST] g_pressKeyButtonPrev;
static bool[SDLK_LAST] g_pressKeyButton;
static const char[] g_logname = "run.log";
// OpenGL
//static GLUquadricObj* g_quadricObj; // ２次曲面オブジェクト

/**
 * 十字キーのプラス方向の入力判定
 */
static const AXIS_PRESSED_PLUS  =  32768 * 8 / 10;
/**
 * 十字キーのマイナス方向の入力判定
 */
static const AXIS_PRESSED_MINUS = -32768 * 8 / 10;
bool _isLeftJ(Sint16 axisX)
{
	return axisX < AXIS_PRESSED_MINUS;
}
bool _isUpJ(Sint16 axisY)
{
	return axisY < AXIS_PRESSED_MINUS;
}
bool _isRightJ(Sint16 axisX)
{
	return axisX > AXIS_PRESSED_PLUS;
}
bool _isDownJ(Sint16 axisY)
{
	return axisY > AXIS_PRESSED_PLUS;
}
static Sint16 g_joyXPrev;
static Sint16 g_joyYPrev;
static Sint16 g_joyX;
static Sint16 g_joyY;
static bool[] g_joyButtonPrev;
static bool[] g_joyButton;
static SDL_Joystick* g_joy;
static int g_joyButtonNum;
static char[] FONT_PATH = "resource/font/font.bmp";

static int fps_cnt;
static int fps_view;
static Uint32 fps_lastTick;

/**
 * ウィンドウ・フルスクリーンモードの切り替え
 */
void _toggleFullScreen()
{
	g_videoFlags ^= SDL_FULLSCREEN;
	// スクリーン初期化
	if((g_screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags)) is null)
	{
		throw new Error("_toggleFullScreen: Couldn't set video mode");
	}
	glOrtho(0, g_width, g_height, 0, -1, 1); 
	glViewport(0, 0, g_width, g_height);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	_reloadTexture();
	Hell_setAlpha(g_alpha_prev);
}

/**
 * マウスカーソル表示切替
 */
void _toggleShowCursor()
{
	if(SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) SDL_ShowCursor(SDL_DISABLE);
	else                                        SDL_ShowCursor(SDL_ENABLE);
}
/**
 * フォントの読み込み
 */
public void Hell_loadFont()
{
	g_fontTexture = new Texture(FONT_PATH, 0x00, 0xff, 0x00); // 緑を抜き色で
}

public:
const static uint HELL_BUTTON_LEFT   = SDL_PRESSED << (SDL_BUTTON_LEFT-1);
const static uint HELL_BUTTON_MIDDLE = SDL_PRESSED << (SDL_BUTTON_MIDDLE-1);
const static uint HELL_BUTTON_RIGHT  = SDL_PRESSED << (SDL_BUTTON_RIGHT-1);
enum {
	HELL_RETURN = SDLK_RETURN,
	HELL_ESCAPE = SDLK_ESCAPE,
	HELL_SPACE  = SDLK_SPACE,
	HELL_c      = SDLK_c,
	HELL_x      = SDLK_x,
	HELL_z      = SDLK_z,
	HELL_UP     = SDLK_UP,
	HELL_DOWN   = SDLK_DOWN,
	HELL_RIGHT  = SDLK_RIGHT,
	HELL_LEFT   = SDLK_LEFT,
}
enum {
	HELL_J_UP,
	HELL_J_DOWN,
	HELL_J_RIGHT,
	HELL_J_LEFT,
	HELL_J_MAX,
}

/**
 * テクスチャを全て読み直し
 */
void _reloadTexture()
{
	delete g_fontTexture;
	Hell_loadFont();
	foreach(key; g_poolTexture.keys)
	{
		char[] path = g_poolTexture[key].getPath();
		int mask[]  = g_poolTexture[key].getMaskInfo();
		Hell_loadTexture(key, path, [mask[0], mask[1], mask[2]]);
	}
}

/**
 * スクリーンショットをBMPで保存
 */
void _saveBmp()
{
	auto line = g_width*3; // スキャンライン
	// メモリ確保
	scope pixels = new GLubyte[g_width*line];
	// データ格納のサイズを設定
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	// データの読み出し
	glReadPixels(0, 0, g_width, g_height, GL_RGB, GL_UNSIGNED_BYTE, pixels.ptr);
	
	// 上下反転
	for(int j = 0; j < g_height/2; j++)
	{
		for(int i = 0; i < line; i++)
		{
			auto ptr1 = line*j + i;
			auto ptr2 = line*(g_height-1-j) + i;
			auto tmp  = pixels[ptr1];
			pixels[ptr1] = pixels[ptr2];
			pixels[ptr2] = tmp;
		}
	}
	if(auto surface = SDL_CreateRGBSurfaceFrom(
		pixels.ptr,
		g_width, g_height, 24, line,
		0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000))
	{
		SDL_SaveBMP(surface, "hellshot.bmp");
		SDL_FreeSurface(surface);
	}
}

/**
 * SDL初期化
 */
void Hell_init(char[] caption, int width=640, int height=480, bool fullscreen=false)
{
	if(g_init) return;
	g_width      = width;
	g_height     = height;
	g_videoBpp   = 0;
	g_videoFlags = SDL_OPENGL;
	if(fullscreen)
	{
		g_videoFlags |= SDL_FULLSCREEN;
	}
	
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0)
	{
		throw new Error("Hell_init: Couldn't initialize SDL");
	}
	// スクリーン初期化
	if((g_screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags)) is null)
	{
		throw new Error("Hell_init: Couldn't set video mode");
	}
	if(fullscreen)
	{
		_toggleShowCursor(); // フルスクリーン時はマウスカーソルを消す
	}
	
	glOrtho(0, g_width, g_height, 0, -1, 1); 
	glViewport(0, 0, g_width, g_height);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	Hell_setCaption(caption);
	// アイコン設定
	SDL_Surface* icon = SDL_LoadBMP("resource/hell.bmp");
	SDL_WM_SetIcon(icon, null);
	// サウンド初期化（音を良くしたい場合、下のコメントを外す）
//	if(Mix_OpenAudio(44100, AUDIO_S16, 2, 8192) < 0)
	if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) < 0)
	{
		throw new Error("Hell_init: " ~ toString(SDL_GetError()));
	}
	
	// ログ初期化
	write(g_logname, "");
	if(SDL_NumJoysticks() > 0)
	{
		// ジョイスティック生成
		g_joy = SDL_JoystickOpen(0);
		if(!g_joy)
		{
			throw new Error(toString(SDL_GetError()));
		}
		g_joyButtonNum = SDL_JoystickNumButtons(g_joy);
		g_joyButtonPrev = new bool[g_joyButtonNum];
		g_joyButton = new bool[g_joyButtonNum];
	}
	else
	{
		g_joy = null;
		g_joyButtonNum = 0;
	}
	
	// フォントの読込
	Hell_loadFont();
	
	// 曲面オブジェクト生成
//	g_quadricObj = gluNewQuadric();
	
	glEnable(GL_BLEND);
	g_init = true;
}

/**
 * 終了処理
 */
void Hell_quit()
{
	// 曲面オブジェクト破棄
//	gluDeleteQuadric(g_quadricObj);
	
	// テクスチャ破棄
	delete g_fontTexture;
	Hell_disposeTexture();
	
	if(g_joy) SDL_JoystickClose(g_joy);
	Hell_stopWAV();
	Mix_HaltMusic();
	if(!g_music)
	{
		Mix_FreeMusic(g_music);
	}
	Hell_disposeWAV();
	Mix_CloseAudio();
	SDL_Quit();
}

/**
 * キャプション設定（※日本語不可）
 */
void Hell_setCaption(...)
{
	char[] _caption = "";
	void _putc(dchar c)
	{
		auto tmp = [c];
		_caption ~= toUTF8(tmp);
	}
	doFormat(&_putc, _arguments, _argptr);
	SDL_WM_SetCaption(toStringz(_caption), null);
}


/**
 * ログ書き込み
 */
void Hell_write(...)
{
	void _putc(dchar c)
	{
		fputc(c, stdout);
		auto tmp = [c];
		append(g_logname, toUTF8(tmp));
	}
	doFormat(&_putc, _arguments, _argptr);
}

void Hell_write(Exception e)
{
	dout.writeLine(e.toString());
	append(g_logname, e.toString() ~ "\n");
}

/**
 * 画面を消去
 */
void Hell_begin()
{
   	glClear(GL_COLOR_BUFFER_BIT);
}
/**
 * 画面を更新
 */
void Hell_update()
{
	SDL_GL_SwapBuffers();
}

/**
 * いったん停止。入力を更新
 */
void Hell_wait(int ms)
{
	// ウエイト
	Uint32 nowTick , leftTick;
	static Uint32 prvTick = 0;
	
	if(prvTick == 0) prvTick = SDL_GetTicks();
	
	while (true){
	 	nowTick = SDL_GetTicks();
 		leftTick = prvTick + ms - nowTick;
 		if(leftTick < 1 || leftTick > 9999) break;
 	 	SDL_Delay(1);
	}
	prvTick = nowTick;
	
	// マウス更新
	g_pressMouseButtonPrev = g_pressMouseButton;
	g_pressMouseButton     = SDL_GetMouseState(null, null);
	
	// キーボード更新
	foreach(i, key; g_pressKeyButton)
	{
		g_pressKeyButtonPrev[i] = key;
	}
	ubyte* keys = SDL_GetKeyState(null);
	for(int i = 0; i < SDLK_LAST; i++)
	{
		g_pressKeyButton[i] = keys[i] == SDL_PRESSED;
	}
	// ジョイスティック更新
	SDL_JoystickUpdate();
	for(int i = 0; i < g_joyButtonNum; i++)
	{
		g_joyButtonPrev[i] = g_joyButton[i];
		g_joyButton[i] = SDL_JoystickGetButton(g_joy, i) == SDL_PRESSED;
	}
	g_joyXPrev = g_joyX;
	g_joyYPrev = g_joyY;
	g_joyX = SDL_JoystickGetAxis(g_joy, 0);
	g_joyY = SDL_JoystickGetAxis(g_joy, 1);

	// イベント更新
	SDL_Event e;
	while(SDL_PollEvent(&e)) g_quit = (e.type == SDL_QUIT);
	if(Hell_isPushKey(SDLK_F4))      _reloadTexture();
	if(Hell_isPushKey(SDLK_F5))      SDL_WM_IconifyWindow();
	if(Hell_isPushKey(SDLK_F9))      _toggleFullScreen();
	if(Hell_isPushKey(SDLK_F10))     _toggleShowCursor();
	if(Hell_isPushKey(SDLK_F12))     _saveBmp();
	if(Hell_isPressKey(SDLK_ESCAPE)) g_quit = true;
	if(g_quit) throw new Error("dHell system quit.");
	
	// fps計測
	fps_cnt++;
	if(cast(int)fps_lastTick / 1000 != cast(int)nowTick / 1000){
		fps_view = fps_cnt;
		fps_cnt = 0;
		fps_lastTick = nowTick;
	}
}

/**
 * マウス座標Xの取得
 */
int Hell_getMouseX()
{
	int x, y;
	ubyte button = SDL_GetMouseState(&x, &y);
	return x;
}

/**
 * マウス座標Yの取得
 */
int Hell_getMouseY()
{
	int x, y;
	ubyte button = SDL_GetMouseState(&x, &y);
	return y;
}

/**
 * マウスを押し続けているかどうか
 */
ubyte Hell_isPressMouse()
{
	return g_pressMouseButton;
}

/**
 * マウスをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
ubyte Hell_isPushMouse()
{
	return cast(ubyte)(g_pressMouseButton ^ (g_pressMouseButton & g_pressMouseButtonPrev));
}

/**
 * キーを押し続けているかどうか
 */
bool Hell_isPressKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id];
}

/**
 * キーをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id] && !g_pressKeyButtonPrev[id];
}

/**
 * ジョイスティックの十字キーを押しているかどうか
 */
bool Hell_isPressJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return _isUpJ(g_joyY);
	case HELL_J_DOWN:
		return _isDownJ(g_joyY);
	case HELL_J_LEFT:
		return _isLeftJ(g_joyX);
	case HELL_J_RIGHT:
		return _isRightJ(g_joyX);
	default:
		return false;
	}
	return false;
}

/**
 * ジョイスティックの十字キーを押したかどうか
 */
bool Hell_isPushJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return _isUpJ(g_joyY) && !_isUpJ(g_joyYPrev);
	case HELL_J_DOWN:
		return _isDownJ(g_joyY) && !_isDownJ(g_joyYPrev);
	case HELL_J_LEFT:
		return _isLeftJ(g_joyX) && !_isLeftJ(g_joyXPrev);
	case HELL_J_RIGHT:
		return _isRightJ(g_joyX) && !_isRightJ(g_joyXPrev);
	default:
		return false;
	}
	return false;
}

/**
 * ジョイスティックのボタンを押しているかどうか
 */
bool Hell_isPressJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id];
}

/**
 * ジョイスティックのボタンを押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id] && !g_joyButtonPrev[id];
}

/**
 * SE（WAVファイル）の読み込み
 * @param key  キー
 * @param path サウンドファイルのパス
 */
void Hell_loadWAV(char[] key, char[] path)
{
	if(key in g_poolChunk)
	{
		Hell_disposeWAV(key);
	}
	Mix_Chunk* chunk = Mix_LoadWAV(toStringz(path));
	if(!chunk)         { throw new Error(format("Hell_loadWAV: %s(%.*s)", SDL_GetError(), path)); }
	g_poolChunk[key] = chunk;
}

/**
 * SE（WAVファイル）の再生
 * @param key     キー
 * @param loops   ループ回数（-1で無限ループ）
 * @param channel チャンネル番号(-1であいているチャンネルを自動で使う)
 */
void Hell_playWAV(char[] key, int loops=0, int channel=-1)
{
	if(!(key in g_poolChunk)) { throw new Error("Hell_playWAV: Has not exist key: " ~ key); }
	Mix_PlayChannel(channel, g_poolChunk[key], loops);
}

/**
 * SE（WAVファイル）を停止
 * @param channel 停止チャンネル番号(-1で全て停止)
 */
void Hell_stopWAV(int channel=-1)
{
	Mix_HaltChannel(channel);
}

/**
 * SEを破棄
 * @param key   キー（nullで全て破棄）
 */
void Hell_disposeWAV(char[] key=null)
{
	if(key is null)
	{
		foreach(k, chunk; g_poolChunk)
		{
			Mix_FreeChunk(chunk);
			g_poolChunk.remove(k);
		}
		return;
	}
	if(!(key in g_poolChunk)) { throw new Error("Hell_disposeWAV: Has not exist key: " ~ key); }
	Mix_Chunk* chunk = g_poolChunk[key];
	Mix_FreeChunk(chunk);
	g_poolChunk.remove(key);
}

/**
 * BGMを再生する
 * @param path  ファイルパス
 * @param loops ループ回数（-1で無限ループ）
 * @param ms    フェードイン時間（ms）
 */
void Hell_playBgm(char[] path, int loops=-1, int ms=0)
{
	if(g_music)
	{
		Mix_HaltMusic();
		Mix_FreeMusic(g_music);
	}
	g_music = Mix_LoadMUS(cast(char*)(path ~ "\0"));
	if(!g_music)
	{
		throw new Error("Hell_playBgm: " ~ toString(SDL_GetError()));
	}
	if(ms <= 0)
	{
		if(Mix_PlayMusic(g_music, loops) != 0)
		{
			throw new Error("Hell_playBgm: " ~ toString(SDL_GetError()));
		}
	}
	else
	{
		if(Mix_FadeInMusic(g_music, loops, ms) != 0)
		{
			throw new Error("Hell_playBgm: " ~ toString(SDL_GetError()));
		}
	}
}

/**
 * BGMを停止する
 * @param ms    フェードアウト時間（ms）
 */
void Hell_stopBgm(int ms)
{
	if(g_music)
	{
		if(ms <= 0)
		{
			Mix_HaltMusic();
			Mix_FreeMusic(g_music);
			g_music = null;
		}
		else
		{
			Mix_FadeOutMusic(ms);
		}
	}
}

/**
 * 矩形の描画
 * @param x     X座標
 * @param y     Y座標
 * @param dx    幅
 * @param dy    高さ
 * @param width 線の太さ（0で塗りつぶし）
 * @param r     R成分
 * @param g     G成分
 * @param b     B成分
 * @param a     A成分
 */
void Hell_drawRect(int x=0, int y=0, int dx=0, int dy=0, int width=0, float rot=0, ubyte r=0x00, ubyte g=0x00, ubyte b=0x00, ubyte a=0xff)
{
	Texture.unbind();
	
	if(dx == 0 || dy == 0)
	{
		dx = g_width;
		dy = g_height;
	}
	
	glPushMatrix();
	glColor4ub(r, g, b, a);
	glTranslatef(x, y, 0);
	if(rot != 0)
	{
		glTranslatef(dx/2, dy/2, 0);
		// 左回り
		glRotatef(rot, 0, 0, -1);
		glTranslatef(-dx/2, -dy/2, 0);
	}
	if(width <= 0)
	{
		// 塗りつぶし
		glBegin(GL_POLYGON);
	}
	else
	{
		// 塗りつぶしなし
		glLineWidth(width);
		glBegin(GL_LINE_LOOP);
	}
	
	{
		{
			glVertex3f(0,  0, 0);
		 	glVertex3f(dx, 0, 0);
			glVertex3f(dx, dy, 0);
			glVertex3f(0,  dy, 0);
		}
		glEnd();
	}
	glPopMatrix();
}
void Hell_drawRectEx(int cx=0, int cy=0, int dx=0, int dy=0, int width=0, float rot=0, ubyte r=0x00, ubyte g=0x00, ubyte b=0x00, ubyte a=0xff)
{
	Hell_drawRect(cx-dx, cy-dy, dx*2, dy*2, width, rot, r, g, b, a);
}

/**
 * 線分の描画
 * @param x1    開始X座標
 * @param y1    開始Y座標
 * @param x2    終了X座標
 * @param y2    終了Y座標
 * @param width 線の太さ
 * @param r     R成分
 * @param g     G成分
 * @param b     B成分
 * @param a     A成分
 */
void Hell_drawLine(float x1, float y1, float x2, float y2, int width=1, ubyte r=0x00, ubyte g=0x00, ubyte b=0x00, ubyte a=0xff)
{
	if(width < 1) return;
	Texture.unbind();
	glPushMatrix();
	glColor4ub(r, g, b, a);
	glTranslatef(x1, y1, 0);

	glLineWidth(width);
	glBegin(GL_LINES);
	{
		glVertex3f(x1, y1, 0);
		glVertex3f(x2, y2, 0);
	}
	glEnd();

	glPopMatrix();
}

/**
 * 三角形の描画
 * @param x      X座標（中心）
 * @param y      Y座標（中心）
 * @param radius 半径
 * @param rot    回転角度（0～360）
 * @param width  線の太さ（0で塗りつぶし）
 * @param r     R成分
 * @param g     G成分
 * @param b     B成分
 * @param a     A成分
 */
void Hell_drawTriangle(float x, float y, float radius, float rot=0, int width=1, ubyte r=0x00, ubyte g=0x00, ubyte b=0x00, ubyte a=0xff)
{
	if(radius < 1) return;
	Texture.unbind();
	glPushMatrix();
	glColor4ub(r, g, b, a);
	glTranslatef(x, y, 0);
	if(rot != 0) glRotatef(rot, 0, 0, -1);
	if(width <= 0)
	{
		// 塗りつぶし
		glBegin(GL_POLYGON);
	}
	else
	{
		// 塗りつぶしなし
		glLineWidth(width);
		glBegin(GL_POINTS);
	}
	
	{
		{
			glVertex3f(0,                -radius,  0);
			glVertex3f(-radius*1.713f/2, radius/2, 0);
			glVertex3f( radius*1.713f/2, radius/2, 0);
		}
		glEnd();
	}
	glPopMatrix();
}

/**
 * 円の描画
 * @param cx 中心座標X
 * @param cy 中心座標Y
 * @param radius 半径
 * @param width  線の太さ（0で塗りつぶし）
 * @param r     R成分
 * @param g     G成分
 * @param b     B成分
 * @param a     A成分
 */
void Hell_drawCircle(float cx, float cy, float radius, int width=1, ubyte r=0x00, ubyte g=0x00, ubyte b=0x00, ubyte a=0xff)
{
	if(radius <= 0) return;
	Texture.unbind();
	glPushMatrix();
	glColor4ub(r, g, b, a);
	if(width <= 0)
	{
		// 塗りつぶし
		glBegin(GL_POLYGON);
	}
	else
	{
		// 塗りつぶしなし
		glLineWidth(width);
		glBegin(GL_LINE_LOOP);
	}
	
	{
		{
			// 超遅い円描画ルーチン
			for(float i = 0; i < 3.1421356f*2; i+=0.3f)
			{
				glVertex3f(cx + radius*cos(i), cy + radius*sin(i), 0);
			}
		}
		glEnd();
	}
	glPopMatrix();
}

/**
 * テクスチャの描画（左上から描画）
 * <pre>
 * ※抜き色を有効にする場合、Hell_setAlpha()に「HELL_ALPHA_NORMAL/HELL_ALPHA_ADD」を指定する必要があります。
 * </pre>
 * @param key キー（Hell_loadTextureで読み込み済みのもの）
 * @param x   X座標
 * @param y   Y座標
 * @param ox  切り取り開始X座標
 * @param oy  切り取り開始Y座標
 * @param ow  切り取る幅
 * @param oh  切り取る高さ
 * @param dx  拡大サイズ（X）
 * @param dy  拡大サイズ（Y）
 * @param rot 回転角度（0～360。左回り）
 * @param r   マスク色(赤）
 * @param g   マスク色(緑）
 * @param b   マスク色(青）
 */
void Hell_drawTexture(
	char[] key,
	float x, float y,
	int ox=0, int oy=0, int ow=0, int oh=0,
	float dx=1.0f, float dy=1.0f,
	float rot=0,
	ubyte r=255, ubyte g=255, ubyte b=255)
{
	if(!(key in g_poolTexture)) { throw new Error("Hell_drawTexture: Has not exist key: " ~ key); }
	Texture tex = g_poolTexture[key];
	if(ow == 0 || oh == 0)
	{
		ow = tex._width;
		oh = tex._height;
	}
	float w  = ow * dx;
	float h  = oh * dy;
	tex.bind();
	
	glPushMatrix();
	glColor3ub(r, g, b);
	glTranslatef(x, y, -800);
	if(rot != 0)
	{
		glTranslatef(w/2, h/2, -800);
		// 左回り
		glRotatef(rot, 0, 0, -1);
		glTranslatef(-w/2, -h/2, 0);
	}
	
	float s = cast(float)ox / cast(float)tex._width;
	float t = cast(float)oy / cast(float)tex._height;
	float u = cast(float)ow / cast(float)tex._width;
	float v = cast(float)oh / cast(float)tex._height;
	
	glBegin(GL_POLYGON);
	{
		glTexCoord2f(s,     t);     glVertex3f(0, 0, 0);
	 	glTexCoord2f(s + u, t);     glVertex3f(w, 0, 0);
		glTexCoord2f(s + u, t + v); glVertex3f(w, h, 0);
		glTexCoord2f(s,     t + v); glVertex3f(0, h, 0);
	}
	glEnd();
	
	glPopMatrix();
	tex.unbind();
}
/**
 * 中心座標を指定して描画
 */
void Hell_drawTextureEx(char[] key, float cx, float cy,  int ox=0, int oy=0, int ow=0, int oh=0, float dx=1.0f, float dy=1.0f, float rot=0, ubyte r=255, ubyte g=255, ubyte b=255)
{
	if(!(key in g_poolTexture)) { throw new Error("Hell_drawTexture: Has not exist key: " ~ key); }
	Texture tex = g_poolTexture[key];
	if(ow == 0 || oh == 0)
	{
		ow = tex._width;
		oh = tex._height;
	}
	float w  = ow * dx;
	float h  = oh * dy;
	Hell_drawTexture(key, cx-w/2, cy-h/2,  ox, oy, ow, oh, dx, dy, rot, r, g, b);
}

/**
 * フォントの描画（ascii文字のみ）
 * @param msg   描画文字列
 * @param x     X座標
 * @param y     Y座標
 * @param zoom  拡大サイズ（1.0fで32x48）
 * @param r     R成分
 * @param g     G成分
 * @param b     B成分
 * @param a     A成分
 */
void Hell_drawFont(char[] msg, int x, int y, float zoom=1.0f, ubyte r=0xff, ubyte g=0xff, ubyte b=0xff, ubyte a=0xff)
{
	const int WIDTH  = 32; // フォントの幅
	const int HEIGHT = 48; // フォントの高さ
	const int COLUMN = 16; // フォントテクスチャの一行の文字数
	char decoder[]   = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.()[]#$%&'\"!?^+-*/=;:";
	Texture tex = g_fontTexture;
	tex.bind();
	glColor4ub(r, g, b, a);
	
	float u = WIDTH  / cast(float)tex._width;  // テクスチャ座標の幅
	float v = HEIGHT / cast(float)tex._height; // テクスチャ座標の高さ
	float w = WIDTH  * zoom; // 実際の幅
	float h = HEIGHT * zoom; // 実際の高さ
	foreach(c; msg)
	{
		if(!isascii(c)) continue;    // ascii文字じゃないです
		int idx = find(decoder, c); // 文字のインデックスを取得
		if(c == ' ' | idx < 0)
		{
			x += cast(int)(WIDTH * zoom); // スペース
			continue;
		}
		
		glPushMatrix();
		glTranslatef(x, y, -800);
		
		// テクスチャ座標を取得
		float s = (idx % COLUMN) * u;
		float t = (idx / COLUMN) * v;
		
		glBegin(GL_POLYGON);
		glTexCoord2f(s,     t);     glVertex3f(0, 0, 0);
	 	glTexCoord2f(s + u, t);     glVertex3f(w, 0, 0);
		glTexCoord2f(s + u, t + v); glVertex3f(w, h, 0);
		glTexCoord2f(s,     t + v); glVertex3f(0, h, 0);
		glEnd();
		
		glPopMatrix();
		
		x += cast(int)(WIDTH * zoom);
	}
	tex.unbind();
}

void Hell_drawFontEx(int x, int y, float zoom=1.0f, ubyte r=0xff, ubyte g=0xff, ubyte b=0xff, ubyte a=0xff, ...)
{
	char[] _tmpString = "";
	void _putc(dchar c)
	{
		auto tmp = [c];
		_tmpString ~= toUTF8(tmp);
	}
	doFormat(&_putc, _arguments, _argptr);
	Hell_drawFont(_tmpString, x, y, zoom, r, g, b, a);
}

/**
 * αブレンド定数
 */
enum {
	HELL_ALPHA_DISABLE, // αブレンドしない
	HELL_ALPHA_NORMAL,  // 半透明
	HELL_ALPHA_ADD,     // 加算
}

static int g_alpha_prev = HELL_ALPHA_DISABLE;
int Hell_getAlpha()
{
	return g_alpha_prev;
}
/**
 * αブレンドの設定
 * @parma flag 設定値(HELL_ALPHA_XXX)
 */
void Hell_setAlpha(int flag)
{
//	if(g_alpha_prev == flag) return; // 前と同じ
	switch(flag)
	{
	case HELL_ALPHA_DISABLE:
		glDisable(GL_BLEND);
		break;
	case HELL_ALPHA_NORMAL:
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA);
		break;
	case HELL_ALPHA_ADD:
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA , GL_ONE);
		break;
	default:
		break;
	}
	g_alpha_prev = flag;
}

/**
 * FPS値の取得
 */
int Hell_getFPS()
{
	return fps_view;
}

/**
 * FPSの描画
 */
void Hell_drawFPS(int x=10, int y=-1)
{
	if(x < 0 || y < 0)
	{
		x = 10;
		y = g_height - 48; // フォントの高さ
	}
	Hell_drawFont("FPS:" ~ toString(Hell_getFPS()), x, y);
}


/**
 * テクスチャ読み込み
 * @parma key      キー（Hell_drawTextureで使用する）
 * @param filepath ファイルパス
 * @param mask     抜き色（mask[0]に「-1」で抜き色なし。「-2」で座標指定）
 */
void Hell_loadTexture(char[] key, char[] filepath, int[3] mask=[-1, 0, 0])
{
	//if(key in g_poolTexture) { throw new Error("Hell_loadTexture: Has exist key: " ~ key); }
	if(key in g_poolTexture)
	{
		// 同じキーがある場合、そのテクスチャを破棄
		Hell_disposeTexture(key);
	}
	if(mask is null) { throw new Error("Hell_loadTexture: PARAMETER 'mask' is null"); }
	if(mask[0] >= 0)
	{
		// 抜き色指定
		g_poolTexture[key] = new Texture(filepath, mask[0], mask[1], mask[2]);
	}
	else if(mask[0] == -1)
	{
		// 抜き色なし
		g_poolTexture[key] = new Texture(filepath);
	}
	else if(mask[0] == -2)
	{
		// 座標指定
		g_poolTexture[key] = new Texture(filepath, mask[1], mask[2]);
	}
	else
	{
		throw new Error("Hell_loadTexture: Invalid parameter 'mask'");
	}
}

/**
 * テクスチャの破棄
 * @param key キー（Hell_loadTextureで指定したもの）「null」で全て破棄
 */
public void Hell_disposeTexture(char[] key=null)
{
	if(key is null)
	{
		foreach(k; g_poolTexture.keys)
		{
			Texture tex = g_poolTexture[k];
			delete tex;
			g_poolTexture.remove(key);
		}
	}
	else
	{
		if(!(key in g_poolTexture)) { throw new Error("Hell_disposeTexture: Has exist key: " ~ key); }
		Texture tex = g_poolTexture[key];
		delete tex;
		g_poolTexture.remove(key);
	}
}

/**
 * テスクチャクラス（BMPのみ）
 */
class Texture
{
private:
	/**
	 * テクスチャ番号
	 */
	GLuint id;
	/**
	 * 高さ
	 */
	int _width;
	/**
	 * 幅
	 */
	int _height;
	/**
	 * パス
	 */
	char[] _path;
	/**
	 * マスク情報
	 */
	int[] _maskInfo;
	SDL_Surface* _surface;
public:
	/**
	 * コンストラクタ（抜き色なし）
	 * @param filename  ファイルパス
	 * @param maskColor 抜き色
	 */
	this(char[] filename)
	{
		_maskInfo = [-1, 0, 0];
		SDL_Surface* s = loadBMP(filename);
		create(s);
	}
	/**
	 * コンストラクタ（抜き色指定）
	 * @param filename  ファイルパス
	 * @param maskColor 抜き色
	 */
	this(char[] filename, int r, int g, int b)
	{
		_maskInfo = [r, g, b];
		SDL_Surface* s = loadBMP(filename);
		Uint32 maskColor = (r << 16) | (g << 8) | (b);
		s = setColorKey(s, maskColor); // 抜き色設定
		create(s);
	}
	/**
	 * コンストラクタ（指定座標の色を抜き色に）
	 * @param filename  ファイルパス
	 * @param maskColor 抜き色
	 */
	this(char[] filename, int x, int y)
	{
		_maskInfo = [-2, x, y];
		SDL_Surface* s = loadBMP(filename);
		if((0 <= x && x < s.w) && (0 <= y && y < s.h))
		{
			// 座標が画像の範囲内
			SDL_LockSurface(s);	
			Uint32* surfacepixels = cast(Uint32*) s.pixels;
			Uint32 maskColor = surfacepixels[y * s.pitch / 4  + x];
			SDL_UnlockSurface(s);
			s = setColorKey(s, maskColor); // 抜き色設定
		}
		create(s);
	}
	char[] getPath()     { return _path;     }
	int[] getMaskInfo()  { return _maskInfo; }
	/**
	 * 生成
	 */
	void create(SDL_Surface* s)
	{
		glGenTextures(1, &id);
		glBindTexture(GL_TEXTURE_2D, id);
		gluBuild2DMipmaps(GL_TEXTURE_2D, 4, s.w, s.h, GL_RGBA, GL_UNSIGNED_BYTE, s.pixels);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		
		_width   = s.w;
		_height  = s.h;
		_surface = s;
	}
	/**
	 * 抜き色設定
	 */
	SDL_Surface* setColorKey(SDL_Surface* s, Uint32 maskColor)
	{
		maskColor |= 0xff000000u;
		Uint32* surfacepixels = cast(Uint32*) s.pixels;
		SDL_LockSurface(s);
		for(int y = 0; y < s.h; y++)
		{
			for(int x = 0; x < s.w; x++)
			{
				Uint32* p = &(surfacepixels[y * s.pitch / 4  + x]);
				if(*p == maskColor) *p = 0x00000000u;
			}
		}
		SDL_UnlockSurface(s);
		return s;
	}
	
	/**
	 * デストラクタ
	 */
	~this()
	{
		glDeleteTextures(1 , &id);
		SDL_FreeSurface(_surface);
	}
	
	/**
	 * BMP読み込み
	 */
	SDL_Surface* loadBMP(char[] filename)
	{
		// ファイルパス
		_path = filename;
		// サーフェース生成
		SDL_Surface *s = SDL_LoadBMP(toStringz(filename));
		if(!s) { throw new Error("Hell_Texture::loadBmp : not found file: " ~ filename); }
		SDL_PixelFormat format;
		format.palette       = null;
		format.BitsPerPixel  = 32;
		format.BytesPerPixel = 4;
		format.Rmask         = 0x000000ff;
		format.Gmask         = 0x0000ff00;
		format.Bmask         = 0x00ff0000;
		format.Amask         = 0xff000000;
		format.Rshift        = 0;
		format.Gshift        = 8;
		format.Bshift        = 16;
		format.Ashift        = 24;
		format.Rloss         = 0;
		format.Gloss         = 0;
		format.Bloss         = 0;
		format.Aloss         = 0;
		format.alpha         = 0;
		SDL_Surface *ret = SDL_ConvertSurface(s, &format, SDL_SWSURFACE);
		SDL_FreeSurface(s);
		return ret;
 	}
	/**
	 * バインド
	 */
	void bind()
	{
		glBindTexture(GL_TEXTURE_2D, id);
		glEnable(GL_TEXTURE_2D);
	}
	/**
	 * バインド解除
	 */
	static void unbind()
	{
		glDisable(GL_TEXTURE_2D);
	}
}

// 数学関係
const float HELL_PI = PI;
float Hell_sin(float rad)            { return sin(rad);             }
float Hell_cos(float rad)            { return cos(rad);             }
float Hell_atan2(float y, float x)   { return atan2(y, x);          }
float Hell_sinEx(float deg)          { return sin(deg2rad(deg));    }
float Hell_cosEx(float deg)          { return cos(deg2rad(deg));    }
float Hell_atan2Ex(float y, float x) { return rad2deg(atan2(y, x)); }
float Hell_deg2rad(float deg)        { return deg2rad(deg);         }
float Hell_rad2deg(float rad)        { return rad2deg(rad);         }
// 乱数
/**
 * 乱数の初期化
 * <pre>
 * わざわざ呼ばなくてもプログラム開始時に乱数は初期化されます。
 * 再現性のある乱数を生成したい場合にコールします。
 * </pre>
 * @param seed  種
 * @param index 次の乱数インデックスへの増分
 */
void Hell_randSeed(uint seed, uint index)
{
	rand_seed(seed, index);
}
/**
 * begin <= x < endの範囲で乱数を生成
 * @param begin 開始値
 * @param end   終了値
 */
int Hell_randInt(int begin, int end)
{
	auto range = end - begin;
	if(range < 1) return -1;
	return begin + rand()%range;
}
