#include "../../util/check_error.h"
#include "../../util/pick_convert.h"
#include "../../view/geometry.h"
#include "../../view/state0.h"
#include "../actions/process_hits.h"
#include "../actions/set_ortho.h"
#include "../actions/zoom.h"
#include "mouse.h"
#include "reshape.h"
#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/*
 * A simple alias to make the code more readable.
 */
#define S state0

void
mouse (int button, int state, int x, int y)
{
  // Release right button.
  if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
      // Deactive a panning event if one was happening.
      S.pan.active = false;
    }

  // Release left button.
  if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
      if (S.selection.active &&
	  S.selection.purpose == ZOOM &&
	  glutGetModifiers () == GLUT_ACTIVE_CTRL)
	{
	  /*
	   * NOOP if the mouse was not moved.
	   */
	  if (x == S.selection.x || y == S.selection.y)
	    return;

	  /*
	   * Convert the selection boundary from window coordinates to
	   * world coordinates.
	   */
	  glMatrixMode (GL_MODELVIEW);
	  glLoadIdentity ();
	  GLdouble model[16];
	  glGetDoublev (GL_MODELVIEW_MATRIX, model);
	  GLdouble projection[16];
	  glGetDoublev (GL_PROJECTION_MATRIX, projection);
	  GLint viewport[4];
	  glGetIntegerv (GL_VIEWPORT, viewport);

	  check_error (__FILE__, __LINE__);

	  GLdouble start_position[3];
	  gluUnProject (x,
			viewport[3] - y,
			0,
			model,
			projection,
			viewport,
			&start_position[0],
			&start_position[1], &start_position[2]);

	  check_error (__FILE__, __LINE__);

	  GLdouble end_position[3];
	  gluUnProject (S.selection.x,
			viewport[3] - S.selection.y,
			0,
			model,
			projection,
			viewport,
			&end_position[0], &end_position[1], &end_position[2]);

	  check_error (__FILE__, __LINE__);

	  zoom (fmin (start_position[0], end_position[0]),
		fmin (start_position[1], end_position[1]),
		fmax (start_position[0], end_position[0]),
		fmax (start_position[1], end_position[1]));
	}

      /*
       * Complete a selection if one was started and not cancelled.
       */
      if (S.selection.active &&
	  S.selection.purpose == SET &&
	  glutGetModifiers () == GLUT_ACTIVE_CTRL)
	{

	  /*
	   * "Specify the array to be used for the returned hit records
	   * with glSelectBuffer () [Redbook]."
	   */
	  GLuint *select_buf = calloc (S.rows, sizeof (GLuint));
	  glSelectBuffer (S.rows, select_buf);

	  /*
	   * "Enter selection mode by specifying GL_SELECT with
	   * glRenderMode () [Redbook]."
	   */
	  glRenderMode (GL_SELECT);

	  /*
	   * "Initialize the name stack using glInitNames () and glPush
	   * Names () [Redbook]."
	   */
	  glInitNames ();
	  glPushName (0);

	  /*
	   * "Define the viewing volume you want to use for selection.
	   * Usually this is different from the viewing volume you
	   * originally used to draw the scene, so you probably want to
	   * save and then restore the current transformation state with
	   * glPushMatrix () and glPopMatrix () [Redbook]."
	   */
	  glMatrixMode (GL_PROJECTION);
	  glPushMatrix ();
	  glLoadIdentity ();

	  GLint viewport[4];
	  glGetIntegerv (GL_VIEWPORT, viewport);

	  double c_x = 0.0;
	  double c_y = 0.0;
	  double w = 0.0;
	  double h = 0.0;
	  pick_convert (S.selection.x, S.selection.y, x, y,
			&c_x, &c_y, &w, &h);

	  gluPickMatrix (c_x, (GLdouble) viewport[3] - c_y, w, h, viewport);

	  set_ortho ();

	  /*
	   * "Alternately issue primitive drawing commands and commands to
	   * manipulate the name stack so that each primitive of interest
	   * has appropriate names assigned [Redbook]."
	   */
	  geometry (GL_SELECT);

	  glMatrixMode (GL_PROJECTION);
	  glPopMatrix ();
	  glutSwapBuffers ();

	  /*
	   * "Exit selection mode and process the returned selection data
	   * (the hit records) [Redbook]."
	   */
	  GLint hits = glRenderMode (GL_RENDER);
	  check_error (__FILE__, __LINE__);

	  /* "process hits from selection mode rendering [Angel,2008]." */
	  process_hits (hits, select_buf);

	  /* "normal render [Angel,2008]." */
	  glutPostRedisplay ();
	}

    }

  // Begin selection.
  if (button == GLUT_LEFT_BUTTON &&
      state == GLUT_DOWN && glutGetModifiers () == GLUT_ACTIVE_CTRL)
    {
      S.selection.active = true;
      S.selection.x = x;
      S.selection.y = y;
    }

  // Pan.
  if (button == GLUT_RIGHT_BUTTON &&
      state == GLUT_DOWN && glutGetModifiers () != GLUT_ACTIVE_CTRL)
    {
      /*
       * Detection of the first point in a panning event.
       */
      if (S.pan.active == false)
	{
	  S.pan.active = true;
	  S.pan.begin[0] = x;
	  S.pan.begin[1] = y;
	}
    }

  return;
}