#include "bird_renderer.h"
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <math.h>
#define FALSE 0
#define TRUE 1

const float piover180 = 0.0174532925;

/* special effects variables */
int aniOn = 0;
int fillOn = 1;
double move_speed = INITIAL_MOVE_SPEED;
double rotate_speed = INITIAL_ROTATE_SPEED;

/* camera variables */
vec3  Position;
vec3  ViewDir;
int ViewDirChanged = FALSE;
double RotatedX = 0.0, RotatedY = 0.0, RotatedZ = 0.0;
int mouseCenterX = INITIAL_W / 2;
int mouseCenterY = INITIAL_H / 2;
int lastMouseX = -1;
int lastMouseY = -1;
double mouseXMultiplier = 360.0 / INITIAL_W;
double mouseYMultiplier = 360.0 / INITIAL_H;


/* get the current camera heading */
void GetViewDir( void ) {

  vec3 Step1;
  double cosX;

  //Rotate around Y-axis:
  Step1.x = cos( (RotatedY + 90.0) * piover180);
  Step1.z = -sin( (RotatedY + 90.0) * piover180);
  //Rotate around X-axis:
  cosX = cos (RotatedX * piover180);
  ViewDir.x = Step1.x * cosX;
  ViewDir.z = Step1.z * cosX;
  ViewDir.y = sin(RotatedX * piover180);
}

/* move in the specified direction */
void Move (vec3 Direction) {

  AddVectorToVector(&Position, &Direction );
}

/* rotate on the Y axis */
void RotateY (double Angle) {

  RotatedY = ClampAngle (RotatedY + Angle);
  ViewDirChanged = TRUE;
}

/* rotate on the X axis */
void RotateX (double Angle) {

  RotatedX = ClampAngle (RotatedX + Angle);
  ViewDirChanged = TRUE;
}

/* call the OpenGL functions to position our anti-camera */
void RenderCamera( void ) {

  glRotatef(-RotatedX , 1.0, 0.0, 0.0);
  glRotatef(-RotatedY , 0.0, 1.0, 0.0);
  glRotatef(-RotatedZ , 0.0, 0.0, 1.0);
  glTranslatef( -Position.x, -Position.y, -Position.z );

}

/* move on our current axis */
void MoveForwards( double Distance ) {

  vec3 MoveVector;

  if (ViewDirChanged) GetViewDir();
  MoveVector.x = ViewDir.x * -Distance;
  MoveVector.y = ViewDir.y * -Distance;
  MoveVector.z = ViewDir.z * -Distance;
  AddVectorToVector(&Position, &MoveVector );

}

/* move orthogonal to axis */
void StrafeRight ( double Distance ) {

  vec3 MoveVector;

  if (ViewDirChanged) GetViewDir();
  MoveVector.z = -ViewDir.x * -Distance;
  MoveVector.y = 0.0;
  MoveVector.x = ViewDir.z * -Distance;
  AddVectorToVector(&Position, &MoveVector );

}

/*

 Our main display function.  This will clear the color and depth
 buffers.  After that it will translate the world to our eye position
 and rotate it by the rotation amounts.  Finally the object is drawn
 and the buffers are swapped.

*/
void glutDisplay (void)
{

  GLuint i;
  vec3 my_vector;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity();
  RenderCamera();
  /*glTranslatef(0.0, 0.8, 0.0);
  glScalef(.3, .1, .3);*/

  for (i=0; i < *num_objects; i++) {

    /* save our camera position */
    glPushMatrix();

    /* adjust to this bird's position */
    bird_get_position(shapes[i], &my_vector);
    glTranslated (my_vector.x, my_vector.y, my_vector.z);
  
    /* rotate to this bird's orientation */
    bird_get_heading(shapes[i], &my_vector);
    glRotated(my_vector.x, 1.0, 0.0, 0.0);
    glRotated(my_vector.y, 0.0, 1.0, 0.0);
    glRotated(my_vector.z, 0.0, 0.0, 1.0); 

    /* render the bird */
    glCallList(glObject);

    /* go back to camera's default matrix */
    glPopMatrix();
  }

  glFlush();
  glutSwapBuffers();
}

/* 
  The idle function that animates our object.  
  Advances the rotation variables.  
*/
void glutIdle (void)
{       

  double scale = (2.0 / *num_objects);
  GLuint i;

  for (i=0; i < *num_objects; i++) {

    shapes[i]->heading.x += (0.7 * scale * i);
    shapes[i]->heading.y += (0.5 * scale * i);
    shapes[i]->heading.z += (0.3 * scale * i);
    ClampVector (shapes[i]->heading); 
      
  }
  glutDisplay();

}

/* 
 * glutResize
 *
 * Called when the application starts by default and when the window
 * is resized.  When called the winW and winH are updated.  Then the
 * depth and color buffers are cleared and a viewing projection is
 * established.  The viewing projection needs to be set only when the
 * width and height of the window change.
 * 
 */

