#ifndef ATS_GLUTXX_H
#define ATS_GLUTXX_H

#include <GL/glut.h>
#include "ats_component.h"
#include <list>
/********************************************************
* class Glutxx
* GLUTのラッパークラス。細かい指定はできない。
* staticなメンバ関数はC言語のcallbackに呼ばせるため。(
* thisポインタがない。)
********************************************************/

// floatからdoubleへの切りつめを解除
#pragma warning(disable : 4305)

namespace ats
{
	template<class T>
	class Glutxx
	{
	protected:
		static int m_button, m_state, m_x, m_y;
		static GLfloat m_scale;
		static GLfloat m_rot_x, m_rot_y, m_rot_z;
		static GLfloat m_rot_matrix[16];
		static Glutxx<T>* m_instance;
		static GLenum m_front, m_back;
		Glutxx(int* argc, char** argv, int x, int y);
		Glutxx(const Glutxx&) {};

		static void DrawText();

		static void OnAnimation();
		static void OffAnimation();
		static void idle();
		static void menu(int value);
		static void display();
		static void reshape(int, int);
		static void keyboard(unsigned char, int, int);
		static void mouse(int, int, int, int);
		static void motion(int, int);
	public:
		/*******************************************************
		* コンストラクタを隠し、instance()からのみオブジェクトを
		* 生成することでインスタンスの数を制御する。
		*******************************************************/
		static Glutxx<T>* instance(int* argc, char** argv, int x, int y);
		virtual ~Glutxx();

		static std::list<Obj3D<T>*> m_draw_list;
		void loop();
	};

	// スタティック変数 ---------------------------------------------------
	template<class T>
	std::list<Obj3D<T>*> Glutxx<T>::m_draw_list;

	template<class T>
	Glutxx<T>* Glutxx<T>::m_instance = 0;
	template<class T>
	int Glutxx<T>::m_button = 0;
	template<class T>
	int Glutxx<T>::m_state = 0;
	template<class T>
	int Glutxx<T>::m_x = 0;
	template<class T>
	int Glutxx<T>::m_y = 0;
	template<class T>
	GLfloat Glutxx<T>::m_rot_x = 0;
	template<class T>
	GLfloat Glutxx<T>::m_rot_y = 0;
	template<class T>
	GLfloat Glutxx<T>::m_rot_z = 0;
	template<class T>
	GLfloat Glutxx<T>::m_rot_matrix[16] = {0};
	template<class T>
	GLfloat Glutxx<T>::m_scale = 1;
	template<class T>
	GLenum Glutxx<T>::m_front = GL_FILL;
	template<class T>
	GLenum Glutxx<T>::m_back = GL_LINE;

	// コンストラクタ ------------------------------------------------------
	template<class T>
	Glutxx<T>::Glutxx(int* argc, char** argv, int x, int y)
	{
		const int WINDOWSYSTEM_POSITION = -1;
		glutInit(argc, argv);
		glutInitWindowSize(x, y);
		glutInitWindowPosition(WINDOWSYSTEM_POSITION, WINDOWSYSTEM_POSITION);
		glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
		glutCreateWindow("3DViewer");

		glutDisplayFunc (display);
		glutReshapeFunc (reshape);
		glutMouseFunc   (mouse);
		glutMotionFunc  (motion);
		glutKeyboardFunc(keyboard);
		glutCreateMenu  (menu);
		glutAddMenuEntry("OnAnimation", 1);
		glutAddMenuEntry("OffAnimation", 2);
		glutAddMenuEntry("Exit    ESC", 3);
		glutAttachMenu  (GLUT_RIGHT_BUTTON);

		glClearColor(0, 0, 0, 0);

		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);

		static GLfloat spec[] = { 0.5, 0.5, 0.5, 1 };
		static GLfloat shin[] = { 0.5 };
		static GLfloat diff[] = { 1, 1, 1, 0 };
		static GLfloat ambi[] = { 0.1, 0.1, 0.1, 1 };
		static GLfloat l_pos[] = { 20, 20, 20, 0 };

		glShadeModel(GL_SMOOTH);

