/************************************************************************/
/************************************************************************/
// manual1.c: Allow user to control board using mouse.
// user must click mouse on board to start game. */

/************************************************************************/
/************************************************************************/

#include "stdafx.h" /* Windows stuff, make empty stdafx.h under Linux */

/************************************************************************/

#ifdef LINUX
#include <sys/time.h> /* This allows us to do control in real time */
#endif

#include <stdio.h>
#include <math.h>
#include <GL/glut.h>
#include "maze1.h"

/************************************************************************/
/* DEFINES */

#ifndef LINUX
#define M_PI 3.1415927
#endif

/* simulation modes */
#define PAUSED 0
#define RUNNING 1
#define GAME_OVER 2

/************************************************************************/
/* General Globals */

#ifdef LINUX
/* Stuff for figuring out what time it is on LINUX */
struct timeval tv_val;
struct timeval tv_val_last;
struct timezone tz_val;
#else
/* Stuff for figuring out what time it is on Windows (win32) */
long int last_time;
#endif

/************************************************************************/
/* GLUT/OPENGL Globals */

float view[4] = { -1.0, -1.0, 1.0, 1.0 };
GLsizei window_widthi, window_heighti;
GLfloat window_widthf, window_heightf;
float mouse_display[2] = { 0.0, 0.0 };

#define BACKGROUND_LIST 1
#define BALL_LIST 2

#define MAX_WINDOW_SIZE 400

#define N_POINTS_IN_CIRCLE 16 /* How circular are our holes? */

/************************************************************************/
/* Globals */

BOARD a_board;
BOARD *the_board;

/* Used to figure out appropriate velocity scale for planning. */
float max_states[N_STATES] = {-1e10,-1e10,-1e10,-1e10};
float min_states[N_STATES] = {1e10,1e10,1e10,1e10};

float manual_max_force = 0.1;
float force_display_scale = 1.0;

float force[2];

int simulation_mode = PAUSED;

/************************************************************************/
/* Generate forces on the ball */

int controller( BOARD *b )
{
  
  if ( simulation_mode == RUNNING )
    {
      force[XX] = (mouse_display[XX]/window_widthf)
	*(2*manual_max_force) - manual_max_force;
      force[YY] = ((window_heightf - mouse_display[YY])/window_heightf)
	*(2*manual_max_force) - manual_max_force;
      b->force[XX] = force[XX];
      b->force[YY] = force[YY];
      return TRUE;
    }
  force[XX] = 0.0;
  force[YY] = 0.0;
  b->force[XX] = force[XX];
  b->force[YY] = force[YY];
  return TRUE;
}

/************************************************************************/

void simulate( BOARD *b )
{
  float time_step, time_done;
  int i;
  static int count = 0;

  controller( b );

#ifdef LINUX
  gettimeofday( &tv_val, &tz_val );
  if ( tv_val.tv_sec == tv_val_last.tv_sec )
    {
      time_step = (tv_val.tv_usec - tv_val_last.tv_usec)*(1.0e-6);
    }
  else if ( tv_val.tv_sec == tv_val_last.tv_sec + 1 )
    {
      time_step = (1000000 + tv_val.tv_usec - tv_val_last.tv_usec)*(1.0e-6);
    }
  else
    {
      fprintf( stderr, "Real time clock error: %ld %ld\n",
	       tv_val.tv_sec, tv_val_last.tv_sec );
      exit( -1 );
    }
  tv_val_last.tv_sec = tv_val.tv_sec;
  tv_val_last.tv_usec = tv_val.tv_usec;
#else
  the_time = GetCurrentTime();
  dt = ((float) the_time - last_time )/1000.0;
  /* Guard against rollover, misunderstanding how GetCurrentTime works. */
  if ( dt < 0.05 ) 
    time_step = dt;
  else
    time_step = 0.01;
  last_time = the_time;
#endif

  if ( simulation_mode == RUNNING )
    {
      for( i = 0; i < 1000; i++ ) /* limit number of iterations */
	{
	  time_done = do_simulation( b, time_step );
	  time_step -= time_done;
	  if ( in_hole( b, b->puck ) )
	    {
	      simulation_mode = GAME_OVER;
	      printf( "GAME OVER\n" );
	      break;
	    }
	  if ( time_step < 1e-4 )
	    break;
	}
    }
  
  for( i = 0; i < N_STATES; i++ )
    {
      if ( b->puck[i] < min_states[i] )
	min_states[i] = b->puck[i];
      if ( b->puck[i] > max_states[i] )
	max_states[i] = b->puck[i];
    }
  count++;
  if ( count >= 100 )
    {
      /*
      printf( "min: %g %g %g %g\n", min_states[0], min_states[1],
	      min_states[2], min_states[3] );
      printf( "max: %g %g %g %g\n", max_states[0], max_states[1],
	      max_states[2], max_states[3] );
      */
      count = 0;
    }
}

