#ifndef CAMERA_H
#define CAMERA_H

#include "./math/Mathematics.h"
using namespace ANALYTICAL_GEOMETRY;

/// Class to easier work with camera in 3d space.
/**
*	Trida pro praci s kamerou v prostoru. Trida je primarne urcena pro OpenGL protoze je napsana primo pro potreby funkce gluLookAt.
*/
template<typename T>	//T muze byt float,int,double NE nic jinyho!!!
class Camera
{
private:
	Vector_3<T> up;			/**< UP vektor kamery. Dopocitava se z look a right vektoru automaticky, nemenit rucne */

public:					
	Point_3<T> pos;		/**< pozice kamery v prostoru */
	Vector_3<T> look;		/**< smer pohledu kamery */ // musi byt VZDY NORMALIZOVANY!!!
	Vector_3<T> right;		/**< RIGHT vektor kamery, tento vektor urcuje kde je pravo z pohledu kamery */
	
	void recalculate_up(void); /**< metoda ktera prepocita up vector (up vector je kolmice na look a right vector) */
	Vector_3<T> get_up(); /**< metoda vraci hodnotu up vectoru */

	Camera(void);	
	Camera(const Point_3<T> &pos,const Vector_3<T> &look,const Vector_3<T> &right);	/**< Konstruktor v nemz se nainicializuji vsechny potrebne parametry kamery, vstupni parametry jsou pozice kamery, vektor pohledu kamery a right vektor kamery*/
	Camera(T pos_x, T pos_y, T pos_z,T look_x, T look_y, T look_z,T right_x,T right_y,T right_z); /**< Konstruktor v nemz se nainicializuji vsechny potrebne parametry kamery, vstupni parametry jsou pozice kamery (po slozkach), smer pohledu kamery (po slozkach) a right vektor kamery (po slozkach)*/
	void setClipingPlane(float _nearPlane, float _farPlane) { nearPlane = _nearPlane; farPlane = _farPlane; }
	void move_camera(T dis);	/**< Posune kameru ve smeru dir o dis, kde kladna hodnota dis je pohyb ve smeru pohledu (tj pohyb vpred) a zaporna hodnota je pohyb proti smeru pohledu (tj. pohyb vzad) */
	void strafe_camera(T dis);	/**< Posune kameru ve smeru right vektoru o dis, kde kladna hodnota dis je pohyb ve smeru right vektoru (tj ukrok doprava) a zaporna hodnota je pohyb proti smeru right vektoru (tj. ukrok do leva) */
	void set_position(const Point_3<T> &new_pos);	/**< Nastavuje pozici kamery */
	void set_look(const Vector_3<T> &new_look,const Vector_3<T> &new_right);	/**< Nastavuje look vector kamery */
	void increment_position(const Vector_3<T> &incr_pos);	/**< K pozici kamery se pricte vektor incr_pos */
	void turn_camera_y(T ang);	/**< Otaci kameru kolem kolem osy Y o uhel ang (ve stupnich) */
	void turn_camera_x(T ang);	/**< Otaci kameru kolem osy X o uhel ang (ve stupnich) */
	void turn_camera_axis(T ang, const Vector_3<T> &normalize_axis);		/**< Otaci kameru kolem zadane osy o uhel ang (ve stupnich) */

	void rotate_camera_axis(const Point_3<T>& pivot, const Vector_3<T>& axis,T ang);	/**< Rotate camera around given axis and pivot */
	void rotate_camera_axis(const Ray_3<T>& axis,T ang);			/**< Otoci kameru (pozici, right, look i up vektor) kolem osy prochazejici bodem pivot. */
};
template<typename T> void Camera<T>::rotate_camera_axis(const Ray_3<T>& axis,T ang)
{
	this->pos.rotate_point_arround_ray( axis,ang ); //rotace kolem RAY (= pevne dana osa)
	
	this->right.rotate_around_vector( axis.dir,ang ); //rotace RIGHT VEKTORU kolem dane osy (pro rotaci vektoru neni treba znat zadny PIVOT)
	this->right.normalize();

	this->look.rotate_around_vector( axis.dir,ang );
	this->look.normalize();

	this->recalculate_up();
}
template<typename T> void Camera<T>::rotate_camera_axis(const Point_3<T>& pivot, const Vector_3<T>& axis,T ang)
{
	Ray_3<T> ray;
	ray.dir = axis;
	ray.pos = pivot;

	this->rotate_camera_axis( ray, ang );
}
template<typename T>  Vector_3<T> Camera<T>::get_up()
{
	return this->up;
}
template<typename T> void Camera<T>::recalculate_up(void)
{
	this->up.calculate_cross_product(this->right,this->look);
	this->up.normalize();
}
template<typename T> void Camera<T>::move_camera(T dis)
{
	this->pos+=this->look*dis;	//pozici posunu ve smeru move vektoru ktery je normalizovan a tedy po vynasobeni DIS posun odpovida poctu jednotek
}
template<typename T> void Camera<T>::strafe_camera(T dis)
{
	this->pos+=this->right*dis;
}

