#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>
#include <GL/gl.h>
//#include <GL/glx.h>
//#include <GL/glut.h>
//#include <GL/glext.h>
//#include <GL/glew.h>
#include <sys/time.h>

#define GLWIDTH 1600
#define GLHEIGHT 1200
#define WIDTH 1600
#define HEIGHT 1200

//#define CYL_DIA 92.5
#define BORE 92.5
#define STROKE 89
//#define ROD_LEN 
#define ROD_BE 53
#define ROD_SE 22

int windowPosX = 0;
int windowPosY = 0;
int windowWidth = 1600;
int windowHeight = 1200;

int w = 1600;
int h = 1200;
GLUquadricObj *quadratic[17];
double Eye_X = 0;
double Eye_Y = 0.0;
double Eye_Z = -1000;
double Eye_dist = 100;
double Cen_X = 0;
double Cen_Y = 0;
double Cen_Z = 0;
int crank_angle = 0;
struct timeval starttime,endtime;
char scorestring[16];
int spark_cylinder = 0;
float y_rotation = 10;
float x_rotation = -90;
float y_angle = 11;
int fullscreen = 0;
//char cycle[5] = {'P', 'E', 'I', 'C', '\0'};
//char cycle_chars[5] = {'P', 'E', 'C', 'I', '\0'};

char cycle_chars[] = "PEIC";
//unsigned char cycle_order[4] = {0, 1, 2, 3};
int cycle_cur[4] = {0, 1, 3, 2};

float cyl_x[] = {(BORE*3)+20, (BORE)+10, (BORE*-1), (BORE*-3)-10};
float rod_x[] = {(BORE*3)+20+ROD_SE, (BORE)+10, (BORE*-1), (BORE*-3)-10-ROD_SE};

void Run_GL(void);
static void display(void);
static void reshape(int w, int h);
//static float Calc_Above_BDC(int piston, int crank_angle);
static float Calc_Below_TDC(int piston, int crank_angle);
static void Frames(double fps);
static void Cycle(int crank_angle);
static void special(int key, int x, int y);
static void mouse(int button, int state, int x, int y);
static void mouse_pos(int x, int y);
void keyboard(unsigned char key, int x, int y);

int main(int argc, char **argv){
	glutInit(&argc,argv);
	Run_GL();
	return 0;
}

void Run_GL(void){
	int i = 0;
    glutInitDisplayMode (GLUT_DOUBLE| GLUT_RGB | GLUT_DEPTH);
//	glutGameModeString( "1600x1200" );
//glutEnterGameMode();
    glutInitWindowSize(GLWIDTH, GLHEIGHT);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("bla");
//glutEnterGameMode();
//glutRedisplayFunc(display);
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutIgnoreKeyRepeat(0);
    glutSpecialFunc(special);
	glutKeyboardFunc(keyboard);
	//glutMouseFunc(mouse);
	glutPassiveMotionFunc(mouse_pos);
	while(i<17){
		quadratic[i]=gluNewQuadric();
		i++;
	}
//Eye_Z=Eye_dist*sin(y_angle);
//Eye_X=Eye_dist*cos(y_angle);

	//glEnable(GL_DEPTH_TEST);
	glutMainLoop();
}