void glutResize (int x, int y)
{       
  if (y == 0 || x == 0) return;  //Nothing is visible then, so return
  
  //Set a new projection matrix
  glMatrixMode(GL_PROJECTION);  
  glLoadIdentity();
  //Angle of view:90 degrees
  //Near clipping plane distance: 0.5
  //Far clipping plane distance: 20.0
  gluPerspective(90, winW / winH, 1, 9999);
  
  glMatrixMode(GL_MODELVIEW);
  glViewport(0,0,x,y);  //Use the whole window for rendering

}

/*
 * glutMouse
 *
 * Handles mouse input.
 */

void glutMouse (int x, int y) {

  int deltay, deltax;
  int warp = 0;

  if ((lastMouseX < 0) || (lastMouseY < 0)) {
    lastMouseX = x;
    lastMouseY = y;
  }
  else {
    deltay = y - lastMouseY;
    deltax = x - lastMouseX;
    RotatedY = ClampAngle(deltax * mouseXMultiplier);
    RotatedX = ClampAngle(deltay * mouseYMultiplier);
    ViewDirChanged = TRUE;
    glutDisplay();
    /* implement a crude wrap-around for the mouse */
    if (x >= (INITIAL_W - 1)) {
      lastMouseX = x = 1;
      warp = 1;
    }
    else 
      if (x <= 0) {
	lastMouseX = x = (INITIAL_W - 10);
	warp = 1;
      }
    if (y >= (INITIAL_H - 1)) {
      lastMouseY = y = 1;
      warp = 1;
    }
    else 
      if (y <= 0) {
	lastMouseY = y = (INITIAL_H - 10);
	warp = 1;
      }
    if (warp)
      glutWarpPointer(x, y);

  }
  return;
}

/* 
=============
glutKeyboard
=============

Handles keyboard input.  
*/
void glutKeyboard (unsigned char key, int x, int y)
{

  /* is a number? */
  if ((key >= '1') && (key <= '9')) {
    key -= '0';
    move_speed = key * INITIAL_MOVE_SPEED;
    rotate_speed = INITIAL_ROTATE_SPEED + ((key-1/3.0) * INITIAL_ROTATE_SPEED);
  }

  /* it's not a number */
  else {
    switch (key)
      {
      case ' ':
	RotatedX = RotatedY = RotatedZ = 0.0;
	break;
      case 'r':
	aniOn = !aniOn;
	if (aniOn)
	  glutIdleFunc (glutIdle);
	else
	  glutIdleFunc (NULL);
	break;
	
      case 'f':
      case 'F':
	fillOn = !fillOn;
	
	if (fillOn)
	  glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
	else glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
	break;
	
      case 'h':
      case 'H':
	printf ("%s\n", APP_NAME);
	printf ("w -- Move Forward\n");
	printf ("s -- Move Backward\n");
	printf ("a -- Step Left\n");
	printf ("d -- Step Right\n");
	printf ("k -- Look Up\n");
	printf ("i -- Look Down\n");
	printf ("j -- look left\n");
	printf ("l -- look right\n");
	printf ("r -- On/Off Animation\n");
	printf ("f -- On/Off Wireframe\n");
	printf ("Space will reset to origin\n");
	printf ("q -- Quit\n");
	break;
	
      case 'q':
      case 27:  // ESC
	printf ("Bye!\n");
	exit (0);
	break;
	
      case 'k': // tilt up
	RotateX(rotate_speed);
	glutDisplay();
	break;
    
      case 'i': // tilt down
	RotateX(-rotate_speed);
	glutDisplay();
	break;
	
      case 'j': // tilt left
	RotateY(rotate_speed);
	glutDisplay();
	break;

      case 'l': // tilt right
	RotateY(-rotate_speed);
	glutDisplay();
	break;

      case 'w': // go forward
	MoveForwards(-move_speed) ;
	glutDisplay();
	break;
	
      case 's': // go back
	MoveForwards(move_speed) ;
	glutDisplay();
	break;
	
      case 'a': // side step left
	StrafeRight(-move_speed);
	glutDisplay();
	break;
	
      case 'd': // side step right
	StrafeRight(move_speed);
	glutDisplay();
	break;
	
      default:
	printf ("Special key %d pressed. No action there yet.\n", key);
	break;
      }
  } /* else */

  glutPostRedisplay ();
}

/* 
=============
glInit
=============

Sets up depth test and tells opengl to fill our polys.  
*/
void glInit (void)
{
  /* setup variables */
  Position.x = Position.y = 0.0;
  Position.z = 20.0;
  ViewDir.x = ViewDir.y = 0.0;
  ViewDir.z = -1.0;

  glEnable (GL_DEPTH_TEST);
  glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
  glDepthFunc(GL_LESS);                       // type of depth test to do.
  glShadeModel(GL_SMOOTH);			// Enables Smooth Color Shading
  glMatrixMode(GL_PROJECTION);
}



