#include <stdlib.h>
#include <stdio.h>
#include "GL/glut.h"

/* global definitions */

#define WIN_SIZE_X 400
#define WIN_SIZE_Y 400

#define MODE_1 1
#define MODE_2 2
#define MODE_3 3

/* values for high level primitives */
#define SLICES 24
#define STACKS 24
/* step size for rotations / translations */
#define STEP 5.0

int win_width;
int win_height;

/* dimensions of the robot */
struct Arm {
	float x;  /* actual rotation angle x-axis for arm2, arm3 */
	float y;  /* actual rotation angle y-axis for arm2, arm3
		  		  		maximal height of arm1                     */
	float z;  /* rotation angle z-axis */
	float h;  /* (variable) height of current arm */
	float r1; /* radius of the bottom */
	float r2; /* radius of the top  */
};
struct mySocket {
	float l; /* length of the prism */
	float h; /* height of the prism */
};
struct Link {
	float r; /* radius of the spheres */
};

struct Arm arm[5]; 
struct mySocket mySocket;
struct Link link;

static GLfloat lightPos[4] = {3.0, 3.0, 3.0, 1.0};

void drawSocket () {
	GLUquadricObj *quadric = gluNewQuadric ();

	glShadeModel (GL_FLAT);
 /* body */
	gluCylinder (quadric, mySocket.l/2, mySocket.l/2, mySocket.h/2, 4, 1);
	/* top */
	glTranslatef (0.0, 0.0, mySocket.h/2);
	gluDisk (quadric, 0.0, mySocket.l/2, 4, 1);
	
	gluDeleteQuadric (quadric);
}

void drawArm (int armNr) {
	GLUquadricObj *quadric = gluNewQuadric ();
	
	glShadeModel (GL_FLAT);
	gluCylinder (quadric, arm[armNr].r1, arm[armNr].r2, arm[armNr].h, SLICES, STACKS);
	glTranslatef (0.0, 0.0, arm[armNr].h);
	
	gluDeleteQuadric (quadric);
}

void drawLink () {
	GLUquadricObj *sphere = gluNewQuadric ();

	glShadeModel (GL_SMOOTH);
	gluSphere (sphere, 10.0, SLICES, STACKS);

	gluDeleteQuadric (sphere);
}

void setShadowMatrix (GLfloat planeEqn[4], GLfloat lightPos[4]) {
  GLdouble matrix[4][4];
  GLdouble dotProd = 0.0;
  int      i, j;

  for (i=0;i<4;i++)
	dotProd += planeEqn[i] * lightPos[i];

  for (i=0;i<4;i++)
	for (j=0;j<4;j++)
	matrix[j][i] = ((i==j) ? dotProd:0.0) - planeEqn[j] * lightPos[i];

  glMultMatrixd((GLdouble *) matrix);
}

void setupShadowMatrix (GLdouble a, GLdouble b, GLdouble c, GLdouble d,
					   GLdouble dx, GLdouble dy, GLdouble dz) {

  GLfloat v1[4];
  GLfloat v2[4];
  
  v1[0] = a;
  v1[1] = b;
  v1[2] = c;
  v1[3] = d;
  v2[0] = lightPos[0];
  v2[1] = lightPos[1];
  v2[2] = lightPos[2];
  v2[3] = lightPos[3];
  
  setShadowMatrix(v1, v2);
}

void drawMyObjects () {
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix ();
	
	drawSocket ();
	
	drawArm (1);

	drawLink ();
	
	glRotatef (arm[2].x, 1.0, 0.0, 0.0);
	glRotatef (arm[2].y, 0.0, 1.0, 0.0);
	glRotatef (arm[2].z, 0.0, 0.0, 1.0);
	drawArm (2);

	drawLink ();

	glRotatef (arm[3].x, 1.0, 0.0, 0.0);
	glRotatef (arm[3].y, 0.0, 1.0, 0.0);
	glRotatef (arm[3].z, 0.0, 0.0, 1.0);
	drawArm (3);

	drawArm (4); /* the peak */
	
	glPopMatrix ();

	glutSwapBuffers ();
}


