#include <windows.h>
#include <mmsystem.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#ifndef GLUTCALLBACK
#define GLUTCALLBACK			//for X11 Mesa4 verision of glut.
#endif
#include "tool.h"
#ifndef	M_PI
#define	M_PI	3.1415926535897932384626433832795
#endif	// M_PI

/*	サンプル用デモ作品.
 *
 *	・'my_なんとか'はこのデモ作品オリジナル部分ですので、
 *	　参考程度にお留めください。
 *	　パクるとサードレート作品となりますのでお気を付けくださいな。
 *	・他の部分はパクっちゃっても大丈夫です。
 *	・今回に関していえば、tool.cc/hもパクっちゃって大丈夫です。
 */

// my functions.
void my_init();
void my_exit(int t);
void my_draw();

// common functions.
void draw_string( float x, float y, const char *str);
void draw_string_center( float x, float y, const char *str);
void calc_fps();
void show_fps();

// glut callbacks.
GLUTCALLBACK
void display();
GLUTCALLBACK
void reshape( int width, int height );
GLUTCALLBACK
void idle();
GLUTCALLBACK
void keyboard( unsigned char key, int x, int y);
GLUTCALLBACK
void special( int key, int x, int y);

// common initializations.
void initglut( const char *title);
void init2();
int main(int argc, char **argv);


// common data.
bool	m_pause;
bool	m_show_fps;
DWORD	m_prevtime, m_lapsetime; // milisecond.
DWORD	m_prevsecond;
DWORD	m_framecount;
DWORD	m_fps;
float	m_lowest, m_highest;

struct	billboard_t {
	GLuint		*texid;
	size_t		size;			// 想定しているテクスチャサイズ.
	size_t		bmp[2][2];		// bounding box. (in pixel)
	float		tex[2][2];		// texture coordinates.
	float		pos[2][2];		// centerized box.
	float		base[2];		// base position.
	float		angle;			// angle rotation.
};

// my_data;
bool	my_photo;
GLuint	my_tex_positive[6];
GLuint	my_tex_negative[3];
GLuint	my_tex_photo;
GLuint	my_tex_string[2];
float my_string_fin_base[2];
float my_string_fin_tex[2][2];	//テクスチャ座標.(左下原点)
float my_string_fin_pos[2][2];	//画面座標.(左下原点)
size_t my_string_fin_bmp[2][2] = { //画像位置.(左上原点)
	{ 328, 379 },
	{ 693, 615 }
};
float my_string_soredake_base[2];
float my_string_soredake_tex[2][2];
float my_string_soredake_pos[2][2];
size_t my_string_soredake_bmp[2][2] = {
	{ 312, 745 },
	{ 1008, 847 }
};
enum string2_strings {
	STRING2_SOREDAKE,
	STRING2_HONPEN,
	STRING2_NANINANI,
	STRING2_ARE,
	STRING2_NANDE,
	STRING2_JIKAN,
	STRING2_OIOI,
	STRING2_NETA,
	STRING2_NUM,
	STRING2_RANDOMS = STRING2_NETA
};
float my_string2_strings_angle[STRING2_NUM]; //右方向(通常)をゼロ.
float my_string2_strings_base[STRING2_NUM][2];
float my_string2_strings_tex[STRING2_NUM][2][2];
float my_string2_strings_pos[STRING2_NUM][2][2];
size_t my_string2_strings_bmp[STRING2_NUM][2][2] = {
{	{ 1, 1 },
	{ 9*64+1, 64+1 }
},
{	{ 1, 64+3 },
	{ 4*64+1, 2*64+3 }
},
{	{ 1, 2*64+5 },
	{ 15*64+1, 3*64+5 }
},
{	{ 1, 3*64+7 },
	{ 11*64+1, 4*64+7 }
},
{	{ 1, 4*64+9 },
	{ 7*64+1, 5*64+9 }
},
{	{ 1, 5*64+11 },
	{ 13*64+1, 6*64+11 }
},
{	{ 1, 6*64+13 },
	{ 16*64+1, 7*64+13 }
},
{	{ 1, 7*64+15 },
	{ 12*64+1, 8*64+15 }
}};