template<typename T> void Camera<T>::set_position(const Point_3<T> &new_pos)
{
	this->pos=new_pos;
}
template<typename T> void Camera<T>::set_look(const Vector_3<T> &new_look,const Vector_3<T> &new_right)
{
	this->look=new_look;
	this->right=new_right;
	
	this->recalculate_up();
}

template<typename T> void Camera<T>::increment_position(const Vector_3<T> &incr_pos)
{
	this->pos+=incr_pos;
}

template<typename T> void Camera<T>::turn_camera_axis(T ang, const Vector_3<T> &normalize_axis)	//uhel ve stupnich
{
	this->look.rotate_around_vector(normalize_axis,ang); //orotuju look vector kolem dane osy
	this->right.rotate_around_vector(normalize_axis,ang); //orotuju right vector kolem dane osy
	this->look.normalize();	//normalizuju vektor (POZOR muze se ale vlastne rotaci denormalizovat????)
	this->right.normalize();	//normalizuju vektor (POZOR muze se ale vlastne rotaci denormalizovat????)

	this->recalculate_up();	//dopocitam right vektor
}
template<typename T> void Camera<T>::turn_camera_y(T ang)	//uhel ve stupnich
{
	/*
		x'	|cos(aplha) -sin(alpha)| *|x|
		y'	|sin(alpha)  cos(alpha)|  |y|
	*/
	//rotace probiha kolem osy Y (tzn kolem osy 0,1,0 tedy slozka Y look vektoru je pri rotaci ignorovana (3d vektor vlastne prevadime na 2d vektor [look vektor by se asi mel projektovat nejdrive do roviny a pak az ho otacet aby se uplne neignorovala slozka y!!!])
 	
	double alpha_Rad=(ang/180)*(DT_PI);		//prevod ze STUPNU na RADIANY
	T m[2][2];
	m[1][1]=m[0][0]=cos(alpha_Rad);
 	m[0][1]=sin(alpha_Rad);
 	m[1][0]=-m[0][1];
	
	T tmp_v[2];
	tmp_v[0]=this->look.x();
	tmp_v[1]=this->look.z();

	this->look.set_data(m[0][0]*tmp_v[0]+m[1][0]*tmp_v[1],this->look.y(),m[0][1]*tmp_v[0]+m[1][1]*tmp_v[1]);
	this->look.normalize();

	//-----------right vector
	tmp_v[0]=this->right.x();
	tmp_v[1]=this->right.z();

	this->right.set_data(m[0][0]*tmp_v[0]+m[1][0]*tmp_v[1],this->right.y(),m[0][1]*tmp_v[0]+m[1][1]*tmp_v[1]);
	this->right.normalize();


	this->recalculate_up();

}
template<typename T> void Camera<T>::turn_camera_x(T ang)	//uhel ve stupnich
{
	this->look.rotate_around_vector(this->right,ang);
	this->look.normalize();	//normalizace mozna neni potreba (netestovano bez normalizace)

	this->recalculate_up();
}

template<typename T> Camera<T>::Camera(void)
{
	//set default camera params
	this->pos.set_data( 0, 0, 0);
	this->look.set_data( 0, 0, -1);
	this->right.set_data( 1, 0, 0);
	
	this->recalculate_up();
}
template<typename T> Camera<T>::Camera(T pos_x, T pos_y, T pos_z,T look_x, T look_y, T look_z,T right_x,T right_y,T right_z)
{
	this->pos.set_data(pos_x,pos_y,pos_z);
	this->look.set_data(look_x,look_y,look_z);
	this->right.set_data(right_x,right_y,right_z);
	
	this->recalculate_up();
}

template<typename T> Camera<T>::Camera(const Point_3<T> &pos,const Vector_3<T> &look,const Vector_3<T> &right)
{
	this->pos=pos;
	this->look=look;
	this->right=right;
	
	this->recalculate_up();
}

#endif