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


/* global definitions */

/* initial window size */

#define WIN_SIZE_X 400
#define WIN_SIZE_Y 400

/* global menu ids */

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

/* dimensions of the texture map */

#define checkImageWidth 16
#define checkImageHeight 16

/* global texture map */

static GLubyte textureImage[checkImageWidth][checkImageHeight][3];

/* actual window size */

static int win_width;
static int win_height;

/* possible mouse modes */

static enum {MM_ROTATE, MM_ZOOM, MM_MOVE} mouseMode;  

/* current position of the mouse pointer */

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

/* position of the global light */

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

/* some global definitions required by the navigation code */

static GLdouble prevRotMatrix[16] = {
	  1.0, 0.0, 0.0, 0.0,
      0.0, 1.0, 0.0, 0.0,
	  0.0, 0.0, 1.0, 0.0,
	  0.0, 0.0, 0.0, 1.0};

static GLdouble moveX      = 0.0;
static GLdouble moveY      = 0.0;

static GLdouble zoomFactor = 1.0;
int k;


/* implementation section */

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 generateTrackballMatrix(int mx, int my, int buttonPressed) {
  static int lastX = -1000;
  static int lastY = -1000;

  GLdouble rotX;
  GLdouble rotY;

  if (buttonPressed) {
	  lastX = mx;
	  lastY = my;
  } else {
	rotX  = mx - lastX;
	rotY  = my - lastY;
	lastX = mx;
	lastY = my;

    glPushMatrix();
      glLoadIdentity();
      glRotated(rotX, 0.0, 1.0, 0.0);
      glRotated(rotY, 1.0, 0.0, 0.0);
      glMultMatrixd(prevRotMatrix);
      glGetDoublev(GL_MODELVIEW_MATRIX, prevRotMatrix);
    glPopMatrix();
  }
}

void setZoomFactor(int x, int y, int button) {
  static int lastX;
  static int lastY;

  static GLdouble lastFactor;

  if (button) {
	lastX = x;
	lastY = y;
	lastFactor = zoomFactor;
  } else {
	zoomFactor = lastFactor + (lastY-y) / (GLdouble)win_height * 3.0;
	if (zoomFactor < 0.05)
	  zoomFactor = 0.05;
	if (zoomFactor > 2.0)
	  zoomFactor = 2.0;
  }
}

void setMoveOffset(int x, int y, int button) {
  static int lastX;
  static int lastY;

  static GLdouble lastMoveX;
  static GLdouble lastMoveY;

  if (button) {
	lastX = x;
	lastY = y;
	lastMoveX = moveX;
	lastMoveY = moveY;
  } else {
	moveX = lastMoveX + (x - lastX) / (GLdouble)win_width * 4.0 / zoomFactor; 
	  /* consider frustum settings */
	moveY = lastMoveY + (lastY - y) / (GLdouble)win_height * 4.0 / zoomFactor;
  }
}

void mouseButton (int button, int state, int x, int y) {
  switch(mouseMode) {
    case MM_ROTATE:
      if (state == GLUT_DOWN)
        generateTrackballMatrix(x,y,1);
	  break;
	case MM_ZOOM:
	  if (state == GLUT_DOWN)
		setZoomFactor(x,y,1);
	  break;
	case MM_MOVE:
	  if (state == GLUT_DOWN)
		setMoveOffset(x,y,1);
	  break;
  }
}

void mouseMotion (int x, int y) {
  mouse_x = x;
  mouse_y = y;
  switch(mouseMode){
    case MM_ROTATE: generateTrackballMatrix(x,y,0);break;
	case MM_ZOOM: setZoomFactor(x,y,0); break;
	case MM_MOVE: setMoveOffset(x,y,0); break;
  }
  glutPostRedisplay ();
}

void menu (int state) {
  switch (state)
    {
    case MODE_1:
	  mouseMode = MM_ROTATE;
      break;
    case MODE_2:
	  mouseMode = MM_ZOOM;
      break;
    case MODE_3:
	  mouseMode = MM_MOVE;
      break;
    default:			/* define your default action */ ;
    }
}


void initTex() {
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    /* descibes the internal storage format of the texture given by 
	   the field textureImage */

  
  glTexImage2D(GL_TEXTURE_2D, 0, 3, checkImageWidth,
	  checkImageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE,
	  &textureImage[0][0][0]);
    /* specifies the texture to be mapped */

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    /* both functions specify the handling of texture coordinates 
	   outside the range [0,1].
	   CLAMP  - texture coordinates are clamped to the minimum or
	            maximum texture coordinates respectively (0, 1).
	   REPEAT - if you have a large plane with texture coordinates 
	            from 0 to 10 in both directions, you will get
				100 copies of the texture tiled together on the
				the screen */

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    /* controls filtering on magnification of texture maps, possible
	   settings are:
	     GL_NEAREST - no filtering
		 GL_LINEAR  - bilinear filitering
     */

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    /* controls filtering on minification, possible settings are
	   GL_NEAREST, GL_LINEAR,
	   in addition there are different settings for MIPMAPs
	   available */

  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
    /* defines how texels are combined with the color values
	   of the fragment being processed */

  glEnable(GL_TEXTURE_2D);
    /* activates texture mapping related to 2D-textures */
}


/* drawing functions */

