// __author__ = Juan Carlos De Abreu (jcabreur@gmail.com)

#include "Camera.h"
#include "../Includes/OpenGL.h"

BEGIN_TEG_NAMESPACE();

//funcion para inicializar las variables de la camara
void Camera::init() {
	//se inicializan las variables
	pos.set(0.0f, 0.0f, 0.0f);
	view.set(0.0f, 0.0f, 0.0f);
	up.set(0.0f, 0.0f, 0.0f);
	strafe.set(0.0f, 0.0f, 0.0f);
	
	frameInterval = 0.0f;

	// prueba ..
	set(Vec3f(0,0,2), Vec3f(0, 0, 0), Vec3f(0, 1, 0));
}

//funcion para ajustar los valores de la camara
void Camera::set(const Vec3f &_pos, const Vec3f &_view, const Vec3f &_up) {
	//se ajustan los valoresd de la camara con los valores pasados a la funcion
	pos = _pos; view = _view; up = _up;
}

//funcion para mover la camara
void Camera::move(const float &direction) {
	//se obtiene la direccion en la cual esta mirando la camara
	Vec3f lookDirection = view - pos;

	//se noraliza la direccion
	lookDirection.normalize();

	//se llama a updateCamera para mover la camara en la direccion que queremos
	update(lookDirection, direction);
}

//funcion para actualizar los valores de la camara
void Camera::update(Vec3f lookDirection, float dir) {
	//se utiliza el movimiento basado en el tiempo cuando actualizamos la camara
	dir = dir * frameInterval;
	
	//se actualiza la posicion de la scamara
	pos += lookDirection * dir;

	//se actualiza el punto al que mira la camara
	view += lookDirection * dir;

	// Descomentar si no se quiere afectar la altura
	// Aunque mas bien tendria que hacerse sin afectar el valor almacenado
	//para que no afecte la altura 
	//pos.y = 0;
	//view.y = 0;	//nota: creo que esto esta malo por que deberia de manter el valor anterior
}

//funcion para mover la camara perpendicular a la vista
void Camera::StrafeCam(float direction) {
	//se calcula el strafe y luego se actualiza la camara
	CalculateStrafe();
	update(strafe, direction);
}

//funcion para calcular el strafe de la camara
void Camera::CalculateStrafe() {
	// Se obtiene y se normaliza la direccion
	Vec3f dir = view - pos;
	dir.normalize();

	//se calcula el producto cruz entre la direccion en la cual miramos y el vector Up
	//se almacenan los valores del strafe
	strafe = cross(dir, up);
}

//funcion para rotar la camara (con el teclado)
void Camera::RotateCamera(float AngleDir, Vec3f speed) {
	//se crea el quaternion de rotacion basado en los ejes en los que queremos rotar
	math::Quaternion<float> qRotation = rotate(AngleDir, speed);

	//se crea el quaternion con la direccion en la cual esta mirando la camara
	math::Quaternion<float> qView(view.x - pos.x, view.y - pos.y, view.z - pos.z, 0);

	//se multiplica el quaternion de rotacion por el el quaternion de la vista
	//luego se multiplica por la conjugada del quaternion de rotacion
	math::Quaternion<float> qNewView = ((qRotation * qView) * qRotation.conjugate());

	// Update the view information by adding the position to the resulting quaternion.
	//se actualiza la informacion de la vista sumando la psosicion con el vector resultante 
	view.x = pos.x + qNewView.x;
	view.y = pos.y + qNewView.y;
	view.z = pos.z + qNewView.z;
}

void Camera::rotateX(float angleDir) {
  RotateCamera(angleDir, Vec3f(0, 1, 0));
  //	RotateCamera(angleDir, up);
}

void Camera::rotateY(float angleDir) {
  //se obtienen el vector perpendicualr a la vista con la vista y con el vecor Up 
  //se obtiene la direccion la vista
  Vec3f dir = view - pos;

  //se calcula el producto cruz entre la vista y el vetor Up.
  //se normaliza el vector
  Vec3f axis = cross(dir, up);
  axis.normalize();

  //se rota la camara
  RotateCamera(angleDir, axis);

  // Prueba para actualizar el vector up.
/*  dir = view - pos;
  up = cross(axis, dir);
  up.normalize();
*/
  //RotateCamera(yDirection, Vec3f(0, 1, 0));
}

//funcion para rotar la camara (con el mouse)
void Camera::RotateByMouse(int mousePosX, int mousePosY, int midX, int midY) {
	float yDirection = 0.0f;	//angulo de direccion.
	float yRotation = 0.0f;		//angulo de rotacion

	//si el mouse se encuentra en el centro de la pantalla no se puede rotar la camara
	if ((mousePosX == midX) && (mousePosY == midY))
		return;

	//se obtiene la direccion de cada eje 
	yDirection = (float)((midX - mousePosX)) / 20.0f;
	yRotation = (float)((midY - mousePosY)) / 20.0f;

	// We use curentRotX to help use keep the camera from rotating too far in either direction.
/*	currentRotationAngle -= yRotation;

	//nota: -->aquoi debe ir el max pith
	//se para la camara para que no de la vuelta por arriba
	if (currentRotationAngle > 60.0f) {
		currentRotationAngle = 60.0f;
		return;
	}

	//se para la camara para que no de la vuelta por abajo
	if (currentRotationAngle < -60.0f) {
		currentRotationAngle = -60.0f;
		return;
	}
*/
	//se obtienen el vector perpendicualr a la vista con la vista y con el vecor Up 
	//se obtiene la direccion la vista
	Vec3f dir = view - pos;

	//se calcula el producto cruz entre la vista y el vetor Up.
	//se normaliza el vector
	Vec3f axis = cross(dir, up);
	axis.normalize();

	//se rota la camara
	RotateCamera(yRotation, axis);
	RotateCamera(yDirection, Vec3f(0, 1, 0));
}

//funcion para calcular el tiempo entre los dos ultimos frame`s
void Camera::CalculateTime() {
	//frameInterval = (TimeManagement::GetInstance().current - TimeManagement::GetInstance().last) * 0.001;
	const float clocksPerSec = 1.0f / CLOCKS_PER_SEC;
	frameInterval = (TimeManagement::GetInstance().current - TimeManagement::GetInstance().last) * clocksPerSec;
	//frameInterval = 0.005;
}

// Method to set the camera properties in OpenGL.
void Camera::lookAt(void) const {
  gluLookAt(pos.x, pos.y, pos.z,
      view.x, view.y, view.z,
      up.x, up.y, up.z);
}

END_TEG_NAMESPACE();
