#include <GL/glu.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <xstep.h>

struct {

	struct OBJECT {

		char	*name;
		int  	vsize,psize;

		struct VECTOR { 
		
			GLfloat x,y,z; 

		} **vector;

		struct POLYGON { 

			int 	size,
				vector[64];
				
		} **polygon;

		struct OBJECT *next;

		GLint glxlint;
		
	} *object;

	GLfloat x,y,z,ix,iy,iz,b,ax,ay,az;

} glxworld;

XSWidget *desktop,*window,*control;

int light;

void GLXRedraw(XSWidget *wid) {

	struct OBJECT *aux;
	
	int z;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



	for(z=0,aux=glxworld.object;aux;aux=aux->next,z++) {

		glPushMatrix();
	
		glTranslatef(glxworld.ix,glxworld.iy,glxworld.iz);

		glRotatef(glxworld.x, 1.0, 0.0, 0.0);
		glRotatef(glxworld.y, 0.0, 1.0, 0.0);
		glRotatef(glxworld.z, 0.0, 0.0, 1.0);

		glCallList(aux->glxlint);

		glPopMatrix();
	}

	glXSwapBuffers(wid->display,wid->window);
}

void GLXResize(XSWidget *wid) {

	printf("*** CONFIGURE NOTIFY ***\n");

	XSConfigure(wid);

	glViewport(0, 0, (GLsizei) wid->width, (GLsizei) wid->height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();   
	gluPerspective(45, (GLfloat) wid->width/(GLfloat) wid->height, 0.1, 100);
//	gluPerspective(0.0, (GLfloat) wid->width/(GLfloat) wid->height, 1.0, 0.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslatef(0.0,0.0,-20.0);
}

void GLXKeyPress(XSWidget *wid) {

	int key=XLookupKeysym(&wid->event->xkey,0);

	switch(key) {

		case XK_Up:		glxworld.ax -= 1.0; break;
		case XK_Down:		glxworld.ax += 1.0; break;
		case XK_Left:		glxworld.ay -= 1.0; break;
		case XK_Right:		glxworld.ay += 1.0; break;
		case 'w':		glxworld.iz -= 0.5; break;
		case 'z':		glxworld.iz += 0.5; break;
		case 'a':		glxworld.az -= 0.5; break;
		case 'd':		glxworld.az += 0.5; break;

		case XK_Escape:		XSWindowClose(window);
//					XSWindowClose(control);
	}
}

void GLXButtonPress(XSWidget *wid) {

	XSetInputFocus(wid->display,wid->window,None,RevertToNone);	
}

void GLXCreate(XSWidget *wid) {

	GLfloat nx,ny,nz,ax,ay,az,bx,by,bz,sn,cx,cy,cz;
	GLfloat color[4];
	GLfloat LightAmbient[] 	= { 0.5, 0.5, 1.0, 1.0 };
	GLfloat LightDiffuse[] 	= { 0.5, 0.5, 1.0, 1.0 };
	GLfloat LightSpecular[] = { 0.5, 0.5, 1.0, 1.0 };
	GLfloat LightPosition[]	= { 1.0, 1.0, 1.0, 0.0 };
	GLint	materialspec	= 20;
	GLfloat specularity[]	= { 0.0, 0.0, 0.0, 1.0 };
	GLfloat MaterialSpecular [] = { 0, 0, 0, 1.0 };
	GLfloat MaterialShininess [] = { 100.0 };

	GLXContext glxcontext;
	XVisualInfo *glxvisual;

	struct OBJECT *aux;

	int x=0,y=0,z,glxattribute[] = { 

		GLX_RGBA,
		GLX_RED_SIZE, 	1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 	1,
		GLX_DOUBLEBUFFER,
		None 
	};

	XSWidgetCreate(wid);

	glxvisual = glXChooseVisual(desktop->display,
		DefaultScreen(desktop->display),
		glxattribute);

	if (!glxvisual) {

		fprintf(stderr,
			"fatal error: can't initialize GLX in display %s\n",
			desktop->global->name);

		exit(-1);
	}

	glxcontext = glXCreateContext(desktop->display,glxvisual,NULL,True);

	glXMakeCurrent(desktop->display,window->window,glxcontext);
/*
	glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmbient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR,LightSpecular);
*/
	glLightfv(GL_LIGHT0, GL_POSITION,LightPosition);

//	glMaterialfv(GL_FRONT, GL_SPECULAR, MaterialSpecular);
//	glMaterialfv(GL_FRONT, GL_SHININESS, MaterialShininess);

//	glClearColor(0.0,0.0,0.5,1.0);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);
	//glEnable(GL_NORMALIZE);
	//glEnable(GL_CULL_FACE);
	glShadeModel(GL_SMOOTH);

	//glCullFace(GL_BACK);
	glPolygonMode(GL_FRONT,GL_FILL);

        glEnable(GL_LINE_SMOOTH);
                        

	for(z=0,aux=glxworld.object;aux;aux=aux->next,z++) {



		color[0]=1.0;
		color[1]=1.0;
		color[2]=1.0;
		color[3]=1.0;

		aux->glxlint=glGenLists(1);
		glNewList(aux->glxlint,GL_COMPILE);

		fprintf(stderr,"get object %d/%d/%s",aux->glxlint,z,aux->name); 

		glMaterialfv(GL_FRONT,GL_SPECULAR,specularity);
		glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,color);
		glMateriali(GL_FRONT,GL_SHININESS,materialspec);

//		glLightModelfv(GL_LIGHT_MODEL_AMBIENT,LightAmbient);

		for(x=0;x!=aux->psize;x++) {

/*
			normal vector:

			          |nx ny nz|
			N = AxB = |ax ay az|=(ay*bz-az*by)nx+(az*bx-ax*bz)ny+(ax*by-ay*bx)nz
 			          |bx by bz|

			N = N/|N|

			A and B must be in the same plane.
*/

			ax = aux->vector[aux->polygon[x]->vector[1]]->x
			    -aux->vector[aux->polygon[x]->vector[0]]->x;
			ay = aux->vector[aux->polygon[x]->vector[1]]->y
			    -aux->vector[aux->polygon[x]->vector[0]]->y;
			az = aux->vector[aux->polygon[x]->vector[1]]->z
			    -aux->vector[aux->polygon[x]->vector[0]]->z;

			bx = aux->vector[aux->polygon[x]->vector[2]]->x
			    -aux->vector[aux->polygon[x]->vector[1]]->x;
			by = aux->vector[aux->polygon[x]->vector[2]]->y
			    -aux->vector[aux->polygon[x]->vector[1]]->y;
			bz = aux->vector[aux->polygon[x]->vector[2]]->z
			    -aux->vector[aux->polygon[x]->vector[1]]->z;

			nx=ay*bz-az*by;
			ny=az*bx-ax*bz;
			nz=ax*by-ay*bx;

			sn=sqrt(nx*nx+ny*ny+nz*nz);

			nx/=sn;
			ny/=sn;
			nz/=sn;

			// object

			glBegin(GL_TRIANGLE_FAN);
			
			glNormal3f(nx,ny,nz);
			glColor3f(1,0,0); 
			
			cx=cy=cz=0.0;
			
			for(y=0;y!=aux->polygon[x]->size;y++) {

				glVertex3f(
					aux->vector[aux->polygon[x]->vector[y]]->x,
					aux->vector[aux->polygon[x]->vector[y]]->y,
					aux->vector[aux->polygon[x]->vector[y]]->z);

				cx+=aux->vector[aux->polygon[x]->vector[y]]->x;
				cy+=aux->vector[aux->polygon[x]->vector[y]]->y;
				cz+=aux->vector[aux->polygon[x]->vector[y]]->z;
			}
			
			glEnd();

// line
/*
			cx=cx/(float)y;
			cy=cy/(float)y;
			cz=cz/(float)y;

			// AxB

			glBegin(GL_LINE_STRIP);
			glColor3f(0,1.0,0);
			glVertex3f(cx,cy,cz);
			glVertex3f(nx/40+cx,ny/40+cy,nz/40+cz);
			glEnd();
*/

		}
		glEndList();
	}

	wid->on.event[ConfigureNotify] 	= GLXResize;

}