/************************************************************************/

void display()
{
  BOARD *b;
  int ix, iy;
  float x_increment, y_increment, x1, y1, x2, y2;

  b = the_board;

  simulate( b );

  glClear( GL_COLOR_BUFFER_BIT );

  glCallList( BACKGROUND_LIST );

  if ( simulation_mode != GAME_OVER )
    {
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      glTranslatef( b->puck[XX], b->puck[YY], 0.0 );
      glCallList( BALL_LIST );
      glLoadIdentity();
    }

#ifdef COMMENT
  /* Put in hole map for debugging */
  x_increment = (b->board.p[RIGHT] - b->board.p[LEFT])/MAP_RESOLUTION;
  y_increment = (b->board.p[TOP] - b->board.p[BOTTOM])/MAP_RESOLUTION;
  for( ix = 0; ix < MAP_RESOLUTION; ix++ )
    {
      x1 = ix*x_increment;
      x2 = x1 + x_increment;
      for( iy = 0; iy < MAP_RESOLUTION; iy++ )
	{
	  y1 = iy*y_increment;
	  y2 = y1 + y_increment;
	  if ( b->hole_map[ix][iy] != NULL )
	    {
	      if ( b->hole_map[ix][iy]->next != NULL )
		glColor3f( 1.0, 1.0, 1.0 );
	      else
		glColor3f( 0.0, 1.0, 0.0 );
	      /*
	      glLineWidth( 1.0 );
	      glBegin( GL_LINE_LOOP );
	      */
	      glBegin( GL_POLYGON );
	      glVertex2f( x1, y1 );
	      glVertex2f( x1, y2 );
	      glVertex2f( x2, y2 );
	      glVertex2f( x2, y1 );
	      glEnd();
	    }
	}
    }

  /* Put in obstacle map for debugging */
  x_increment = (b->board.p[RIGHT] - b->board.p[LEFT])/MAP_RESOLUTION;
  y_increment = (b->board.p[TOP] - b->board.p[BOTTOM])/MAP_RESOLUTION;
  for( ix = 0; ix < MAP_RESOLUTION; ix++ )
    {
      x1 = ix*x_increment;
      x2 = x1 + x_increment;
      for( iy = 0; iy < MAP_RESOLUTION; iy++ )
	{
	  y1 = iy*y_increment;
	  y2 = y1 + y_increment;
	  if ( b->obstacle_map[ix][iy] != NULL )
	    {
	      if ( b->obstacle_map[ix][iy]->next != NULL )
		glColor3f( 0.0, 1.0, 1.0 );
	      else
		glColor3f( 1.0, 0.0, 0.0 );
	      /*
	      glLineWidth( 1.0 );
	      glBegin( GL_LINE_LOOP );
	      */
	      glBegin( GL_POLYGON );
	      glVertex2f( x1, y1 );
	      glVertex2f( x1, y2 );
	      glVertex2f( x2, y2 );
	      glVertex2f( x2, y1 );
	      glEnd();
	    }
	}
    }
#endif

  /* overlay force visualization */
  glColor3f( 0.0, 1.0, 0.0 );
  glLineWidth( 2.0 );
  glBegin( GL_LINES );
  glVertex2f( (view[LEFT] + view[RIGHT])/2, (view[TOP] + view[BOTTOM])/2 );
  glVertex2f( (view[LEFT] + view[RIGHT])/2 
	      + force[XX]*force_display_scale, 
	      (view[TOP] + view[BOTTOM])/2 
	      + force[YY]*force_display_scale );
  glEnd();

  /*
  glFlush();
  */
  glutSwapBuffers();
}