static void display(void){
	int i = 0;
	//float j = 1;
	//float cyl_x[] = {(GLWIDTH-200)/4*-2, (GLWIDTH-200)/4*-1, (GLWIDTH-200)/4*1, (GLWIDTH-200)/4*2};
	//float cyl_x[] = {BORE*-2-20, BORE*-1-10, 0, BORE+10};
	//float cyl_x[] = {100, 110+BORE, 120+BORE*2, 130+BORE*3};
	//float cyl_x[] = {2*BORE, BORE, 0, -1*BORE};
    //static struct timespec ts;
    //ts.tv_sec = 0;
    //ts.tv_nsec = 50000000;
    static double time1 = 2;
    static double frame_c = 0;
    static double fps = 0;

	//float cyl_x[] = {(BORE*3)+20, (BORE)+10, (BORE*-1), (BORE*-3)-10};
	//float cyl_y[] = {0, 0, 0, 0};
	//float pist_x[] = {(BORE)*3+20, (BORE)+10, (BORE)*-1, (BORE)*-3-10};
	//float cran_x[] = {
	float mm_above_bdc = STROKE;

	GLint params[4];

//if( IsExtensionSupported( "GL_ARB_vertex_buffer_object" ) ){

sleep(1);
printf("OpenGL Version: %s\n", glGetString(GL_VERSION));
glGetIntegerv(GL_VBO_FREE_MEMORY_ATI, params);
//glGetIntegerv(TEXTURE_FREE_MEMORY_ATI, params);
//glGetIntegerv(RENDERBUFFER_FREE_MEMORY_ATI, &params[0]);
//glGetIntegerv(0x87FB, &params[0]);
//glGetIntegerv(VBO_FREE_MEMORY_ATI, &params[0]);

printf("total free: %d\n", params[0]);
//printf("largest free block: %d\n", params[1]);
//printf("auxiliary free: %d\n", params[2]);
//printf("aux largest free block: %d\n", params[3]);
//}
/*
        VBO_FREE_MEMORY_ATI                     0x87FB
        TEXTURE_FREE_MEMORY_ATI                 0x87FC
        RENDERBUFFER_FREE_MEMORY_ATI            0x87FD

#ifndef GL_ATI_meminfo
#define GL_VBO_FREE_MEMORY_ATI            0x87FB
#define GL_TEXTURE_FREE_MEMORY_ATI        0x87FC
#define GL_RENDERBUFFER_FREE_MEMORY_ATI   0x87FD
#endif

#ifndef GL_ATI_meminfo
#define GL_ATI_meminfo 1
#endif

      param[0] - total memory free in the pool
      param[1] - largest available free block in the pool
      param[2] - total auxiliary memory free
      param[3] - largest auxiliary free block
*/



	//float x_cyc_coord[] = {
	//float y_cyc_coord[] = {
	//GLUquadricObj *quadratic;
//GLfloat LightAmbient[]=  { 0.5, 0.5, 0.5, 1.0 };    // Ambient Light Values
//GLfloat LightDiffuse[]=  { 1.0, 1.0, 1.0, 1.0 };    // Diffuse Light Values
//GLfloat LightPosition[]= { 0.0, 0.0, 2.0, 1.0 };    // Light Position
    glClearColor (0.0,0.0,0.0,0.0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();
gluLookAt ( Eye_X, Eye_Y, Eye_Z,  Cen_X, Cen_Y, Cen_Z, 0.0, 0.1, 0.0);
//    gluLookAt ( Eye_X, Eye_Y, Eye_Z,  Eye_X, Eye_Y, 0.0, 0.0, 0.1, 0.0);



/*
	for(i=0; i<4; i++){
glLoadIdentity();
		glTranslatef(cyl_x[i],cyl_y[i],3000.0);
		glRotatef(90,1.0,0.0,0.0);
		//quadratic=gluNewQuadric();
		gluQuadricNormals(quadratic[i], GLU_SMOOTH);
		glColor3f(j,0.0,0.0);
		j -= .25;
		gluCylinder(quadratic[i],100.0,100.0,200.0,128,128);
	}
*/
glEnable(GL_DEPTH_TEST);
glEnable (GL_BLEND); 
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//glDepthMask(GL_TRUE);
	for(i=0; i<4; i++){
		//spark?
		if(i == spark_cylinder){
			glPushMatrix();
			glColor4f(0.75,0.65,0.75, 0.77);

			glTranslatef(cyl_x[i], STROKE*2, 800);

			//y rotation
//			glRotatef(y_rotation,0.0,1.0,0.0);

			glRotatef(x_rotation, 1, 0, 0.0);
			glRotatef(y_rotation,0.0,0.0,1.0);
			gluCylinder(quadratic[i],BORE/8,BORE/16,STROKE/2,128,128);
			glPopMatrix();
		}
		//gluSphere(
			glPushMatrix();
			glColor4f(0.95,0.95,0.95, 0.77);
			glTranslatef(cyl_x[i], STROKE*2+STROKE/2, 800);
			glRotatef(x_rotation, 1, 0, 0.0);
			glRotatef(y_rotation,0.0,0.0,1.0);
			gluCylinder(quadratic[i+1],BORE/12,BORE/12,STROKE/8,128,128);
			glPopMatrix();

		//draw piston
        glPushMatrix();
        glColor4f(0.95,0.05,0.05, 0.77);
        mm_above_bdc = STROKE - Calc_Below_TDC(i, crank_angle);
        //mm_above_bdc = Calc_Above_BDC(i, crank_angle);

        glTranslatef(cyl_x[i], mm_above_bdc, 800.5);
//		glRotatef(y_rotation,0.0,1.0,0.0);
        glRotatef(x_rotation, 1, 0, 0.0);
glRotatef(y_rotation,0.0,0.0,1.0);
        gluCylinder(quadratic[5+i],BORE-1,BORE-1,STROKE,256,256);
        glPopMatrix();



		//draw cylinder/bore
		glPushMatrix();
		glColor4f(0.25,0.25,0.25, 0.77);
		//glRotatef(90, 1, 0, 0.0);

		glTranslatef(cyl_x[i], 0, 800);
		//glTranslatef(GLWIDTH/2-(i*(BORE)), 0, 1000.0);
//		glRotatef(y_rotation,0.0,1.0,0.0);
		glRotatef(x_rotation, 1, 0, 0.0);
glRotatef(y_rotation,0.0,0.0,1.0);
		gluCylinder(quadratic[9+i],BORE,BORE,STROKE*2,256,256);
		glPopMatrix();
//		glTranslatef(-128, 0, 8000.0);
//		glRotatef(90,cyl_x[i],cyl_y[i],800.0);
		//draw piston

/*
		glPushMatrix();
		glColor4f(0.45,0.45,0.45, .85);
		mm_above_bdc = STROKE - Calc_Below_TDC(i, crank_angle);
		//mm_above_bdc = Calc_Above_BDC(i, crank_angle);
		glTranslatef(pist_x[i], mm_above_bdc, 801);
		glRotatef(-90, 1, 0, 0.0);
		gluCylinder(quadratic[i],BORE-2,BORE-2,STROKE*2,256,256);
		glPopMatrix();
*/
		//draw connecting rods
		glPushMatrix();
		glColor4f(0.25,0.85,0.25, 0.77);
		glTranslatef(rod_x[i], mm_above_bdc-STROKE/2, 800+BORE/2);
		glRotatef(x_rotation, 0, 1, 0.0);
//		glRotatef(-90,1.0,0.0,0.0);
		glBegin(GL_QUADS);
        glVertex3f(0,         170/2,    0.0);
        glVertex3f(ROD_SE,    170/2,    0.0);
        glVertex3f(0,         170/2*-1, 0.0);
        glVertex3f(ROD_BE,    170/2*-1, 0.0);


/*
		glVertex3f(ROD_SE/2*-1, 170/2, 0.0);
		glVertex3f(ROD_SE/2,    170/2, 0.0);
		glVertex3f(ROD_BE/2*-1, 170/2*-1, 0.0);
		glVertex3f(ROD_BE/2,    170/2*-1, 0.0);
*/
		glEnd();

		//gluCylinder(quadratic[12+i],ROD_BE,ROD_SE,170,256,256);


		glPopMatrix();
	}
	//draw crank
	glPushMatrix();
	glColor4f(0.25,0.25,0.25, 0.77);

	glTranslatef((BORE*10/2), STROKE*-1, 800);
	glRotatef(x_rotation, 0, 1, 0.0);
glRotatef(crank_angle,0.0,0.0,1.0);
	gluCylinder(quadratic[16],ROD_BE,ROD_BE,BORE*10,256,256);

	glPopMatrix();





//	if(crank_angle<360)
//		crank_angle++;
//	else
//		crank_angle = 0;
 //   glLoadIdentity();
//    gluLookAt ( Eye_X, Eye_Y, Eye_Z,  Eye_X, Eye_Y, 0.0, 0.0, 0.1, 0.0);

        frame_c++; //frame counter
        if(time1 > 1){
            gettimeofday(&endtime, NULL);
            time1=((double)(endtime.tv_sec*1000000-starttime.tv_sec*1000000+endtime.tv_usec-starttime.tv_usec))/1000000;
            fps = frame_c / time1;
            gettimeofday(&starttime, NULL);
            frame_c = 0;
        }else{
            gettimeofday(&endtime, NULL);
            time1=((double)(endtime.tv_sec*1000000-starttime.tv_sec*1000000+endtime.tv_usec-starttime.tv_usec))/1000000;
        }
//glDepthMask(GL_FALSE);

glDisable(GL_DEPTH_TEST);
//glDepthMask(GL_FALSE);
//(GL_BLEND);
	//Cycle(crank_angle);
	Frames(fps);
	//sleep(1);
    if(crank_angle<359)
        crank_angle++;
    else
        crank_angle = 0;


	if(crank_angle % 180 == 0){
		for(i=0; i<4; i++){
			if(cycle_cur[i] < 3)
				cycle_cur[i]++;
			else
				cycle_cur[i] = 0;
		}

	}
	Cycle(crank_angle);
//    if(crank_angle<360)
//        crank_angle++;
//    else
//        crank_angle = 0;

//glRotatef(-90, 1, 0, 0.0);
    glutSwapBuffers();
    glutPostRedisplay();

}

static float Calc_Below_TDC(int piston, int crank_angle){
	//power exhaust intake compression
	//all that matters here is the position of the piston
	//crank spins clockwise
	//0deg = 1+4  180deg = 2+3 tdc
/*
	const float multed = 180*STROKE;
	int opt = 0;
	opt = (piston == 0 || piston == 3) ? 1 : 0;

	if(crank_angle < 180){
		if(opt)
			return ((float)crank_angle/multed);
		return ((float)(180-crank_angle)/multed);
	}else{
		if(opt)
			return ((float)(360-crank_angle)/multed);
		return ((float)(crank_angle-180)/multed);
	}

*/
	if(piston == 0 || piston == 3){
		if(crank_angle < 180){
			//subtract from tdc
			return ((float)crank_angle/180*STROKE);
		}else{
			//add to tdc
			return ((float)(360-crank_angle)/180*STROKE);
			//return ((float)crank_angle/180*STROKE*-1);
		}
	}else{
		//return ((float)(360-crank_angle)/180*STROKE*-1);
		//0 == 1*STROKE here  180 == 0
		//if(crank_angle < 180){
			//add to tdc
		//	return ((float)180/crank_angle*STROKE);
		//}else{
			//subtract from tdc
		//	return ((float)(360-crank_angle)/180*STROKE);
		//}
		if(crank_angle < 180){
			return ((float)(180-crank_angle)/180*STROKE);
		}else{
			return ((float)(crank_angle-180)/180*STROKE);
		}
	}

}

static void reshape(int w, int h){
    //GLfloat aspect = (GLfloat)WIDTH / (GLfloat)(HEIGHT+LEGEND_H+4.4);;
	GLfloat aspect = w/h;
	//GLfloat aspect = (GLfloat)WIDTH / (GLfloat)(HEIGHT);
    glViewport (0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (60, (GLfloat)w / (GLfloat)h/aspect, 0.25 , 5000.0);
    glMatrixMode (GL_MODELVIEW);
}

static void Frames(double fps){
    (void)memset(scorestring, '\0', 16);
    //int empty = 0;
    (void)sprintf(scorestring, "%.02lf", fps);
    float pos[3] = {-1, 10, -25};
    int i = 0;
    glPushMatrix();
    glLoadIdentity();
    pos[0]+=0.1;
    glColor3f(1.0, 0.0, 0.0);
    glRasterPos3f(pos[0], pos[1], pos[2]);
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'F');
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'P');
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'S');

