
#include "Camera.h"
#include <cmath>
#include <iostream>
#include <string>
#include <cassert>
#include <GL/glut.h>

const int PI = 3.14159;


using namespace std;

const double Camera::MAX_D = 100.0;
const double Camera::MIN_D = 0.1;
const double Camera::DEF_STEP = 1.5;

Camera::Camera(double theta, double phi, double d)
: theta_(theta),
  phi_(phi),
  d_(d),
  pTrack_(NULL), // not tracking anything (yet)
  axes_(false), // disable axes by default
  currX_(-1), currY_(-1),
  lookAt_(0,0,0) // start by looking at origin
{
	// Nothing (more) to do
}

Camera::~Camera(void)
{
	// Nothing to do
}

void Camera::init()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0, 1.0, 1.0, 3000.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void Camera::display()
{
	Tuple pt;
	if (pTrack_)
		pt = *pTrack_;
	else
		pt = lookAt_;

	// compute eye tuple
	Tuple eye(
		-d_ * cos(theta_) * sin(phi_),
		2*d_ * sin(theta_),
		d_ * cos(theta_) * cos(phi_));
	eye += pt;

	gluLookAt(eye.x(), eye.y(), eye.z(), 
			  pt.x(), pt.y(), pt.z(), 
			  0.0, 1.0, 0.0);

	// draw axes if applicable
	if (axes_)
	{
		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_LINES);
			glVertex3f(0.0f, 0.0f, 0.0f);
			glVertex3f(100.0f, 0.0f, 0.0f);
		glEnd();
		glBegin(GL_LINES);
			glVertex3f(0.0f, 0.0f, 0.0f);
			glVertex3f(0.0f, 100.0f, 0.0f);
		glEnd();
		glBegin(GL_LINES);
			glVertex3f(0.0f, 0.0f, 0.0f);
			glVertex3f(0.0f, 0.0f, 100.0f);
		glEnd();
	}
}

void Camera::track(const Tuple *pTrack)
{
	// In case we're moving to not tracking anything, save our position
	if (pTrack_)
		lookAt_ = *pTrack_;

	pTrack_ = pTrack;
}

void Camera::rotate(int x, int y, int width, int height)
{

	// wait until a mouse position is recorded and
	// avoid really big jumps
	if (currX_>0 && abs(x-currX_) < width/20 && abs(y-currY_) < height/20) 
	{
		phi_ += (double) (x-currX_) / (double) width * PI;
		theta_ += (double) (y-currY_) / (double) height * PI;

		// limit theta to -16pi/33 and 16pi/33
		// it is disorienting to lose "up"
		if (theta_ < -16*PI/33.0)
			theta_ = -16*PI/33.0;
		if (theta_ > 16*PI/33.0)
			theta_ = 16*PI/33.0;
	}

	currX_ = x;
	currY_ = y;
	//debug(); // *****
}

void Camera::moveForward(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ -= move.norm() * dist;
}

void Camera::moveBackward(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ += move.norm() * dist;
}

void Camera::moveLeft(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ += move.cross(Tuple(0.0, 1.0, 0.0)).norm() * dist;
}

void Camera::moveRight(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ -= move.cross(Tuple(0.0, 1.0, 0.0)).norm() * dist;
}

void Camera::moveUp(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ += move.cross(Tuple(0.0, 1.0, 0.0)).cross(move).norm() * dist;
}

void Camera::moveDown(double dist)
{
	Tuple move(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_));
	lookAt_ -= move.cross(Tuple(0.0, 1.0, 0.0)).cross(move).norm() * dist;
}

void Camera::zoomIn (double dist) 
{
	if (d_ > MIN_D + dist) 
		d_ -= dist; 
	else d_ = MIN_D; 
}

void Camera::zoomOut (double dist) 
{
	if (d_ < MAX_D - dist) 
		d_ += dist; 
	else d_ = MAX_D; 
}

const Tuple Camera::direction() const
{
	return -Tuple(
		-cos(theta_) * sin(phi_),
		2 * sin(theta_),
		cos(theta_) * cos(phi_)).norm();
}

void Camera::debug() const
{
	cout << "Theta: " << theta_ << ", Phi: " << phi_ << ", d: " << d_;
}