struct OBJECT *GLXReadObject(char *name,struct OBJECT *last) {

	struct OBJECT *aux;

	int j,k;
	FILE *f;
	char buffer[4096];

	printf("loading object from file %s...",name);
	fflush(stdout);

	aux=(struct OBJECT *)malloc(sizeof(struct OBJECT));
		
	f=fopen(name,"r");
		
	do fgets(buffer,4096,f); while(*buffer=='#');
	
	aux->name=(char *)malloc(1+strlen(buffer));
	strcpy(aux->name,buffer);
	
	do fgets(buffer,4096,f); while(*buffer=='#');

	sscanf(buffer,"%d %d",&aux->vsize,&aux->psize);
		
	printf(" [detected: %d vectors, %d poligons]...",
		aux->vsize,
		aux->psize);
			
	fflush(stdout);

	aux->vector=(struct VECTOR **)malloc(aux->vsize*sizeof(struct VECTOR));
		
	for(j=0;j!=aux->vsize;j++) {
		
		aux->vector[j]=(struct VECTOR *)malloc(sizeof(struct VECTOR));
		
		fscanf(f,"%s %f %f %f",
			buffer,
			&aux->vector[j]->x,
			&aux->vector[j]->y,
			&aux->vector[j]->z);
			
		aux->vector[j]->x/=2.0;
		aux->vector[j]->y/=2.0;
		aux->vector[j]->z/=2.0;
	}