billboard_t		my_string_fin = {
	&my_tex_string[0],
	1024,
	{	{ 328, 379 },
		{ 693, 615 }
	}
};
billboard_t		my_string_soredake = {
	&my_tex_string[0],
	1024,
	{	{ 312, 745 },
		{ 1008, 847 }
	}
};
billboard_t		my_string2[STRING2_NUM] = {
	{	&my_tex_string[1],
		1024,
		{	{ 1, 1 },
			{ 9*64+1, 64+1 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 64+3 },
			{ 4*64+1, 2*64+3 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 2*64+5 },
			{ 15*64+1, 3*64+5 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 3*64+7 },
			{ 11*64+1, 4*64+7 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 4*64+9 },
			{ 7*64+1, 5*64+9 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 5*64+11 },
			{ 13*64+1, 6*64+11 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 6*64+13 },
			{ 16*64+1, 7*64+13 }
		}
	},
	{	&my_tex_string[1],
		1024,
		{	{ 1, 7*64+15 },
			{ 12*64+1, 8*64+15 }
		}
	}
};


void my_init()
{
	my_photo = true; //(bool)getenv("0123_PHOTO");

	glClearColor( 0, 0, 0, 0 );
	glDisable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );
	glDisable(GL_BLEND);
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	glGenTextures( 6, my_tex_positive);
	glGenTextures( 3, my_tex_negative);
	glGenTextures( 1, &my_tex_photo);
	glGenTextures( 2, my_tex_string);

	int ret;
	ret = bmp_read( my_tex_positive[5], "tex/5.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_positive[4], "tex/4.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_positive[3], "tex/3.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_positive[2], "tex/2.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_positive[1], "tex/1.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_positive[0], "tex/0.bmp");
	if( ret )	my_exit(1);

	ret = bmp_read( my_tex_negative[0], "tex/-1.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_negative[1], "tex/-2.bmp");
	if( ret )	my_exit(1);
	ret = bmp_read( my_tex_negative[2], "tex/-3.bmp");
	if( ret )	my_exit(1);

	if( my_photo ) {
		ret = bmp_read( my_tex_photo, "tex/photo.bmp");
		if( ret )	my_exit(1);
	}

	GLint	width;

	ret = bmp_read( my_tex_string[0], "tex/string.bmp", GL_ALPHA);
	if( ret )	my_exit(1);
	
	glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width );
	bmp2pos( my_string_fin_pos, my_string_fin_bmp, width );
	pos2tex( my_string_fin_tex, my_string_fin_pos, width );
	centerize_pos( my_string_fin_base, my_string_fin_pos);
	bmp2pos( my_string_soredake_pos, my_string_soredake_bmp, width );
	pos2tex( my_string_soredake_tex, my_string_soredake_pos, width );
	centerize_pos( my_string_soredake_base, my_string_soredake_pos);

	ret = bmp_read( my_tex_string[1], "tex/string2.bmp", GL_ALPHA);
	if( ret )	my_exit(1);

	glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width );
	srand(timeGetTime());		// srandom()
	for( size_t i=0; i< STRING2_NUM; i++ ) {
		bmp2pos( my_string2_strings_pos[i], my_string2_strings_bmp[i], width );
		pos2tex( my_string2_strings_tex[i], my_string2_strings_pos[i], width );

		// 看板文句の中心を(0,0)にする.
		centerize_pos( my_string2_strings_base[i], my_string2_strings_pos[i] );
		// そのままではちょっとデカすぎなので乱数で半分くらいに.
		float	v;
		v = (float)rand() / (float)RAND_MAX * 0.5 + 0.25;
		multiply_pos( my_string2_strings_pos[i], v );
		// 看板文句の初期位置および初期角度を乱数で設定.
		float	a, x, y;
// 		v = (float)((double)random() / (double)(2<<31-1));
		v = (float)rand() / (float)RAND_MAX;
		a = v * 360.0;
// 		v = (float)((double)random() / (double)(2<<31-1));
		v = (float)rand() / (float)RAND_MAX;
		x = v * 512 + 256;
// 		v = (float)((double)random() / (double)(2<<31-1));
		v = (float)rand() / (float)RAND_MAX;
		y = v * 512 + 256;
		my_string2_strings_angle[i] = a;
		my_string2_strings_base[i][0] = x;
		my_string2_strings_base[i][1] = y;
	}
}
void my_exit(int t)
{
	glDeleteTextures( 6, my_tex_positive);
	glDeleteTextures( 3, my_tex_negative);
	glDeleteTextures( 1, &my_tex_photo);
	glDeleteTextures( 2, my_tex_string);
if( errno ) fprintf(stderr, "%s\n", strerror(errno));
	exit(t);
}

