/**************************************************
 *   File:     Interaction.cpp
 *   Author:   Dr. Dalton R. Hunkins     
 *   Date:     September 2008
 *
 *
 *  Modes of User Interaction
 *   Mouse:
 *     Left-Click  - select an object
 *     Left Drag   - move the selected object
 *     Right-Click - triggers popup menu
 *   Keyboard:
 *     r, R - change color of object to Red
 *     g, G - change color of object to Green
 *     b, B - change color of object to Blue
 *     c, C - change color of object to Cyan
 *     m, M - change color of object to Magenta
 *     y, Y - change color of object to Yellow
 *     Up Arrow   - scale up slected object
 *     Down Arrow - scale down selecte object
 *
**************************************************/
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>


#include "Object.h"

 
// Set Constants used in Menu
#define  MENU_CLOSE   900
#define  MENU_RESET   901

#define  MENU_RED     913
#define  MENU_GREEN   914
#define  MENU_BLUE    915
#define  MENU_YELLOW  916
#define  MENU_CYAN    917
#define  MENU_MAGENTA 918

#define NUMBER_OF_OBJECTS 7

class ProgramExit{ };

int mainMenuId;
int setColorMenuId;

float red[]     = {1.0, 0.0, 0.0};
float green[]   = {0.0, 1.0, 0.0};
float blue[]    = {0.0, 0.0, 1.0};
float yellow[]  = {1.0, 1.0, 0.0};
float cyan[]    = {0.0, 1.0, 1.0};
float magenta[] = {1.0, 0.0, 1.0};   

int    originalCursor;
bool   translating = false;
double currentScale = 0.0;


// Declare Objects
Object *objects[NUMBER_OF_OBJECTS];
Object *selectedObject = NULL;

int deviceWindowWidth  = 500;
int deviceWindowHeight = 500;

struct {
    int width;
    int height;
} deviceWindow;

struct {
    double left;
    double right;
    double bottom;
    double top;
    double width;
    double height;
} projectionWindow;

   
void init() {
	glClearColor(0.0, 0.0, 0.0, 1.0);
	
      
      //   Set Projection Window Values 
	projectionWindow.left   = projectionWindow.bottom = -1.0;
	projectionWindow.right  = projectionWindow.top    =  1.0;
	projectionWindow.width  = 2.0;
	projectionWindow.height = 2.0;
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(projectionWindow.left,   projectionWindow.right, 
               projectionWindow.bottom, projectionWindow.top);
	
      
      //  Construct the Objects
	objects[0] = new Square(red, 0.05, 0.75, 0.75);
	objects[1] = new Disk(cyan, 0.1, 0.0, 0.0);
	objects[2] = new Triangle(yellow, 0.15, -0.75,-0.75);
	objects[3] = new Square(yellow, 0.08, -0.50, 0.50);
	objects[4] = new Disk(magenta,  0.15, -0.50, 0.50);
	objects[5] = new Disk(green, 0.1, 0.60, -0.25);
	objects[6] = new Triangle(red,  0.25, 0.60, -0.25);
	
	currentScale = 0.0;
}


void reshape(int width, int height) {
	deviceWindow.width  = width;
	deviceWindow.height = height;
      glViewport(0, 0, width, height);
}	


double getProjectionX(int x) {
    return ( projectionWindow.width / (double) deviceWindow.width) * x
  	         + projectionWindow.left;
}


double getProjectionY(int y) {
    return (-projectionWindow.height / (double) deviceWindow.height) * y
	         + projectionWindow.top;
}		

 void display() {
	glClear(GL_COLOR_BUFFER_BIT);
	for (int i=NUMBER_OF_OBJECTS-1; i>=0; i--)
       objects[i]->draw();
    glutSwapBuffers();
}

void reset() {
	currentScale = 0.0;
	for (int i=0; i<NUMBER_OF_OBJECTS; i++)
	   objects[i]->reset();
}	   
      

void menuHandler(int choice) {
	switch (choice) {
	   case MENU_RED :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(red);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_GREEN :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(green);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_BLUE :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(blue);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_YELLOW :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(yellow);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_CYAN :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(cyan);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_MAGENTA :
	      if (selectedObject != NULL) {
	         selectedObject->setColor(magenta);
	         glutPostRedisplay();
	      }    
	      break;
	   case MENU_RESET :
	      reset();
	      break;
	   case MENU_CLOSE :
	      throw ProgramExit();
	      break;
	}
}	