//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'(');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'c');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'a');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'p');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'2');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,'0');
//    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,')');
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,':');
    glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
    for(i = 0; scorestring[i] != '\0'; i++){
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, scorestring[i]);
    }
    glPopMatrix();
}

static void Cycle(int crank_angle){
	int i = 0;
	int j = 0;
	(void)memset(scorestring, '\0', 16);

	for(i=0; i<4; i++){
		j = cycle_cur[i];
		if(j == 0){
			//Make A Spark
			spark_cylinder = i;
			(void)strcat(scorestring, "P");
		}else if(j==1){
			(void)strcat(scorestring, "E");
		}else if(j==2){
			(void)strcat(scorestring, "I");
		}else{
			(void)strcat(scorestring, "C");
		}
		//glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
		//glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
	}
	//(void)sprintf(scorestring, "%c", );

//	for(i=0; i<4; i++){
		glPushMatrix();
		glLoadIdentity();
		glColor3f(1.0, 0.0, 0.0);

		glRasterPos3f(-3, 5, -25);

    //glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
    for(i = 0; scorestring[i] != '\0'; i++){
        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, scorestring[i]);
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
		glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,' ');
    }

/*
			j = (int)cycle_cur[i];

			if(j == 0){
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, 'P');
			}else if(j==1){
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, 'E');
			}else if(j==2){
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, 'I');
			}else{
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, 'C');
			}
*/
//			glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, cycle_chars[(int)cycle_cur[1]]);
//			glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, cycle_chars[(int)cycle_cur[2]]);
//			glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, cycle_chars[(int)cycle_cur[3]]);

		glPopMatrix();