void my_draw()
{
	// カウントダウンパート...
	if( /* 0 <= m_lapsetime && */ m_lapsetime < 9000 ) {
		// use least 8 bits - 経過時間の下位8ビットで振動値を決定してみる.
		unsigned char t0 = (unsigned char)m_lapsetime;
		unsigned char t1 = (t0 >> 4) | (t0 << 4);
		unsigned char t2 = ((t1 & 0xcc) >> 2) | ((t1 & 0x33) << 2);
		unsigned char t3 = ((t2 & 0xaa) >> 1) | ((t2 & 0x55) << 1);
		float randxvalue = (float)t3 / 255.;
		float randyvalue = (float)t0 / 255.;
		// 乱数(数列?)を元に移動幅を決定 - ±2.5の幅で振幅.
		float movex = ((randxvalue - 0.5) * 2.0) * 2.0;
		float movey = -((randyvalue - 0.5) * 2.0) * 2.0;
		glTranslatef( movex, movey, 0 ); // 画面の移動量を設定.

		if( (m_lapsetime % 1000) < 100 ) { //各秒の頭0.1秒の一瞬.
			if( m_lapsetime < 1000 ) {	// 0 〜 1秒.
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[5] );
			}
			else if( m_lapsetime < 2000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[4] );
			}
			else if( m_lapsetime < 3000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[3] );
			}
			else if( m_lapsetime < 4000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[2] );
			}
			else if( m_lapsetime < 5000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[1] );
			}
			else if( m_lapsetime < 6000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_positive[0] );
			}
			else if( m_lapsetime < 7000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_negative[0] );
			}
			else if( m_lapsetime < 8000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_negative[1] );
			}
			else if( m_lapsetime < 9000 ) {
				glBindTexture( GL_TEXTURE_2D, my_tex_negative[2] );
			}
			glEnable(GL_TEXTURE_2D);
			glColor4f( 1, 1, 1, 1 );
			texbox( 10, 10, 1014, 1014 );
			glDisable(GL_TEXTURE_2D);
		}
		else if(m_lapsetime < 5000 ) {	//各秒の残り0.9秒. (カウント0まで)
			// メーター増加アニメーション.
			float msec = (m_lapsetime % 1000);
			float rate = (msec - 100) / 900.;
			// 枠.
			glColor4f( 1, 1, 1, 1 );
			box( 244 - 8, 284 - 8, 780 + 8, 484 + 8 );
			glColor4f( 0, 0, 0, 1 );
			box( 244 - 4, 284 - 4, 780 + 4, 484 + 4 );
			// メーター.
			glColor4f( 1, 1, 1, 1 );
			size_t ofs = (size_t)((780 - 244) * rate);
			for( size_t x=0; x < ofs; x+= 20 )
			{
				box( 244 + x, 284, 244 + x + 16, 484 );
			}
		}
		else if( 5000 <= m_lapsetime ) { //(カウント0以降)
			// メーター減少(?)アニメーション.
			float msec = (m_lapsetime % 1000);
			float rate = (msec - 100) / 900.;
			// 枠.
			glColor4f( 1, 1, 1, 1 );
			box( 244 - 8, 284 - 8, 780 + 8, 484 + 8 );
			glColor4f( 0, 0, 0, 1 );
			box( 244 - 4, 284 - 4, 780 + 4, 484 + 4 );
			// メーター.
			glColor4f( 1, 1, 1, 1 );
			size_t limit= (780 - 244);
			size_t ofs = (size_t)(limit * rate);
			ofs = ofs - (ofs % 20);
			for( size_t x=ofs; x < limit; x+= 20 )
			{
				box( 244 + x, 284, 244 + x + 16, 484 );
			}
		}
	}
	// final表示パート...
	else if( 9000 <= m_lapsetime && m_lapsetime < 16000 ) {
		if( my_photo ) {
			glBindTexture( GL_TEXTURE_2D, my_tex_photo );
			glEnable(GL_TEXTURE_2D);
			glColor4f( 1, 1, 1, 1 );
			texbox( 0, 0, 1024, 1024 );
			glDisable(GL_TEXTURE_2D);
		}

		glEnable( GL_BLEND );
// 		if( 10000 <= m_lapsetime ) {
			if( my_photo ) {	//写真表示時は写真のトーンを半分に落とす.
				glColor4f( 0, 0, 0, 0.5 );
				box(0, 0, 1024, 1024);
			}

			// エンディング Fin!
			glBindTexture( GL_TEXTURE_2D, my_tex_string[0] );
			glEnable(GL_TEXTURE_2D);
			glColor4f( 0.8, 0.8, 0.8, 1 );
			glPushMatrix();
			glTranslatef( my_string_fin_base[0], my_string_fin_base[1], 0 );
			texbox2( my_string_fin_pos, my_string_fin_tex );
			glPopMatrix();

			// それだけかよ!
			if( 11000 <= m_lapsetime ) {
				glColor4f( 1, 1, 1, 1 );
				glPushMatrix();
				glTranslatef( my_string_soredake_base[0],
							  my_string_soredake_base[1],
							  0 );

				if( 13000 <= m_lapsetime ) { //13.0秒から、看板落下アニメ.
					DWORD	lapse = m_lapsetime - 13000;
					float	rate = (float)lapse / 1000;
					float	upvec = rate * 512;
					float	gravity = 9.8 * rate * rate * 32;
					glTranslatef( 0, upvec - gravity, 0 );
					float	ang = rate * 360;
					glRotatef( ang, 1, 0, 0 );
				}

				texbox2( my_string_soredake_pos, my_string_soredake_tex );
				glPopMatrix();
			}
			glDisable(GL_TEXTURE_2D);
// 		}
		// ツッコミ看板...
		if( 11500 <= m_lapsetime ) {
			glBindTexture( GL_TEXTURE_2D, my_tex_string[1] );
			glEnable(GL_TEXTURE_2D);
			//11.5秒から貼りだして, 12.0秒で貼り付け完了.
			DWORD	lapse = m_lapsetime - 11500;
			size_t	num = lapse / 100;
			for( size_t i=0; i< num && i< STRING2_RANDOMS; i++ ) {
				glPushMatrix();
				glTranslatef( my_string2_strings_base[i][0],
							  my_string2_strings_base[i][1], 0 );

				if( 13000 <= m_lapsetime ) { //13.0秒から、看板落下アニメ.
					DWORD	lapse = m_lapsetime - 13000;
					float	rate = (float)lapse / 1000;
					float	dir[3], pos[3];
					dir[0] = my_string2_strings_base[i][0] - 512;
					dir[1] = my_string2_strings_base[i][1] - 256;
					dir[2] = -128;
					normalize( dir, dir );
					pos[0] = dir[0];
					pos[1] = dir[1];
					pos[2] = dir[2];
					float	move = rate * 512;
					multiply( pos, move );
					float	gravity = 9.8 * rate * rate * 32;
					pos[1] -= gravity;
					glTranslatef( pos[0], pos[1], pos[2] );

					float	axis[3];
					float	Z[3] = { 0, 0, 1 };
					crossproduct( axis, dir, Z );
					float	ang = rate * 360;
					glRotatef( ang, axis[0], axis[1], axis[2] );
				}

				glRotatef( my_string2_strings_angle[i], 0, 0, 1 );
				glColor4f( 1, 1, 1, 1 );
				texbox2( my_string2_strings_pos[i],
						 my_string2_strings_tex[i] );
				glPopMatrix();
			}
			glDisable(GL_TEXTURE_2D);
		}
		// fade out.
		if( 14000 <= m_lapsetime && m_lapsetime < 16000 ) {
			float msec = m_lapsetime - 14000;
			float rate = msec / 2000;
			float mask = sin( rate * M_PI * 0.5 );
			glColor4f( 0, 0, 0, mask );
			box(0, 0, 1024, 1024);
		}
		glDisable( GL_BLEND );
	}
	else {
		my_exit(0);
	}
}