void keyboard (unsigned char key, int x, int y) {
  int nr;
	switch (key) {
		/* Repositioning of the light */
    case 'd': lightPos[0] +=0.3; glutPostRedisplay(); break;
		case 'a': lightPos[0] -=0.3; glutPostRedisplay(); break;
		case 'w': lightPos[1] +=0.3; glutPostRedisplay(); break;
		case 's': lightPos[1] -=0.3; glutPostRedisplay(); break;

		/* Rotations of arm2 and arm3 */
		case 'x': arm[2].x += STEP; break;
		case 'X': arm[2].x -= STEP; break;
		case 'y': arm[2].y += STEP; break;
		case 'Y': arm[2].y -= STEP; break;
		case 'z': arm[2].z += STEP; break;
		case 'Z': arm[2].z -= STEP; break;
	
		case 'i': arm[3].x += STEP; break;
		case 'I': arm[3].x -= STEP; break;
		case 'j': arm[3].y += STEP; break;
		case 'J': arm[3].y -= STEP; break;
		case 'k': arm[3].z += STEP; break;
		case 'K': arm[3].z -= STEP; break;
	
		/* Translation of arm1 */
		case 'h': arm[1].h += STEP;
							if (arm[1].h > arm[1].y) arm[1].h = 60.0;
							break;
		case 'H': arm[1].h -= STEP;
							if (arm[1].h < 0.0) arm[1].h = 0.0;
							break;
		case 27: exit (0);	/* 27 = ascii-code for escape */
	  default: printf ("key: %c\n", key);
  }

	for (nr = 1; nr <= 3; nr++) {    /* Check range */
		if (arm[nr].x > 360.0) arm[nr].x -= 360.0;
		if (arm[nr].y > 360.0) arm[nr].y -= 360.0;
		if (arm[nr].z > 360.0) arm[nr].z -= 360.0;
		if (arm[nr].x < 0.0) arm[nr].x += 360.0;
		if (arm[nr].y < 0.0) arm[nr].y += 360.0;
		if (arm[nr].z < 0.0) arm[nr].z += 360.0;
	}

	drawMyObjects (); /* redraws the robot */
}
	

void display (void) {
  GLfloat lightAmbient[] = {0.0, 0.0, 0.0, 1.0};
  GLfloat lightDiffuse[] = {1.0, 1.0, 1.0, 1.0};
  GLfloat lightSpecular[] = {1.0, 1.0, 1.0, 1.0};
  GLfloat lightPosition[] = {1.0, 1.0, 1.0, 0.0};

  GLfloat materialAmbient[] = {1.0f, 1.0f, 1.0f, 1.0f};
  GLfloat materialDiffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
  GLfloat materialSpecular[] = {0.0f, 0.0f, 0.0f, 1.0f};
  GLfloat materialEmission[] = {0.0f, 0.0f, 0.0f, 1.0f};

  glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
	glColor3f (0.0, 0.5, 1.0);

	glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);

  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_AND_BACK, GL_AMBIENT, materialAmbient);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, materialDiffuse);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, materialSpecular);
  glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, materialEmission);
  glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 1.0); /* very low shininess */

  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
  /* enables you to select material diffuse lighting property by glColor*() */


  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glFrustum(-2.0, 2.0, -2.0, 2.0, 18.0, 28.0);
  glTranslated(0.0, 0.0, -22.0); 

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

	glTranslatef (-1.5, -1.5, -2.0);
	glRotatef (-60.0, 1.0, 0.0, 0.0);
	glScalef (0.02, 0.02, 0.02);

  drawMyObjects ();
}

void reshape (int w, int h) {
  win_width = w;
  win_height = h;

  glViewport (0, 0, w, h);
}

void initValues (void) {  
	/* dimensions of the robot */
	mySocket.l = 80.0;
	mySocket.h = 40.0;
	
	arm[1].r1 = 10.0;
	arm[1].r2 = 10.0;
	arm[1].y = 60.0;

	arm[2].r1 = 10.0;
	arm[2].r2 = 10.0;
	arm[2].h = 100.0;

	arm[3].r1 = 10.0;
	arm[3].r2 = 5.0;
	arm[3].h = 80.0;

	arm[4].r1 = 5.0;
	arm[4].r2 = 0.0;
	arm[4].h = 10.0;

	link.r = 10.0;

	/* start values for the rotations/translations   */
	/* (arm1.. translation, arm2 and arm3.. rotation */
	arm[1].h = 30.0; 

	arm[2].x = 15.0;
	arm[2].y = 45.0;
	arm[2].z = 0.0;
	arm[3].x = 10.0;
	arm[3].y = 90.0;
	arm[3].z = 0.0;
}

void InitialGLUT (void) {
  int menu_idx;
  char title[] = "my robot";

  win_width = WIN_SIZE_X;
  win_height = WIN_SIZE_Y;


  glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  glutInitWindowSize (win_width, win_height);
  glutInitWindowPosition (0, 0);
  glutCreateWindow (title);

  initValues ();

  glutDisplayFunc (display);
  glutReshapeFunc (reshape);
  glutKeyboardFunc (keyboard);

}

void main (int argc, char **argv) {
  glutInit (&argc, argv);
  InitialGLUT ();
  glutMainLoop ();
}