//	}
}


void keyboard(unsigned char key, int x, int y){
	if(key==27){
exit(1);
//return;
//		glutLeaveGameMode();
//		exit(0);
		if(!fullscreen)
glutFullScreen();
//			glutEnterGameMode();
		else
reshape(GLWIDTH, GLHEIGHT);
//			glutLeaveGameMode();
		fullscreen ^= 1;

	}

}
static void special(int key, int x, int y){


	//printf("x: %u y: %u\n", x, y);
   switch(key){

      case GLUT_KEY_F1:    // F1: Toggle between full-screen and windowed mode
         fullscreen = !fullscreen;         // Toggle state
         if(fullscreen){                     // Full-screen mode
            windowPosX   = glutGet(GLUT_WINDOW_X); // Save parameters
            windowPosY   = glutGet(GLUT_WINDOW_Y);
            windowWidth  = glutGet(GLUT_WINDOW_WIDTH);
            windowHeight = glutGet(GLUT_WINDOW_HEIGHT);
            glutFullScreen();                      // Switch into full screen
         } else {                                         // Windowed mode

            glutReshapeWindow(windowWidth, windowHeight); // Switch into windowed mode
            glutPositionWindow(windowPosX, windowPosX);   // Postion top-left corner
         }

        case GLUT_KEY_UP:
//glutLeaveGameMode();
			Eye_Z+=10;
			//Eye_Y+=10;
			//Cen_Y+=10;
			//Cen_Y = Eye_Y;
            //Eye_Y+=Eye_dist/10;
            break;
        case GLUT_KEY_DOWN:
			Eye_Z-=10;
			//Eye_Y-=10;
			//Cen_Y-=10;
			//Cen_Y = Eye_Y;
            //Eye_Y-=Eye_dist/10;
            break;
        case GLUT_KEY_RIGHT:
			Eye_X-=10;
			Cen_X-=10;
			//Cen_X = Eye_X;
            //Eye_X-=Eye_Z/10;
            break;
        case GLUT_KEY_LEFT:
			Eye_X+=10;
			Cen_X+=10;
			//Cen_X = Eye_X;
            //Eye_X+=Eye_Z/10;
            break;
        case GLUT_KEY_PAGE_UP:
			Eye_Y+=10;
			//Cen_Y+=10;
            //Eye_dist+=Eye_dist/10;
			//Eye_Z=Eye_dist*sin(y_angle);
			//Eye_X=Eye_dist*cos(y_angle);
			//Cen_X = Eye_X;
//Cen_Z = Eye_dist;
            break;
        case GLUT_KEY_PAGE_DOWN:
			Eye_Y-=10;
            //Eye_dist-=Eye_dist/10;
			//Eye_Z=Eye_dist*sin(y_angle);
			//Eye_X=Eye_dist*cos(y_angle);
			//Cen_X = Eye_X;
//Cen_Z = Eye_dist;
            break;
		case GLUT_KEY_HOME:
			if(y_angle<360)
				y_angle++;
			else
				y_angle=0;
Eye_Z=Eye_dist*cos(y_angle);
Eye_X=Eye_dist*sin(y_angle);
//Cen_X = Eye_X;
//Cen_Z = Eye_dist;
			break;
		case GLUT_KEY_END:
			if(y_angle>0)
				y_angle--;
			else
				y_angle = 360;
Eye_Z=Eye_dist*cos(y_angle);
Eye_X=Eye_dist*sin(y_angle);
//Cen_X = Eye_X;
//Cen_Z = Eye_dist;
			break;
    }
//Eye_Z=Eye_dist*sin(y_angle);
//Eye_X=Eye_dist*cos(y_angle);

}

static void mouse(int button, int state, int x, int y){
	printf("x: %u y: %u\n", x, y);

}

static void mouse_pos(int x, int y){
	//printf("x: %u y: %u\n", x, y);
//	if(x < WIDTH/2)
//		Cen_X = x/-2;
//	else
		static int last_x = 0;
		static int last_y = 0;

		last_x-=x/2;
		last_y-=y/2;

		Cen_X += last_x;
		//Cen_X = WIDTH/2+x*-1;//WIDTH*sin(x/WIDTH*360);
//	if(y < HEIGHT/2)
//		Cen_Y = y/-2;
//	else
		Cen_Y += last_y;
		//Cen_Y = HEIGHT/2+y*-1;//HEIGHT*sin(x/HEIGHT*360);
	last_x = x/2;
	last_y = y/2;
}