/**************************************************
 *   File:     InsideSphere.cpp
 *   Author:   Dr. Dalton R. Hunkins     
 *   Date:     January 2009
 *
 *  User Interaction
 *    Although the standard popup
 *    menu to show code and to exit
 *    are provided in this example,
 *    its code is not included here.
 *
 *    Mouse Motion
 *    Drag Mouse with Left Button Down
 *    orbits the camera (same as in 
 *    the Camera Animation tutorial)
**************************************************/

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <cmath>

#include <string>
using std::string;

#include <GL/Utils/ObjModel.h>
#include <GL/utils/ImageMap.h>
#include <GL/Utils/MathFunctions.h>

// Global Variable Declarations
double currentEyePoint[3];
double movingEyePoint[3];

double lookAtPoint[3];
double upVector[3];

double radius;

double currentTheta;  // angle of Longitude
double currentPhi;    // angle of Latitude

bool dragging;

int startX;
int startY;

double percentChangeX;
double percentChangeY;

int deviceWindowWidth;
int deviceWindowHeight;
   
ObjModel *sphere;
ObjModel *plane;
   
ImageMap *skyMap;

 // Setting Paths to Resources
string parentDirectory = "OpenGLTutorials";

string imageMapPath;
string objModelPath;
string sphereFilename = "sphere.obj";
string planeFilename  = "f-16.obj";
string skyFilename    = "sky.bmp";

void setPaths(string pathName) {
     if (pathName[0] == '/') {
         pathName = pathName.substr(10, pathName.length());
         imageMapPath = pathName.substr(0,1) + ":/";
         objModelPath = pathName.substr(0,1) + ":/";
	     pathName = pathName.substr(2, pathName.length());
     }   
     int position = pathName.find(parentDirectory, 0);
     pathName = pathName.substr(0, position+parentDirectory.length());
     imageMapPath += pathName + "/Resources/ImageMaps/";
     objModelPath += pathName + "/Resources/Wavefront/";  
}

  void setLighting() {
      float spotLightPosition[]        = {0.0,  2.0,  2.0, 1.0};
      float spotLightDirection[]       = {0.0, -1.0, -1.0, 1.0};
      float spotLightColor[]           = {0.4,  0.4,  0.4, 1.0};      

      glLightfv(GL_LIGHT1, GL_DIFFUSE,        spotLightColor);
      glLightfv(GL_LIGHT1, GL_POSITION,       spotLightPosition);
      glLightfv(GL_LIGHT1, GL_SPOT_DIRECTION, spotLightDirection);
      glLightf(GL_LIGHT1,  GL_SPOT_CUTOFF,    20.0);

      glEnable(GL_LIGHT1);
  }
  
  void init() {
     glClearColor(0.0, 0.0, 0.0, 1.0);
     
     // Set Viewing Values and Projection Transform
     movingEyePoint[0]  = 0.0; movingEyePoint[1]  = 1.0; movingEyePoint[2]  = 2.0;
     lookAtPoint[0] = 0.0; lookAtPoint[1] = 0.0; lookAtPoint[2] = 0.0;
     upVector[0] = 0.0; upVector[1] = 1.0; upVector[2] = 0.0;
     
     radius = sqrt(0.0*0.0 + 1.0*1.0 + 2.0*2.0);

     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     gluPerspective(60.0, 1.0, 0.1, 100.0);
     
     dragging = false;
     
     // Set Texture Mapping
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
     skyMap->setTexture();
	 
     setLighting();

     glEnable(GL_DEPTH_TEST);
  }
  
  void reshape(int width, int height) {
       deviceWindowWidth  = width;
       deviceWindowHeight = height;
       glViewport(0, 0, width, height);
  }       
  
  void display() {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     
     // Set Viewing Transform
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
     gluLookAt(movingEyePoint.getX(), movingEyePoint.getY(),  movingEyePoint.getZ(),
               lookAtPoint.getX(),    lookAtPoint.getY(),     lookAtPoint.getZ(),
               upVector.getX(),       upVector.getY(),        upVector.getZ());
     
     // Draw the Models
     glEnable(GL_LIGHTING);
     glPushMatrix();
     glScaled(0.5, 0.5, 0.5);
     plane->drawModel();
     glPopMatrix();
     glDisable(GL_LIGHTING);

     glEnable(GL_TEXTURE_2D);
     glMatrixMode(GL_MODELVIEW);
     glPushMatrix();
     glScaled(4.0, 4.0, 4.0);
     glRotated(180.0, 0.0, 1.0, 0.0);
     sphere->drawModel();    
     glPopMatrix();
     glDisable(GL_TEXTURE_2D);
          
     glutSwapBuffers();
  }

  void computeAngles() {
	   double eyeVector[3];
	   double projectionVector[] = {currentEyePoint[0], 0.0, currentEyePoint[2]};
	   double normalizedVector[3];
	   
	   // compute angle of longitude
       normalize(projectionVector, projectionVector);
       if (projectionVector[0] > 0.0)
          currentTheta = acos(projectionVector[2]);
       else
          currentTheta = -acos(projectionVector[2]);
          
       // compute angle of latitude   
       normalize(currentEyePoint, eyeVector);
       currentPhi = acos(dotProduct(eyeVector, projectionVector));
  }    

  void update(float percentChangeTheta, float percentChangePhi) {
       // add to current angle of longitude a percentage of 2PI
       // and add to current angle of latitude a percentage of PI
       double theta = currentTheta + percentChangeTheta * PI * 2.0;
       double phi   = currentPhi   + percentChangePhi * PI;
       if (phi > (PI / 2.0))
		  phi = PI / 2.0;
       if (phi < (-PI / 2.0))
	      phi = -PI / 2.0;
       
       movingEyePoint[0] = radius * sin(theta) * cos(phi);
       movingEyePoint[1] = radius * sin(phi);
       movingEyePoint[2] = radius * cos(theta) * cos(phi);
  }
  void mouse(int button, int state, int x, int y) {
     if (button == GLUT_LEFT_BUTTON)
        if (state == GLUT_DOWN) {
           startX = x;
           startY = y;
           dragging = true;
           currentEyePoint[0] = movingEyePoint[0];
           currentEyePoint[1] = movingEyePoint[1];
           currentEyePoint[2] = movingEyePoint[2];
	     computeAngles();        }
        else if (button == GLUT_UP){
             dragging = false;
        }
  }
  
  void mouseMotion(int x, int y) {
     if (dragging) {
  	    percentChangeX = ((double) (x - startX)) / 
  	 		                     deviceWindowWidth;
        percentChangeY = ((double) (y - startY)) / 
  	                       (double) deviceWindowHeight; 	 		                        

        update(percentChangeX, percentChangeY);
        glutPostRedisplay();
     }    
  } 	 	   

int main (int argc, char** argv) {
    // Opening Resources
    string pathName = argv[0];
    setPaths(pathName);
    skyMap = new ImageMap(imageMapPath, skyFilename);
    plane  = new ObjModel(objModelPath, planeFilename);  	
    sphere = new ObjModel(objModelPath, sphereFilename);
    
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
        
    glutInitWindowPosition(0, 0);
    glutInitWindowSize(500, 500);
    int cgWorldWindowHandle = glutCreateWindow("Inside Sphere");
 
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(mouseMotion);

    init();
                         
    glutMainLoop();
    return 0;
}