#include <stdlib.h>
#include <stdio.h>

#ifdef _WIN32
#  include "GL/glos.h"
#endif

#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

int win_width;
int win_height;

int mouse_x = WIN_SIZE_X / 2;
int mouse_y = WIN_SIZE_Y / 2;

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 keyboard (unsigned char key, int x, int y) {
  if (key == 27)		/* ascii-code for escape */
    exit (0);
  else switch(key) {
    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;
  }
}


void mouseButton (int button, int state, int x, int y) {
  if (state == GLUT_DOWN)
    {
      if (button == GLUT_LEFT_BUTTON)
	printf ("you pressed the left mousebutton at (%i/%i)\n", x, y);
    }
  else
   printf ("you released a mousebutton\n");
   lightPos[0] =  -8.0 * (0.5 - (x / (double)WIN_SIZE_X));
   lightPos[1] =  8.0 * (0.5 - (y / (double)WIN_SIZE_Y));
   glutPostRedisplay();

}

void mouseMotion (int x, int y) {
  printf ("current position: (%i/%i)\n", x, y);
  mouse_x = x;
  mouse_y = y;
  lightPos[0] =  -8.0 * (0.5 - (x / (double)WIN_SIZE_X));
  lightPos[1] =  8.0 * (0.5 - (y / (double)WIN_SIZE_Y));
  glutPostRedisplay ();
}

void menu (int state) {
  switch (state)
    {
    case MODE_1:
      printf ("you chose mode 1\n");
      break;
    case MODE_2:
      printf ("you chose mode 2\n");
      break;
    case MODE_3:
      printf ("you chose mode 3\n");
      break;
    default:			/* define your default action */ ;
    }
}

void drawQuad() {
  glBegin(GL_QUADS);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(1.0, 0.0, 0.0);
    glVertex3f(1.0, 1.0, 0.0);
    glVertex3f(0.0, 1.0, 0.0);
  glEnd();
}

void drawCube() {
  glutSolidCube(1.0);
}

void drawSpecularSphere() {
  GLUquadricObj *sphere = gluNewQuadric();
  GLfloat materialAmbient[] = {0.0f, 0.0f, 1.0f, 1.0f};
  GLfloat materialDiffuse[] = {0.0f, 0.0f, 1.0f, 1.0f};
  GLfloat materialSpecular[] = {1.0f, 1.0f, 1.0f, 1.0f};
  GLfloat materialEmission[] = {0.0f, 0.0f, 0.1f, 1.0f};
 
  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, 25.0);

  glShadeModel(GL_SMOOTH);  /* activates Gouraud-Shading */
  glPushMatrix();
    gluSphere(sphere, 0.5, 16, 8);
  glPopMatrix();

  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 () {
  /* Viewing transformation - using identity matrix */
	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 ();

  /* Model transformations */
  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
  glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
  glPushMatrix();
    glTranslated(1.0, 0.0, 0.0);
    glRotatef(45, 0.0, 1.0, 0.0);
    drawCube();
  glPopMatrix();
  glDisable(GL_COLOR_MATERIAL);
  glPushMatrix();
    glTranslated(-1.0, 0.0, 0.0); 
    drawSpecularSphere(); 
  glPopMatrix();

  glDisable(GL_LIGHTING);
  glColor3f(0.0, 0.0, 0.0);
  glPushMatrix();
    setupShadowMatrix(0.0, 0.0, 1.0, 0.9, lightPos[0],
		                                  lightPos[1],
										  lightPos[2]);
	glPushMatrix();
      glTranslated(-1.0, 0.0, 0.0); 
      drawSpecularSphere();
	glPopMatrix();
    glTranslated(1.0, 0.0, 0.0);
      glRotatef(45, 0.0, 1.0, 0.0);
      drawCube();
    glPopMatrix();
  glPopMatrix();
  glEnable(GL_LIGHTING);

}

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};

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glFrustum(-2.0, 2.0, -2.0, 2.0, 20.0, 24.0);
  glTranslated(0.0, 0.0, -22.0); 

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
 
  glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, lightSpecular);
  glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

  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*() */
 
  drawMyObjects ();

  glutSwapBuffers ();
  glDisable(GL_COLOR_MATERIAL);
}

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

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

void myinit (void) {

  glClearColor(0.3f, 0.3f, 0.3f, 1.0f);

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

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

  win_width = WIN_SIZE_X;
  win_height = WIN_SIZE_Y;


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

  myinit ();

  glutDisplayFunc (display);
  glutReshapeFunc (reshape);
  glutKeyboardFunc (keyboard);
  glutMouseFunc (mouseButton);
  glutMotionFunc (mouseMotion);


  menu_idx = glutCreateMenu (menu);
  glutSetMenu (menu_idx);
  glutAddMenuEntry ("mode 1", MODE_1);
  glutAddMenuEntry ("------", -1);
  glutAddMenuEntry ("mode 2", MODE_2);
  glutAddMenuEntry ("mode 3", MODE_3);
  glutAttachMenu (GLUT_RIGHT_BUTTON);
}

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