// a simple trackball implementatioon

#include <iostream>
#ifdef _WIN32
#include <windows.h>
#endif
#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#ifndef M_PI
#define M_PI            3.14159265358979323846
#endif 

#include "cvec3t.h"
#include "cvec4t.h"
#include "hmatrix.h"


typedef CVec3T<float> Vec3f;
typedef CVec4T<float> Vec4f;


namespace WindowParams {
  static int WindowWidth = 800;
  static int WindowHeight = 600;
  static int MainWindow; 
};


Vec3f CameraPosition(5,5,5);
Vec3f SphereCenter(0,0,0);
float SphereRadius = 2;
float ExaminerRotAngle = 0; 
Vec3f ExaminerRotAxis(0,1,0); 
HMatrix<float> ExaminerRotation;

void Reshape(int width, int height) {

  WindowParams::WindowWidth = width;
  WindowParams::WindowHeight = height;

  glViewport(0,0,width,height);

  glMatrixMode(GL_PROJECTION); 
  glLoadIdentity();
  gluPerspective(40, width/float(height), 1, 10);

}






void Draw() {

  glMatrixMode(GL_MODELVIEW); 
  glLoadIdentity();
  gluLookAt(CameraPosition.x(),CameraPosition.y(),CameraPosition.z(),0,0,0,0,1,0);


  glClearColor( 0.6, 0.6,0.6,0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

  glEnable(GL_DEPTH_TEST);
  // enable automatic rescaling of normals to unit length
  glEnable(GL_NORMALIZE);
  // enable two lights
  glEnable(GL_LIGHT0);
  //  glEnable(GL_LIGHT1);
  // directional lights (w=0) along z axis
  glLightfv(GL_LIGHT0,GL_DIFFUSE, Vec4f(1,  1, 1,1));
  glLightfv(GL_LIGHT0,GL_POSITION, Vec4f(0,  0, 1,0));
  glLightfv(GL_LIGHT1,GL_DIFFUSE,Vec4f(1,  1, 1,1));
  glLightfv(GL_LIGHT1,GL_POSITION, Vec4f(0,  0, -1,0));

  glPushMatrix();

  glMultMatrixf(ExaminerRotation);
  glutWireSphere(SphereRadius,10,10);
  glEnable(GL_LIGHTING);
  glutSolidTeapot(1.0);
  glDisable(GL_LIGHTING);
  glPopMatrix();
  glutSwapBuffers();
}


Vec3f ScreenToWorld(int windowid, int x, int y) { 
    glutSetWindow(windowid);
	GLdouble modelview[16];
	GLdouble projection[16];
	GLint viewport[4];
    double world_x, world_y, world_z; 
	// get current modelview, projection and viewport transforms
    glGetDoublev(GL_MODELVIEW_MATRIX,modelview);
    glGetDoublev(GL_PROJECTION_MATRIX,projection);
    glGetIntegerv(GL_VIEWPORT,viewport);
	// this function computes inverse of VPM and applies it to (x,y,0) to convert from pixel to world coords
	// this computes the world coordinates of the point on the near plane of the frustum which corresponds to pixel (x,y)
    gluUnProject(x,y,0,modelview,projection,viewport, &world_x,&world_y,&world_z);
    return Vec3f(world_x,world_y,world_z);
}



bool SpherePoint(const Vec3f& center, float r, const Vec3f& pscreen, Vec3f& psphere) { 
    Vec3f v = (pscreen- CameraPosition).dir(); 
	Vec3f d = CameraPosition-center;
	float ddotv = d.dot(v);
	float D = ddotv*ddotv-d.dot() +r*r;
	if (D < 0) return false;
	float t = -ddotv-sqrt(D);
    psphere = CameraPosition+v*t;
	return true;
}


// a mouse button is pressed or released
Vec3f CurrentPsphere;
Vec3f NewPsphere;

void MouseClick (int button, int state, int x, int y) {
	y = WindowParams::WindowHeight - y-1;
  if(state == GLUT_DOWN) {
    Vec3f psphere; 
	if(SpherePoint(SphereCenter,SphereRadius,ScreenToWorld(WindowParams::MainWindow,x,y),psphere)) {
		CurrentPsphere = psphere;
		NewPsphere = psphere;
	}
	glutPostRedisplay();
  } 
  if(state == GLUT_UP) { 
	CurrentPsphere = NewPsphere;
  }
}


void MouseMotion(int x, int y) { 
  	y = WindowParams::WindowHeight - y-1;
	Vec3f psphere;
	if(SpherePoint(SphereCenter,SphereRadius,ScreenToWorld(WindowParams::MainWindow,x,y),psphere)) {
	  ExaminerRotAxis = cross(CurrentPsphere-SphereCenter, psphere-SphereCenter);
	  ExaminerRotAngle = acos((CurrentPsphere-SphereCenter).dot(psphere-SphereCenter)/SphereRadius/SphereRadius);
	  ExaminerRotation = HMatrix<float>::Rotation(ExaminerRotAngle,ExaminerRotAxis)*ExaminerRotation;
	  CurrentPsphere = psphere;
	}
	glutPostRedisplay();
}




int main(int argc, char* argv[]) {

  // initialize glut and parse command-line aguments that glut understands
  glutInit(&argc, argv);

  // initialize dislay mode: 4 color components, double buffer and depth buffer
 
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);

  glutInitWindowSize(WindowParams::WindowWidth,WindowParams::WindowHeight);

  WindowParams::MainWindow = glutCreateWindow("Trackball");
  glutDisplayFunc(Draw); 

  glutReshapeFunc(Reshape);

 // gets called whenever a mouse button is pressed or released
  glutMouseFunc(MouseClick);

  glutMotionFunc(MouseMotion);
 

  // this is an infinite loop get event - dispatch event which never returns
  glutMainLoop();
  return 0;
}