void createMenu() {
    mainMenuId     = glutCreateMenu(menuHandler);
    setColorMenuId = glutCreateMenu(menuHandler);

    
    //  Create Main Menu Entries
    glutSetMenu(mainMenuId);
    glutAddSubMenu("Set Color",       setColorMenuId);
    glutAddMenuEntry("Reset",         MENU_RESET);
    glutAddMenuEntry("Close",         MENU_CLOSE);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    
    
    //  Create SubMenu Entries
    glutSetMenu(setColorMenuId);
    glutAddMenuEntry("Red     ", MENU_RED);
    glutAddMenuEntry("Green   ", MENU_GREEN);
    glutAddMenuEntry("Blue    ", MENU_BLUE);
    glutAddMenuEntry("Yellow  ", MENU_YELLOW);
    glutAddMenuEntry("Cyan    ", MENU_CYAN);
    glutAddMenuEntry("Magenta ", MENU_MAGENTA);    
}


void keyboard(unsigned char key, int x, int y) {
  	  switch (key) {
  	  	 case 'r' :
  	  	 case 'R' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(red);
	           glutPostRedisplay();
	        }   
	        break;
  	  	 case 'g' :
  	  	 case 'G' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(green);
	           glutPostRedisplay();
	        }   
	        break; 
  	  	 case 'b' :
  	  	 case 'B' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(blue);
	           glutPostRedisplay();
	        }   
	        break; 
  	  	 case 'y' :
  	  	 case 'Y' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(yellow);
	           glutPostRedisplay();
	        }   
	        break; 
  	  	 case 'c' :
  	  	 case 'C' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(cyan);
	           glutPostRedisplay();
	        }   
	        break; 
  	  	 case 'm' :
  	  	 case 'M' :
	        if (selectedObject != NULL) {
	           selectedObject->setColor(magenta);
	           glutPostRedisplay();
	        }   
	        break; 
  	  }  	  	 
  }	
     
  
  void specialKeyboard(int key, int x, int y) {
  	 if (selectedObject != NULL)
  	    switch (key) {
  	 	  case GLUT_KEY_UP :
  	 	     selectedObject->incrementScale(0.01);
  	 	     glutPostRedisplay();
  	 	     break;
  	 	  case GLUT_KEY_DOWN :
  	 	     selectedObject->incrementScale(-0.01);
  	 	     glutPostRedisplay();
  	 	     break;
  	    } 	   
  }	


Object* findObject(double x, double y) {
     Object *object = NULL;
     for (int i=0; i<NUMBER_OF_OBJECTS; i++)
  	  if (objects[i]->isOver(x, y)) {
  	     object = objects[i];
  	     break;
  	  }

      for (int i=0; i<NUMBER_OF_OBJECTS; i++)
          objects[i]->setAsCurrentObject(false); 
      if (object != NULL)
         object->setAsCurrentObject(true);     	      
  	return object;     	 
 }	

 
 void mouse(int button, int state, int x, int y) {
  	 if (button == GLUT_LEFT_BUTTON)
  	 	if (state == GLUT_DOWN) {	
		   selectedObject = 
		          findObject(getProjectionX(x),
					         getProjectionY(y));
		   if (selectedObject != NULL) {
		   	  originalCursor = glutGet(GLUT_WINDOW_CURSOR);
    	      glutSetCursor(GLUT_CURSOR_INFO);
    	      translating = true; 
		   }
  	 	}   
  	 	else
  	 	   if (translating) {
  	 	   	  translating = false;
  	 	      glutSetCursor(originalCursor);	
  	 	      glutPostRedisplay();
  	 	  }			 	
  }
  
  void motion(int x, int y) {
  	 if (translating) {
  	 	selectedObject->setTranslation(
  	 	       getProjectionX(x),
  	 	       getProjectionY(y));
        glutPostRedisplay();  
  	 }   	 	                                  
  } 	
  
int main (int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(10, 10);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Interaction");
    
    init();
        
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    createMenu();

    
    // Register Callback Functions
    glutMouseFunc(mouse);
    glutMotionFunc(motion);

    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeyboard);
    
    
    try {
      glutMainLoop();
    }
    catch (ProgramExit) { }
 	
    return 0;
}