		glLightfv(GL_LIGHT0, GL_POSITION, l_pos);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, diff);
		glLightfv(GL_LIGHT0, GL_AMBIENT, ambi);
		glLightfv(GL_LIGHT0, GL_SPECULAR, spec);

		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_NORMALIZE);
	}

	// 単一インスタンスの生成 ---------------------------------------------
	template<class T>
	Glutxx<T>* Glutxx<T>::instance(int* argc, char** argv, int x, int y)
	{
		if(m_instance == 0){
			m_instance = new Glutxx<T>(argc, argv, x, y);
			return m_instance;
		}
		else {
			return m_instance;
		}
	}

	// デストラクタ --------------------------------------------------------
	template<class T>
	Glutxx<T>::~Glutxx()
	{
		if(m_instance != 0){
			delete m_instance;
			m_instance = 0;

			for( std::list<Obj3D<T>*>::iterator itr = m_draw_list.begin(); itr != m_draw_list.end(); itr++){
				delete *itr;
			}
		}
	}

	// テキスト描画関数 ----------------------------------------------------
	template<class T>
	void Glutxx<T>::DrawText()
	{
		glDisable(GL_LIGHTING);
		glColor3f(1,1,1);
		char str_menu[] = "left drag : rotate";
		char str_menu2[] = "right click : exit";
		glRasterPos2f(0, 1.6);

		int i;
		for(i=0; str_menu[i]; i++){
			glutBitmapCharacter(GLUT_BITMAP_8_BY_13, str_menu[i]);
		}
		glRasterPos2f(0, 1.4);
		for(i=0; str_menu[i]; i++){
			glutBitmapCharacter(GLUT_BITMAP_8_BY_13, str_menu2[i]);
		}
	}

	// 描画関数 ------------------------------------------------------------
	template<class T>
	void Glutxx<T>::display()
	{
		using namespace std;
		glMatrixMode(GL_MODELVIEW);
		glGetFloatv(GL_MODELVIEW_MATRIX, m_rot_matrix);
		glLoadIdentity();
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		DrawText();

		glEnable(GL_LIGHTING);

		// 視点の回転を反映
		glRotatef(m_rot_y, 1, 0, 0);
		glRotatef(m_rot_x, 0, 1, 0);
		glMultMatrixf(m_rot_matrix);

		glPushMatrix();
			static GLfloat l_pos[] = { 100, 100, 100, 0 };
			// 保持しているオブジェクトを描画
			for(list<Obj3D<T>*>::iterator itr = m_draw_list.begin(); itr != m_draw_list.end(); itr++){
				(*itr)->Draw();
			}
			glutWireCube(3);
		glPopMatrix();

		// 描画バッファ交換
		glutSwapBuffers();
	}

	// メインループ --------------------------------------------------------
	template<class T>
	void Glutxx<T>::loop()
	{
		glutMainLoop();
	}


	template<class T>
	void Glutxx<T>::reshape(int w, int h)
	{
		glViewport(0, 0, (GLsizei)w, (GLsizei)h);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.001, 1000.0);
		gluLookAt(0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	// メニュールーチン ----------------------------------------------------
	template<class T>
	void Glutxx<T>::menu(int value)
	{
		switch(value){
			case 1:
				OnAnimation();
				break;
			case 2:
				OffAnimation();
				break;
			case 3:
				exit(0);
				break;
			default:
				break;
		}
	}

	template<class T>
	void Glutxx<T>::keyboard(unsigned char key, int x, int y)
	{
		const int ESC = 27;
		if(key == ESC){
			exit(0);
		}
	}

	template<class T>
	void Glutxx<T>::mouse(int button, int state, int x, int y)
	{
		m_button = button;
		m_state  = state;
		m_x      = x;
		m_y      = y;
	}

	template<class T>
	void Glutxx<T>::motion(int x, int y)
	{
		if(GLUT_DOWN == m_state){
			switch(m_button){
				case GLUT_LEFT_BUTTON:
					m_rot_y = y - m_y;
					m_rot_x = x - m_x;
					break;
				case GLUT_RIGHT_BUTTON:
					//if(m_scale <= 0){ m_scale = 0.000000001; }
					m_scale += 0.1;
					break;
				case GLUT_MIDDLE_BUTTON:
					m_scale -= 0.1;
				default:
					break;
			}
			m_x = x;
			m_y = y;
			glutPostRedisplay();
		}
	}

	template<class T>
	void Glutxx<T>::idle()
	{
		for(std::list<Obj3D<T>*>::iterator itr = m_draw_list.begin(); itr != m_draw_list.end(); itr++){
			(*itr)->UpdateTime(0);
		}
		glutPostRedisplay();
	}

	template<class T>
	void Glutxx<T>::OnAnimation()
	{
		glutIdleFunc(Glutxx<T>::idle);
	}

	template<class T>
	void Glutxx<T>::OffAnimation()
	{
		glutIdleFunc(0);
	}
}
#endif // #ifndef ATS_GLUTXX_H