/************************************************************************/

void init_display( BOARD *b )
{
  int i, j;
  float increment, angle;

  view[LEFT] = b->board.p[LEFT];
  view[BOTTOM] = b->board.p[BOTTOM];
  view[RIGHT] = b->board.p[RIGHT];
  view[TOP] = b->board.p[TOP];

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  gluOrtho2D( view[LEFT], view[RIGHT], view[BOTTOM], view[TOP] );

  /* create background */
  glNewList( BACKGROUND_LIST, GL_COMPILE_AND_EXECUTE );
  glPushAttrib( GL_CURRENT_BIT );

  /* board */
  glColor3f( 255.0/255.0, 255.0/255.0, 198.0/255.0 );
  glRectf( b->board.p[LEFT], b->board.p[BOTTOM], 
	   b->board.p[RIGHT], b->board.p[TOP] );

  /* goal */
  glColor3f( 1.0, 0.0, 1.0 );
  glRectf( b->goal.p[LEFT], b->goal.p[BOTTOM], 
	   b->goal.p[RIGHT], b->goal.p[TOP] );

  /* holes */
  glColor3f( 0.0, 0.0, 0.0 );
  increment = 2*M_PI/N_POINTS_IN_CIRCLE;
  for( i = 0; i < b->n_holes; i++ )
    {
      glBegin( GL_TRIANGLE_FAN );
      glVertex2f( b->holes[i].p[XX], b->holes[i].p[YY] );
      glVertex2f( b->holes[i].p[XX] + b->holes[i].p[RADIUS], 
		  b->holes[i].p[YY] );
      angle = increment;
      for( j = 0; j < N_POINTS_IN_CIRCLE-1; j++ )
	{
	  glVertex2f( b->holes[i].p[XX] + b->holes[i].p[RADIUS]*cos(angle), 
		      b->holes[i].p[YY] + b->holes[i].p[RADIUS]*sin(angle) );
	  angle += increment;
	}
      glVertex2f( b->holes[i].p[XX] + b->holes[i].p[RADIUS], 
		  b->holes[i].p[YY] );
      glEnd();
    }

  /* obstacles */
  glColor3f( 225.0/255.0, 125.0/255.0, 50.0/255.0 );
  for( i = 0; i < b->n_obstacles; i++ )
    glRectf( b->obstacles[i].p[LEFT], b->obstacles[i].p[BOTTOM],
	     b->obstacles[i].p[RIGHT], b->obstacles[i].p[TOP] );

  /* end background */
  glPopAttrib();
  glEndList();

  /* create the ball */
  glNewList( BALL_LIST, GL_COMPILE );
  glPushAttrib( GL_CURRENT_BIT );
  glColor3f( 0.6, 0.6, 0.6 );
  increment = 2*M_PI/N_POINTS_IN_CIRCLE;
  glBegin( GL_TRIANGLE_FAN );
  glVertex2f( 0.0, 0.0 );
  glVertex2f( b->puck_radius, 0.0 );
  angle = increment;
  for( j = 0; j < N_POINTS_IN_CIRCLE-1; j++ )
    {
      glVertex2f( b->puck_radius*cos(angle), b->puck_radius*sin(angle) );
      angle += increment;
    }
  glVertex2f( b->puck_radius, 0.0 );
  glEnd();
  glPopAttrib();
  glEndList();
  /* end ball */

#ifdef LINUX
  gettimeofday( &tv_val_last, &tz_val );
#else
  last_time = GetCurrentTime();
#endif
}

