#include "cs148.h"

int winw = 640;
int winh = 480;

// Keep track of where the mouse is
int mouse_lastx=0, mouse_lasty=0;

struct cube {
  float x,y,z;
  float edge;
  float r,g,b;
  cube() {
    x = y = z = 0.0;
    r = g = b = 0.8;
    edge = 0.7;
  }
};

#define NUMCUBES 3

// My array of cubes
cube cubes[NUMCUBES];

// Am I dragging a cube with the mouse?  I'll set this to
// -1 if I'm not...
int draggingcube = -1;

// Which button is being dragged?
int dragging_button = GLUT_LEFT_BUTTON;

void init(void);
void lights(void);


void init(void) {
  glClearColor (0.0, 0.0, 0.0, 0.0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_SMOOTH);
  glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);

  // Create my cubes
  for(int i=0; i<NUMCUBES; i++) {
    cubes[i].y = cubes[i].z = 0;
    cubes[i].x = float(i-1) * 2.0;    
    
    // Give this cube a color
    float* color = &(cubes[i].r);
    if (i<3) color[i] = 1.0;
  }

}



// Draws my cubes.  Each cube is given a unique name
// if we're in selection mode.
void drawCubes(GLenum mode) {

  for(int i=0; i<NUMCUBES; i++) {
    if (mode == GL_SELECT) glPushName(i);
    glPushMatrix();
    glTranslatef(cubes[i].x,cubes[i].y,cubes[i].z);
    glColor3f(cubes[i].r,cubes[i].g,cubes[i].b);
    glutSolidCube(cubes[i].edge);
    glPopMatrix();
    if (mode == GL_SELECT) glPopName ();
  }
}


// Figure out whether the user clicked on any of my cubes
void processHits (GLint hits, GLuint buffer[]) {   

  // How many total hits did we get?
  printf ("\n\nTotal hits = %d\n", hits);

  if (hits == 0) {
    draggingcube = -1;
    return;
  }

  // What's the closest (least-far-away) hit we've seen so far?
  int closest_hit_name = 0;

  // Set the closest depth to a random large value    
  float closest_hit_depth = 100.0;

  // We'll use this pointer to walk through the buffer
  int* ptr = (int*)buffer;

  // For each hit
  for (int curhit = 0; curhit < hits; curhit++) {

    // How many names were there for this hit?
    int numnames = *ptr;
  
    // We should really only have one name per hit, since our
    // program never places more than one name on the stack
    // at once...
    if (numnames != 1) {
      printf("This is odd; I should never see more than one name per hit...\n");
    }

    ptr++;
    // What were the minimum and maximum depth values?
  
    // We like to convert from the integer depth range to the
    // range (0,1) to make the results more readable
    float mindepth = (float) *ptr/0x7fffffff; ptr++;
    float maxdepth = (float) *ptr/0x7fffffff; ptr++;

    int curname = *ptr;
  
    for(int n=0; n<numnames; n++) ptr++;

    // If we've already seen a hit closer than this one, we don't care 
    // about this one...
    if (mindepth >= closest_hit_depth) continue;
      
    closest_hit_depth = mindepth;
    closest_hit_name = curname;
  }

  // Now we know the closest hit, let's drag that cube around
  draggingcube = closest_hit_name;

  printf("Dragging cube %d\n",draggingcube);
}


#define BUFSIZE 512
void mouseclick(int button, int state, int x, int y) {

  mouse_lastx = x;
  mouse_lasty = y;

  GLuint selectBuf[BUFSIZE];
  GLint hits;
  GLint viewport[4];

  // We only care about left mouse clicks
  if (state != GLUT_DOWN) {
    draggingcube = -1;
    return;
  }
  dragging_button = button;

  // Tell GL where to put selection results and turn on selection mode
  glSelectBuffer(BUFSIZE, selectBuf);
  glRenderMode(GL_SELECT);

  // Clear the name stack
  glInitNames();

  // Get the current viewport dimensions
  glGetIntegerv(GL_VIEWPORT, viewport);

  // We're going to build a new projection matrix so everything far 
  // away from the mouse click gets displayed   
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();

  // Create a 5x5 pixel picking region near cursor location
  gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3] - y), 5.0, 5.0, viewport);
  float aspect = (float)winw / (float)winh;
  gluPerspective(45.0,aspect,1.0,20.0);

  glMatrixMode(GL_MODELVIEW);

  // Do our rendering (nothing will be drawn to the framebuffer, because
  // we're in selection mode)
  drawCubes(GL_SELECT);

  // Fix the projection matrix
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  glMatrixMode(GL_MODELVIEW);

  glFlush();

  // How many hits did we get?
  hits = glRenderMode(GL_RENDER);
  
  // Handle the hits
  processHits(hits, selectBuf);
  glutPostRedisplay();
} 


void mousedrag(int x, int y) {
   
  if (draggingcube == -1) return;
  
  #define MOUSE_SCALE_FACTOR 100.0

  if (dragging_button == GLUT_LEFT_BUTTON) {
    cubes[draggingcube].x += (float)(x - mouse_lastx) / MOUSE_SCALE_FACTOR;
    cubes[draggingcube].y -= (float)(y - mouse_lasty) / MOUSE_SCALE_FACTOR;
  }
  else {
    cubes[draggingcube].x += (float)(x - mouse_lastx) / MOUSE_SCALE_FACTOR;
    cubes[draggingcube].z += (float)(y - mouse_lasty) / MOUSE_SCALE_FACTOR;
  }

  mouse_lastx = x; mouse_lasty = y;
  glutPostRedisplay();
}


void display(void) {
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   lights();
   drawCubes(GL_RENDER);
   glutSwapBuffers();
}


void reshape(int w, int h) {
   winw = w; winh = h;
   glViewport(0, 0, w, h);

   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   float aspect = (float)winw / (float)winh;
   gluPerspective(45.0,aspect,1.0,20.0);

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}


int main(int argc, char** argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);
  glutInitWindowSize (winw, winh);
  glutInitWindowPosition (100, 100);
  glutCreateWindow (argv[0]);
  init ();
  glutReshapeFunc (reshape);
  glutDisplayFunc(display); 
  glutMouseFunc(mouseclick);
  glutMotionFunc(mousedrag);
  glutMainLoop();
  return 0; 
}


// Turn the light on
void lights(void) {

  // The position of the light
  GLfloat position[] =  {3.0, 3.0, 3.0, 1.0};

  GLfloat color[] = {0.5,0.5,0.5,1};
  GLfloat acolor[] = {0.4,0.4,0.4,1};
  
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  
  glLightfv(GL_LIGHT0, GL_POSITION, position);
  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 80.0);
  glLightfv(GL_LIGHT0, GL_AMBIENT, acolor);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, color);
  glLightfv(GL_LIGHT0, GL_SPECULAR, color);

}