// -------------------------------------------------------------------------

// 文字列描画.
void draw_string( float x, float y, const char *str)
{
	size_t strsize = strlen(str);
	glRasterPos2f( x, y );
	for( size_t i=0; i< strsize; i++ )
	{
		glutBitmapCharacter( GLUT_BITMAP_HELVETICA_10, str[i] );
	}
}
// 平均的に文字列描画. (1024x768解像度用)
void draw_string_center( float x, float y, const char *str)
{
	size_t strsize = strlen(str);
	size_t half = strsize * 10 / 2;

	x-= half;
	for( size_t i=0; i< strsize; i++ )
	{
		glRasterPos2f( x, y );
		glutBitmapCharacter( GLUT_BITMAP_HELVETICA_10, str[i] );
		x += glutBitmapWidth( GLUT_BITMAP_HELVETICA_10, str[i] );
	}
}
// フレーム速度を計測.
void calc_fps()
{
	m_framecount++;
	DWORD second = timeGetTime() / 1000;
	if( second != m_prevsecond ) {
		m_prevsecond = second;
		m_fps = m_framecount;
		m_framecount = 0;
	}
}
// フレーム速度を表示.
void show_fps()
{
	char	buf[256];
	_snprintf( buf, sizeof(buf), "%d fps", m_fps );
	glColor4f( 1, 1, 1, 1 );
	draw_string( 100, 150, buf);
}
GLUTCALLBACK
void display()
{
	glPushAttrib( GL_ALL_ATTRIB_BITS );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glLoadIdentity();

	my_draw();

	if( m_show_fps ) {
		calc_fps();
		show_fps();
	}
	if( m_pause ) {
		draw_string_center( 512, 512, "- pause -" );
	}

	glPopAttrib();
	glutSwapBuffers();
}
GLUTCALLBACK
void reshape( int width, int height )
{
	glViewport( 0, 0, width, height );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	float aspect_ratio = (float)height / (float)width;
	// 左右計90度の視界を設定.
	glFrustum( -1.0, 1.0,
			   -aspect_ratio, aspect_ratio,
			   1.0, 1000.0 );
	/* レイアウトの考えやすさから (0,0) - (1024,1024)の平面描画座標を設定.
	 *	ただし全部見えるのは左右の範囲.
	 *	(上下の一部は画面比率によってクリップされるため)
	 *
	 *			画面中央は常に (512, 512) である.
	 *
	 *	4:3の画面では、(0, 128) - (1024, 896)の範囲が見える.
	 *	16:9の画面では、(0, 224) - (1024, 800)の範囲が見える.
	 *
	 *	えっと、それから正方形ピクセルにしか対応していませんので悪しからず.
	 *	例) 1280x1024画面サイズは、画像が少し縦に縮む場合があるってこと。
	 */
	glTranslatef( -512.0, -512.0, -512.0 );
	glMatrixMode( GL_MODELVIEW );
	m_lowest = -aspect_ratio * 512 + 512;
	m_highest=  aspect_ratio * 512 + 512;
}
GLUTCALLBACK
void idle()
{
	DWORD curtime = timeGetTime();
	m_lapsetime += curtime - m_prevtime;
	glutPostRedisplay();
	m_prevtime = curtime;
}
GLUTCALLBACK
void keyboard( unsigned char key, int x, int y)
{
	switch(key)
	{
	case 0x1b:	// Esc.
	case 'q':
		my_exit(0);
		break;					// not reached.
	case ' ':	// pause.
		m_pause = !m_pause;
		if( m_pause ) {
			glutIdleFunc(NULL);
			glutPostRedisplay(); // '- pause -' 表示のため再描画.
		} else {
			m_prevtime = timeGetTime();	//一時停止中の経過時間をクリア.
			glutIdleFunc(idle);
		}
		break;
	case 'f':
		m_show_fps = !m_show_fps;
		break;
	default:
		printf("key: %c(%u)\n", key, (unsigned int)key );
		break;
	}
}
GLUTCALLBACK
void special( int key, int x, int y)
{
	switch(key)
	{
	case 100://left
		m_lapsetime -= 1000;
		glutPostRedisplay();
		break;
	case 102://right
		m_lapsetime += 1000;
		glutPostRedisplay();
		break;
	case 101://up
		m_lapsetime -= 100;
		glutPostRedisplay();
		break;
	case 103://down
		m_lapsetime += 100;
		glutPostRedisplay();
		break;
	default:
		printf("special: %d\n", key );
		break;
	}
}

void initglut( const char *title)
{
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH );
	glutCreateWindow( title );
	glutFullScreen();

	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyboard);
	glutSpecialFunc(special);
	glutIdleFunc(idle);

	glutSetCursor(GLUT_CURSOR_NONE);
}
void init2()
{
	m_pause		= false;
	m_show_fps = (bool)getenv("0123_SHOW_FPS");

	Sleep(1000);				//画面切り替え待ちとして 1秒ほど溜めを設ける.
	m_lapsetime = 0;
	m_prevtime = timeGetTime();
}

int main(int argc, char **argv)
{
	initglut(argv[0]);
	my_init();
	init2();
	glutMainLoop();
}