void generateCheckerTexture() {
  int i,j,c;

  for (i=0;i<checkImageWidth;i++)
	  for (j=0;j<checkImageHeight;j++) {
		  switch(k) {
			case 1: c = ((((i&0x2) == 0)^((j&0x2)) == 0)) * 255;
				    textureImage[i][j][0] =  (GLubyte) c;
		            textureImage[i][j][1] =  (GLubyte) c;
		            textureImage[i][j][2] =  (GLubyte) c;
				    break;
			case 2: c = ((((i&0x1) == 0)^((j&0x1)) == 0)) * 255;
				    textureImage[i][j][0] =  (GLubyte) (c*k)%255;
		            textureImage[i][j][1] =  (GLubyte) (c*10*k)%255;
		            textureImage[i][j][2] =  (GLubyte) (c&k)%255;    
				    break;
			case 3: c = ((((i&0x5) == 0)^((j&0x5)) == 0)) * 255;
			        textureImage[i][j][0] =  (GLubyte) (c*i*i+1)%255;
		            textureImage[i][j][1] =  (GLubyte) (c*j*j+1)%255;
		            textureImage[i][j][2] =  (GLubyte) (c*i*j+1)%255;    
				    break;
		    case 4: c = ((((i&0x7) == 0)^((j&0x3)) == 0)) * 255;
			        textureImage[i][j][0] =  (GLubyte) (c+5*(j*j+1))%255;
		            textureImage[i][j][1] =  (GLubyte) (c+5*(j*i+1))%255;
		            textureImage[i][j][2] =  (GLubyte) (c+5*(k*i*j+1))%255;
				    break;
			case 5: c = ((((i&0x7) == 0)^((j&0x3)) == 0)) * 255;
				    textureImage[i][j][0] =  (GLubyte) (c+50*(j*j+1))%255;
		            textureImage[i][j][1] =  (GLubyte) (c+50*(j*i+1))%255;
		            textureImage[i][j][2] =  (GLubyte) (c+50*(k*i*j+1))%255;
				    break;
			case 6: c = ((((i&0x4) == 0)^((j&0x4)) == 0)) * 255;
					textureImage[i][j][0] =  (GLubyte) (c+(j*j))%255;
		            textureImage[i][j][1] =  (GLubyte) (c+(j*i))%255;
		            textureImage[i][j][2] =  (GLubyte) (c+(k*i*j))%255;
					break;
		  }
		  
      }
  initTex();
}

void drawMyObjects () {
 
  /* Viewing transformation - using identity matrix */

  glTranslated(moveX, moveY, 0.0);
  glScalef(zoomFactor, zoomFactor, zoomFactor);
  glMultMatrixd(prevRotMatrix);

  /* Model transformations */

  glRotatef(45., 0.0, 1.0, 1.0);
  
  k = 1;
  generateCheckerTexture();
  glColor3f(1.0, 1.0, 0.0);
  glBegin(GL_QUADS);
    glNormal3f(0.0, 0.0, 1.0);
    glTexCoord2f(0.0, 0.0); glVertex3f(-0.5, -0.5, 0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.5, -0.5, 0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(0.5, 0.5, 0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(-0.5, 0.5, 0.5);
  glEnd();
  
  k = 2;
  generateCheckerTexture();	  
  glColor3f(0.5, 0.8, 0.0);
  glBegin(GL_QUADS);
   	glNormal3f(1.0, 0.0, 0.0);
  	glTexCoord2f(0.0, 0.0); glVertex3f(0.5, 0.5, -0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.5, 0.5, 0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(0.5, -0.5, 0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(0.5, -0.5, -0.5);
  glEnd();	

  k = 3;
  generateCheckerTexture(); 
  glColor3f(1.0, 0.0, 0.0);
  glBegin(GL_QUADS);
    glNormal3f(0.0, 1.0, 0.0);
  	glTexCoord2f(0.0, 0.0); glVertex3f(0.5, 0.5, -0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.5, 0.5, 0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(-0.5, 0.5, 0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(-0.5, 0.5, -0.5);
  glEnd();		
  
  k = 4;
  generateCheckerTexture(); 
  glColor3f(0.0, 0.0, 1.0);
  glBegin(GL_QUADS);
    glNormal3f(-1.0, 0.0, 0.0);
    glTexCoord2f(0.0, 0.0); glVertex3f(-0.5, 0.5, -0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(-0.5, 0.5, 0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(-0.5, -0.5, 0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(-0.5, -0.5, -0.5);
  glEnd();
  
  k = 5;
  generateCheckerTexture(); ;
  glColor3f(0.0, 1.0, 0.0);
  glBegin(GL_QUADS);
    glNormal3f(0.0, -1.0, 0.0);
    glTexCoord2f(0.0, 0.0); glVertex3f(0.5, -0.5, -0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.5, -0.5, 0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(-0.5, -0.5, 0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(-0.5, -0.5, -0.5);
  glEnd();
  
  k = 6;
  generateCheckerTexture();   
  glColor3f(0.0, 0.0, 1.0);
  glBegin(GL_QUADS);
    glNormal3f(0.0, 0.0, -1.0);
    glTexCoord2f(0.0, 0.0); glVertex3f(-0.5, -0.5, -0.5);
	glTexCoord2f(1.0, 0.0); glVertex3f(0.5, -0.5, -0.5);
	glTexCoord2f(1.0, 1.0); glVertex3f(0.5, 0.5, -0.5);
	glTexCoord2f(0.0, 1.0); glVertex3f(-0.5, 0.5, -0.5);
  glEnd();
}

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 ();
  gluPerspective(45.0, win_width/(GLdouble)win_height, 3.0, 7.0);
  glTranslated(0.0, 0.0, -5.5); 

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

  /* setup some lighting flags */
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
 
  glShadeModel(GL_FLAT);
}

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 ("Rotate", MODE_1);
  glutAddMenuEntry ("Zoom", MODE_2);
  glutAddMenuEntry ("Move", MODE_3);
  glutAttachMenu (GLUT_RIGHT_BUTTON);
}

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