	aux->polygon=(struct POLYGON **)malloc(aux->psize*sizeof(struct POLYGON));
		
	for(j=0;j!=aux->psize;j++) {

		aux->polygon[j]=(struct POLYGON *)malloc(sizeof(struct POLYGON));		

		do fscanf(f,"%s",buffer); while(!atoi(buffer));
			
		aux->polygon[j]->size=atoi(buffer);

		for(k=0;k!=aux->polygon[j]->size;k++) {
		
			fscanf(f,"%d",&aux->polygon[j]->vector[k]);	
			aux->polygon[j]->vector[k]--;
		}
	}

	printf(" wow! done!\n");
		
	fclose(f);

	aux->next=last;

	return(aux);
}

void GLXQuit(XSWidget *wid) {

//	XSWindowClose(control);
	XSWindowClose(window);
}

int main(int argc, char **argv) {

	int base=0,i,fps=0;
		
	for(i=1;i!=argc;i++) 
		glxworld.object=GLXReadObject(argv[i],glxworld.object);

	desktop = XSDesktop(getenv("DISPLAY"),argv,argc);
/*
	control = XSWindow(desktop,0,0,400,200,"GLX Controls");
		  XSCheck (control,8,8+21*0,160,21,"Light",&light,1);
		  XSButton(control,-8,-8,72,24,"Quit",GLXQuit);
*/
	window = XSWindow(desktop,0,0,640,480,argv[0]);

	window->on.create 		= GLXCreate;
	window->on.event[KeyPress]	= GLXKeyPress;
	window->on.event[ButtonPress]	= GLXButtonPress;

/*
	glxworld.z+=60.0;
	glxworld.x+=-60.0;
	glxworld.y+=0.0;
*/
	while(XSCheckEvent(desktop,XS_NOBLOCK)) {

		if(desktop->child) GLXRedraw(window);

		if(base!=time(0)) {

			base=time(0);
			printf("%d fps (z=%f,x=%f,y=%f)\n",
				fps,
				glxworld.z,
				glxworld.x,
				glxworld.y);

			fps=0;

		} else fps++;
		
		glxworld.z+=glxworld.az;
		glxworld.x+=glxworld.ax;
		glxworld.y+=glxworld.ay;

		usleep(1);
	}
	return 0;
}