//
// This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Richard Campbell '99)
//
// If you've found this code useful, please let me know.
//
// Visit me at www.demonews.com/hosted/nehe 
// (email Richard Campbell at ulmont@bellsouth.net)
//
#include <GL/glut.h>    // Header File For The GLUT Library 
#include <GL/gl.h>	// Header File For The OpenGL32 Library
#include <GL/glu.h>	// Header File For The GLu32 Library
#include <unistd.h>     // Header File For sleeping.
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>


/* ASCII code for the escape key. */
#define ESCAPE 27
#define UPDATE_THRESHOLD	4
#define SLEEP_INTERVAL		100000	/* microseconds */
#define SYSFS_POSITION_FILE     "/sys/devices/platform/hdaps/position"
#define BUF_LEN                 32
#define WIDTH			1024
#define HEIGHT		768
#define GRAVITY		10


/* The number of our GLUT window */
int window; 
static int val_x = 0;
static int val_y = 0;
static int rest_x;
static int rest_y;
static double vel_x = 0.0;
static double vel_y = 0.0;
static double pos_x = 0.0;
static double pos_y = 2.0;


static int read_position (int *x, int *y)
{
	char buf[BUF_LEN];
	int fd, ret;

	fd = open (SYSFS_POSITION_FILE, O_RDONLY);
	if (fd < 0) {
		perror ("open");
		return fd;
	}	

	ret = read (fd, buf, BUF_LEN);
	if (ret < 0) {
		perror ("read");
		goto out;
	} else if (ret == 0) {
		fprintf (stderr, "error: unexpectedly read zero!\n");
		ret = 1;
		goto out;
	}
	ret = 0;

	if (sscanf (buf, "(%d,%d)\n", x, y) != 2)
		ret = 1;

out:
	if (close (fd))
		perror ("close");

	return ret;
}

/* A general OpenGL initialization function.  Sets all of the initial parameters. */
void InitGL(int Width, int Height)	        /* We call this right after our OpenGL window is created. */
{
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);		/* This Will Clear The Background Color To Black */
  glClearDepth(1.0);				/* Enables Clearing Of The Depth Buffer */
  glDepthFunc(GL_LESS);				/* The Type Of Depth Test To Do */
  glEnable(GL_DEPTH_TEST);			/* Enables Depth Testing */
  glShadeModel(GL_SMOOTH);			/* Enables Smooth Color Shading */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();				// Reset The Projection Matrix

	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	gluPerspective (45.0f, (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);

	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();
	glTranslated (0.0, -0.5, -4.0);
}

/* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */
void ReSizeGLScene(int Width, int Height)
{
  if (Height==0)				/* Prevent A Divide By Zero If The Window Is Too Small */
    Height=1;

  glViewport(0, 0, Width, Height);		/* Reset The Current Viewport And Perspective Transformation */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f);
  glMatrixMode(GL_MODELVIEW);
}

/* The main drawing function. */
static void DrawGLScene()
{
  calc_position();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		/* Clear The Screen And The Depth Buffer */
  glPushMatrix ();
  glLoadIdentity ();
  glTranslatef(0.0f,0.0f,-6.0f);		/* Move Left 1.5 Units And Into The Screen 6.0 */
	
  glTranslatef(pos_x,pos_y,0.0f);		        /* Move to pos_x and pos_y */
	
  /* draw a square (quadrilateral) */
  glBegin(GL_QUADS);				// start drawing a polygon (4 sided)
  glVertex3f(-0.2f, 0.2f, 0.0f);		// Top Left
  glVertex3f( 0.2f, 0.2f, 0.0f);		// Top Right
  glVertex3f( 0.2f,-0.2f, 0.0f);		// Bottom Right
  glVertex3f(-0.2f,-0.2f, 0.0f);		// Bottom Left	
  glEnd();					// done with the polygon
  // swap buffers to display, since we're double buffered.
  glPopMatrix ();
  glutSwapBuffers();
}

/* The function called whenever a key is pressed. */
void keyPressed(unsigned char key, int x, int y) 
{
    /* avoid thrashing this procedure */
    usleep(100);

    /* If escape is pressed, kill everything. */
    if (key == ESCAPE) 
    { 
	/* shut down our window */
	glutDestroyWindow(window); 
	
	/* exit the program...normal termination. */
	exit(0);                   
    }
}

static void update_scene (void)
{
	int ret, x, y, do_update = 0;

	ret = read_position (&x, &y);
	if (ret)
		exit(EXIT_FAILURE);

	x -= rest_x;
	y -= rest_y;

	/* only update if we surpass our threshold, to minimize jitter ... */ 
	if (abs (x - val_x) > UPDATE_THRESHOLD) {
		val_x = x;
		do_update = 1;
	}
	if (abs (y - val_y) > UPDATE_THRESHOLD) {
		val_y = y;
		do_update = 1;
	}

	/* ... or, if we are within our threshold of zero, reset to zero */
	if (abs (x) < UPDATE_THRESHOLD) {
		val_x = 0;
		do_update = 1;
	}
	if (abs (y) < UPDATE_THRESHOLD) {
		val_y = 0;
		do_update = 1;
	}

	DrawGLScene();

	usleep (SLEEP_INTERVAL);
}

void calc_position(void)
{
  double g_x = GRAVITY * sin(val_x*0.01122);
  double g_y = -GRAVITY * cos(val_x*0.01122);
  double del_t = (double)SLEEP_INTERVAL / 1.0e6;
  
  vel_x += (g_x - vel_x)  * del_t;
  vel_y += (g_y - vel_y) * del_t;
  pos_x += vel_x * del_t;
  if (pos_x > 3.0)
  {
    pos_x = 3.0;
    vel_x = -vel_x;
  }
  else if (pos_x < -3.0)
  {
    pos_x = -3.0;
    vel_x = -vel_x;
    }
  pos_y += vel_y * del_t;
  if (pos_y > 2.0)
  {
    pos_y = 2.0;
    vel_y = -vel_y;
    }
  else if (pos_y < -2.0)
  {
    pos_y = -2.0;
    vel_y = -vel_y;
    }
  
}

int main(int argc, char **argv) 
{  
  int ret;
	ret = read_position (&rest_x, &rest_y);
	if (ret)
		return 1;


  /* Initialize GLUT state - glut will take any command line arguments that pertain to it or 
     X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */  
  glutInit(&argc, argv);  

  /* Select type of Display mode:   
     Double buffer 
     RGBA color
     Alpha components supported 
     Depth buffer */  
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH);  

  /* get a WIDTH x HEIGHT window */
  glutInitWindowSize(WIDTH, HEIGHT);  

  /* the window starts at the upper left corner of the screen */
  glutInitWindowPosition(0, 0);  

  /* Open a window */  
  window = glutCreateWindow("Thinkpad Accelerometer Test");  

  /* Register the function to do all our OpenGL drawing. */
  glutDisplayFunc(&update_scene);  

  /* Even if there are no events, redraw our gl scene. */
  glutIdleFunc(&update_scene);

  /* Register the function called when our window is resized. */
  glutReshapeFunc(&update_scene);

  /* Register the function called when the keyboard is pressed. */
  glutKeyboardFunc(&keyPressed);

  /* Initialize our window. */
  InitGL(WIDTH, HEIGHT);
  
 
  /* Start Event Processing Engine */  
  glutMainLoop();  

  return 1;
}