/************************************************************************/
/* This needs to be fixed. */

void reshape( GLsizei w, GLsizei h )
{
  window_widthi = w;
  window_heighti = h;
  window_widthf = (GLfloat) w;
  window_heightf = (GLfloat) h;

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  /*
  if ( w <= h )
    gluOrtho2D( view[LEFT], view[RIGHT], 
		view[BOTTOM]*window_heightf/window_widthf,
		view[TOP]*window_heightf/window_widthf );
  else
    gluOrtho2D( view[LEFT]*window_widthf/window_heightf, 
		view[RIGHT]*window_widthf/window_heightf, 
		view[BOTTOM], view[TOP] );
  */
  gluOrtho2D( view[LEFT], view[RIGHT],
	      view[BOTTOM], view[TOP] );
  /* glMatrixMode( GL_MODELVIEW ); */
  glViewport( 0, 0, w, h );
}

/************************************************************************/

void idle()
{
  glutPostRedisplay();
}

/************************************************************************/
/************************************************************************/

void keypress( unsigned char key, int x, int y )
{
  if ( key == 'q' || key == 'Q' || key == '\27' )
    exit( 0 );
  mouse_display[XX] = x;
  mouse_display[YY] = y;
  simulate( the_board );
}

/************************************************************************/

void mouse( int button, int state, int x, int y )
{
  char b, d;
  if ( button == GLUT_LEFT_BUTTON )
    b = 'L';
  else if ( button == GLUT_RIGHT_BUTTON )
    b = 'R';
  else
    b = 'M';
  if ( state == GLUT_UP )
    d = 'U';
  else
    d = 'D';
  mouse_display[XX] = x;
  mouse_display[YY] = y;
  if ( simulation_mode == PAUSED && state == GLUT_DOWN )
    simulation_mode = RUNNING;
  simulate( the_board );
}

/************************************************************************/

void mouse_motion( int x, int y )
{
  mouse_display[XX] = x;
  mouse_display[YY] = y;
  simulate( the_board );
}

/************************************************************************/

int init_glut( int argc, char **argv, float ratio )
{
  int wx, wy;

  if ( ratio < 1.0 )
    {
      wx = MAX_WINDOW_SIZE;
      wy = wx*ratio;
    }
  else
    {
      wy = MAX_WINDOW_SIZE;
      wx = wy/ratio;
    }
  /*
  printf( "wx: %d; wy: %d\n", wx, wy );
  */

  glutInit( &argc, argv );
  glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE );
  glutInitWindowSize( wx, wy );
  glutInitWindowPosition( 0, 0 );
  glutCreateWindow( "simple" );
  glutDisplayFunc( display );
  glutReshapeFunc( reshape );
  glutIdleFunc( idle );
  glutKeyboardFunc( keypress );
  glutMouseFunc( mouse );
  glutMotionFunc( mouse_motion );
  glutPassiveMotionFunc( mouse_motion );
  return TRUE;
}

/************************************************************************/

int main( int argc, char **argv )
{
  int wx, wy;
  BOARD *b;

  /* Set up a board */
  b = the_board = &a_board;
  initialize_maze( "board2", b );

  /* Should really call set_state() */
  b->puck[XX] = b->start[0];
  b->puck[YY] = b->start[1];

  init_glut( argc, argv,
	     ( b->board.p[TOP] - b->board.p[BOTTOM] )
	     /( b->board.p[RIGHT] - b->board.p[LEFT] ) );
  init_display( b );

  printf( "Click left on board to start game.\n" );

  glutMainLoop();
  return 0;
}

/************************************************************************/


