/* Universidade Federal Fluminense
 * Programa de Pós-Graduação em Computação
 *
 * Tópicos Avançados em Computação Visual e Interfaces II (Técnicas Avançadas de Rendering)
 * Professor: Leandro Augusto Frata Fernandes
 *
 * Version: 2012.05.05-001
 */

#ifndef _BASIC_HEADER_
#define _BASIC_HEADER_

// Include the Standard Template Library (STL).
#ifdef _MSC_VER
	#include <windows.h>
#endif
 
#include <algorithm>
#include <cassert>

// Include the OpenGL Library (GL).
#include <GL/GL.h>

// Macros for defining vector-like structures and their functions.
#define _define_vector_binary_operator(prefix, size, op) \
	template<typename T> \
	inline _##prefix##size##<T> operator op (const _##prefix##size##<T>& v1, const _##prefix##size##<T>& v2) { \
		_##prefix##size##<T> result; \
		for (int i=0; i!=size; ++i) { \
			result[i] = v1[i] op v2[i]; \
		} \
		return result; \
	} \
	\
	template<typename T> \
	inline _##prefix##size##<T> operator op (const T& scalar, const _##prefix##size##<T>& v) { \
		_##prefix##size##<T> result; \
		for (int i=0; i!=size; ++i) { \
			result[i] = scalar op v[i]; \
		} \
		return result; \
	} \
	\
	template<typename T> \
	inline _##prefix##size##<T> operator op (const _##prefix##size##<T>& v, const T& scalar) { \
		_##prefix##size##<T> result; \
		for (int i=0; i!=size; ++i) { \
			result[i] = v[i] op scalar; \
		} \
		return result; \
	}

#define _define_vector_binary_self_operator(prefix, size, op) \
	template<typename T_> \
	inline _##prefix##size operator op (const _##prefix##size##<T_>& other_) { \
		for (int i=0; i!=size; ++i) { \
			(*this)[i] op other_[i]; \
		} \
		return *this; \
	} \
	\
	inline _##prefix##size operator op (const T& v_) { \
		for (int i=0; i!=size; ++i) { \
			(*this)[i] op v_; \
		} \
		return *this; \
	}

#define _define_vector_unary_operator(prefix, size, op) \
	template<typename T> \
	inline _##prefix##size##<T> operator op (const _##prefix##size##<T>& v) { \
		_##prefix##size##<T> result; \
		for (int i=0; i!=size; ++i) { \
			result[i] = op v[i]; \
		} \
		return result; \
	}

#define define_vector_stuff(prefix, c1, c2, c3, c4) \
	/* Template struct for 2-dimensional vectors. */ \
	template<typename T> \
	struct _##prefix##2 { \
		T c1, c2; \
		\
		_##prefix##2() { \
		} \
		\
		_##prefix##2(const T& v_) : \
			c1(v_), c2(v_) { \
		} \
		\
		_##prefix##2(const T& c1##_, const T& c2##_) : \
			c1(c1##_), c2(c2##_) { \
		} \
		\
		template<typename T_> \
		_##prefix##2(const _##prefix##2<T_>& other_) : \
			c1(other_.c1), c2(other_.c2) { \
		} \
		\
		inline T* data() { \
			return (T*)this; \
		} \
		\
		inline const T* data() const { \
			return (T*)this; \
		} \
		\
		inline void set(const T& c1##_, const T& c2##_) { \
			c1 = c1##_; \
			c2 = c2##_; \
		} \
		\
		inline T& operator [] (const int& index) { \
			assert((0 <= index) && (index < 2)); \
			return ((T*)this)[index]; \
		} \
		\
		inline const T& operator [] (const int& index) const { \
			assert((0 <= index) && (index < 2)); \
			return ((T*)this)[index]; \
		} \
		\
		_define_vector_binary_self_operator(prefix, 2, +=) \
		_define_vector_binary_self_operator(prefix, 2, -=) \
		_define_vector_binary_self_operator(prefix, 2, *=) \
		_define_vector_binary_self_operator(prefix, 2, /=) \
	}; \
	\
	/* Operators for 2-dimensional vector. */ \
	_define_vector_binary_operator(prefix, 2, +) \
	_define_vector_binary_operator(prefix, 2, -) \
	_define_vector_binary_operator(prefix, 2, *) \
	_define_vector_binary_operator(prefix, 2, /) \
	_define_vector_unary_operator(prefix, 2, +) \
	_define_vector_unary_operator(prefix, 2, -) \
	\
	/* Dot product. */ \
	template<typename T> \
	inline T dot(const _##prefix##2<T>& a, const _##prefix##2<T>& b) { \
		return (a.c1 * b.c1) + (a.c2 * b.c2); \
	} \
	\
	/* Length of vector. */ \
	template<typename T> \
	inline T length(const _##prefix##2<T>& v) { \
		return sqrt(dot(v, v)); \
	} \
	\
	/* Normalize vector to length 1. */ \
	template<typename T> \
	inline _##prefix##2<T> normalize(const _##prefix##2<T>& v) { \
		const T aux = 1 / length(v); \
		return _##prefix##2<T>(v.c1 * aux, v.c2 * aux); \
	} \
	\
	/* Template struct for 3-dimensional vectors. */ \
	template<typename T> \
	struct _##prefix##3 { \
		T c1, c2, c3; \
		\
		_##prefix##3() { \
		} \
		\
		_##prefix##3(const T& v_) : \
			c1(v_), c2(v_), c3(v_) { \
		} \
		\
		_##prefix##3(const T& c1##_, const T& c2##_, const T& c3##_) : \
			c1(c1##_), c2(c2##_), c3(c3##_) { \
		} \
		\
		template<typename T_> \
		_##prefix##3(const _##prefix##3<T_>& other_) : \
			c1(other_.c1), c2(other_.c2), c3(other_.c3) { \
		} \
		\
		inline T* data() { \
			return (T*)this; \
		} \
		\
		inline const T* data() const { \
			return (T*)this; \
		} \
		\
		inline void set(const T& c1##_, const T& c2##_, const T& c3##_) { \
			c1 = c1##_; \
			c2 = c2##_; \
			c3 = c3##_; \
		} \
		\
		inline T& operator [] (const int& index) { \
			assert((0 <= index) && (index < 3)); \
			return ((T*)this)[index]; \
		} \
		\
		inline const T& operator [] (const int& index) const { \
			assert((0 <= index) && (index < 3)); \
			return ((T*)this)[index]; \
		} \
		\
		_define_vector_binary_self_operator(prefix, 3, +=) \
		_define_vector_binary_self_operator(prefix, 3, -=) \
		_define_vector_binary_self_operator(prefix, 3, *=) \
		_define_vector_binary_self_operator(prefix, 3, /=) \
	}; \
	\
	/* Operators for 3-dimensional vector. */ \
	_define_vector_binary_operator(prefix, 3, +) \
	_define_vector_binary_operator(prefix, 3, -) \
	_define_vector_binary_operator(prefix, 3, *) \
	_define_vector_binary_operator(prefix, 3, /) \
	_define_vector_unary_operator(prefix, 3, +) \
	_define_vector_unary_operator(prefix, 3, -) \
	\
	/* Cross product. */ \
	template<typename T> \
	inline _##prefix##3<T> cross(const _##prefix##3<T>& a, const _##prefix##3<T>& b) { \
		return _##prefix##3<T>( \
			(a.c2 * b.c3) - (a.c3 * b.c2), \
			(a.c3 * b.c1) - (a.c1 * b.c3), \
			(a.c1 * b.c2) - (a.c2 * b.c1)); \
	} \
	\
	/* Dot product. */ \
	template<typename T> \
	inline T dot(const _##prefix##3<T>& a, const _##prefix##3<T>& b) { \
		return (a.c1 * b.c1) + (a.c2 * b.c2) + (a.c3 * b.c3); \
	} \
	\
	/* Length of vector. */ \
	template<typename T> \
	inline T length(const _##prefix##3<T>& v) { \
		return sqrt(dot(v, v)); \
	} \
	\
	/* Normalize vector to length 1. */ \
	template<typename T> \
	inline _##prefix##3<T> normalize(const _##prefix##3<T>& v) { \
		const T aux = 1 / length(v); \
		return _##prefix##3<T>(v.c1 * aux, v.c2 * aux, v.c3 * aux); \
	} \
	\
	/* Template struct for 4-dimensional vectors. */ \
	template<typename T> \
	struct _##prefix##4 { \
		T c1, c2, c3, c4; \
		\
		_##prefix##4() { \
		} \
		\
		_##prefix##4(const T& v_) : \
			c1(v_), c2(v_), c3(v_), c4(v_) { \
		} \
		\
		_##prefix##4(const T& c1##_, const T& c2##_, const T& c3##_, const T& c4##_) : \
			c1(c1##_), c2(c2##_), c3(c3##_), c4(c4##_) { \
		} \
		\
		template<typename T_> \
		_##prefix##4(const _##prefix##4<T_>& other_) : \
			c1(other_.c1), c2(other_.c2), c3(other_.c3), c4(other_.c4) { \
		} \
		\
		inline T* data() { \
			return (T*)this; \
		} \
		\
		inline const T* data() const { \
			return (T*)this; \
		} \
		\
		inline void set(const T& c1##_, const T& c2##_, const T& c3##_, const T& c4##_) { \
			c1 = c1##_; \
			c2 = c2##_; \
			c3 = c3##_; \
			c4 = c4##_; \
		} \
		\
		inline T& operator [] (const int& index) { \
			assert((0 <= index) && (index < 4)); \
			return ((T*)this)[index]; \
		} \
		\
		inline const T& operator [] (const int& index) const { \
			assert((0 <= index) && (index < 4)); \
			return ((T*)this)[index]; \
		} \
		\
		_define_vector_binary_self_operator(prefix, 4, +=) \
		_define_vector_binary_self_operator(prefix, 4, -=) \
		_define_vector_binary_self_operator(prefix, 4, *=) \
		_define_vector_binary_self_operator(prefix, 4, /=) \
	}; \
	\
	/* Operators for 2-dimensional vector. */ \
	_define_vector_binary_operator(prefix, 4, +) \
	_define_vector_binary_operator(prefix, 4, -) \
	_define_vector_binary_operator(prefix, 4, *) \
	_define_vector_binary_operator(prefix, 4, /) \
	_define_vector_unary_operator(prefix, 4, +) \
	_define_vector_unary_operator(prefix, 4, -) \
	\
	/* Dot product. */ \
	template<typename T> \
	inline T dot(const _##prefix##4<T>& a, const _##prefix##4<T>& b) { \
		return (a.c1 * b.c1) + (a.c2 * b.c2) + (a.c3 * b.c3) + (a.c4 * b.c4); \
	} \
	\
	/* Length of vector. */ \
	template<typename T> \
	inline T length(const _##prefix##4<T>& v) { \
		return sqrt(dot(v, v)); \
	} \
	\
	/* Normalize vector to length 1. */ \
	template<typename T> \
	inline _##prefix##4<T> normalize(const _##prefix##4<T>& v) { \
		const T aux = 1 / length(v); \
		return _##prefix##4<T>(v.c1 * aux, v.c2 * aux, v.c3 * aux, v.c4 * aux); \
	} \
	\
	/* Struct for 2-dimensional vector. */ \
	typedef _##prefix##2<GLboolean> prefix##2b; \
	typedef _##prefix##2<GLdouble>  prefix##2d; \
	typedef _##prefix##2<GLfloat>   prefix##2f; \
	typedef _##prefix##2<GLint>     prefix##2i; \
	typedef _##prefix##2<GLuint>    prefix##2u; \
	\
	/* Struct for 3-dimensional vector. */ \
	typedef _##prefix##3<GLboolean> prefix##3b; \
	typedef _##prefix##3<GLdouble>  prefix##3d; \
	typedef _##prefix##3<GLfloat>   prefix##3f; \
	typedef _##prefix##3<GLint>     prefix##3i; \
	typedef _##prefix##3<GLuint>    prefix##3u; \
	\
	/* Struct for 4-dimensional vector. */ \
	typedef _##prefix##4<GLboolean> prefix##4b; \
	typedef _##prefix##4<GLdouble>  prefix##4d; \
	typedef _##prefix##4<GLfloat>   prefix##4f; \
	typedef _##prefix##4<GLint>     prefix##4i; \
	typedef _##prefix##4<GLuint>    prefix##4u;

// Data types and functions for points and normals.
define_vector_stuff(coord, x, y, z, w)

// Data types and functions for colors.
define_vector_stuff(color, r, g, b, a)

// Data types and functions for texture coordinates.
define_vector_stuff(texcoord, s, t, p, q)

// Template struct for MxN matrices.
template<typename T, int NROWS, int NCOLS>
struct _matrix {
	_matrix() {
	}

	_matrix(const T& v_) {
		std::fill(&coeff[0], &coeff[NROWS * NCOLS], v_);
	}

	template<typename T_>
	_matrix(const _matrix<T_, NROWS, NCOLS>& other_) {
		std::copy(&(other_.coeff[0]), &(other_.coeff[NROWS * NCOLS]), &coeff[0]);
	}

	inline T* data() {
		return coeff;
	}

	inline const T* data() const {
		return coeff;
	}

	inline T& operator [] (const int& index) {
		assert((0 <= index) && (index < (NROWS * NCOLS)));
		return coeff[index];
	}

	inline const T& operator [] (const int& index) const {
		assert((0 <= index) && (index < (NROWS * NCOLS)));
		return coeff[index];
	}

	inline T& operator () (const int& row, const int& col) {
		assert((0 <= row) && (row < NROWS) && (0 <= col) && (col < NCOLS));
		return coeff[(col * NROWS) + row];
	}

	inline const T& operator () (const int& row, const int& col) const {
		assert((0 <= row) && (row < NROWS) && (0 <= col) && (col < NCOLS));
		return coeff[(col * NROWS) + row];
	}

private:

	T coeff[NROWS * NCOLS];
};

// Struct for 4x4 matrix.
typedef _matrix<GLboolean, 4, 4> matrix4x4b;
typedef _matrix<GLdouble, 4, 4>  matrix4x4d;
typedef _matrix<GLfloat, 4, 4>   matrix4x4f;
typedef _matrix<GLint, 4, 4>     matrix4x4i;
typedef _matrix<GLuint, 4, 4>    matrix4x4u;

// Inverse matrix.
template<typename T>
_matrix<T, 4, 4> inverse(const _matrix<T, 4, 4>& m) {
	_matrix<T, 4, 4> result;

	result[ 0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10];
	result[ 1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10];
	result[ 2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6];
	result[ 3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6];

	result[ 4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10];
	result[ 5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10];
	result[ 6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6];
	result[ 7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6];

	result[ 8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9];
	result[ 9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9];
	result[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
	result[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5];

	result[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
	result[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
	result[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
	result[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5];

	const T invDet = 1 / (m[0] * result[0] + m[1] * result[4] + m[2] * result[8] + m[3] * result[12]);

	for (int i=0; i<16; i++) {
		result[i] *= invDet;
	}

	return result;
}

// Matrix multiplication.
template<typename T, int NROWS, int NSIZE, int NCOLS>
_matrix<T, NROWS, NCOLS> multiply(const _matrix<T, NROWS, NSIZE>& a, const _matrix<T, NSIZE, NCOLS>& b) {
	_matrix<T, NROWS, NCOLS> result;

	for (int row=0; row<NROWS; ++row) {
		for (int col=0; col<NCOLS; ++col) {
			result(row, col) = 0;
			for (int i=0; i<NSIZE; ++i) {
				result(row, col) += a(row, i) * b(i, col);
			}
		}
	}

	return result;
}

// Identity matrix.
template<typename T>
_matrix<T, 4, 4> identity() {
	_matrix<T, 4, 4> result;

	result[ 0] = 1;
	result[ 1] = 0;
	result[ 2] = 0;
	result[ 3] = 0;

	result[ 4] = 0;
	result[ 5] = 1;
	result[ 6] = 0;
	result[ 7] = 0;

	result[ 8] = 0;
	result[ 9] = 0;
	result[10] = 1;
	result[11] = 0;

	result[12] = 0;
	result[13] = 0;
	result[14] = 0;
	result[15] = 1;

	return result;
}

// Rotation matrix.
template<typename T>
_matrix<T, 4, 4> rotation(const _coord3<T>& axis, const T& angleInRadians) {
	const T cosAngle = cos(angleInRadians);
	const T sinAngle = sin(angleInRadians);
	const _coord3<T>& unitAxis = normalize(axis);

	_matrix<T, 4, 4> result;

	result[ 0] = cosAngle + unitAxis.x * unitAxis.x * (1 - cosAngle);
	result[ 1] = unitAxis.y * unitAxis.x * (1 - cosAngle) + unitAxis.z * sinAngle;
	result[ 2] = unitAxis.z * unitAxis.x * (1 - cosAngle) - unitAxis.y * sinAngle;
	result[ 3] = 0;

	result[ 4] = unitAxis.x * unitAxis.y * (1 - cosAngle) - unitAxis.z * sinAngle;
	result[ 5] = cosAngle + unitAxis.y * unitAxis.y * (1 - cosAngle);
	result[ 6] = unitAxis.z * unitAxis.y * (1 - cosAngle) + unitAxis.x * sinAngle;
	result[ 7] = 0;

	result[ 8] = unitAxis.x * unitAxis.z * (1 - cosAngle) + unitAxis.y * sinAngle;
	result[ 9] = unitAxis.y * unitAxis.z * (1 - cosAngle) - unitAxis.x * sinAngle;
	result[10] = cosAngle + unitAxis.z * unitAxis.z * (1 - cosAngle);
	result[11] = 0;

	result[12] = 0;
	result[13] = 0;
	result[14] = 0;
	result[15] = 1;

	return result;
}

// Translation matrix.
template<typename T>
_matrix<T, 4, 4> scaling(const _coord3<T>& factor) {
	_matrix<T, 4, 4> result;

	result[ 0] = factor.x;
	result[ 1] = 0;
	result[ 2] = 0;
	result[ 3] = 0;

	result[ 4] = 0;
	result[ 5] = factor.y;
	result[ 6] = 0;
	result[ 7] = 0;

	result[ 8] = 0;
	result[ 9] = 0;
	result[10] = factor.z;
	result[11] = 0;

	result[12] = 0;
	result[13] = 0;
	result[14] = 0;
	result[15] = 1;

	return result;
}

// Translation matrix.
template<typename T>
_matrix<T, 4, 4> translation(const _coord3<T>& delta) {
	_matrix<T, 4, 4> result;

	result[ 0] = 1;
	result[ 1] = 0;
	result[ 2] = 0;
	result[ 3] = 0;

	result[ 4] = 0;
	result[ 5] = 1;
	result[ 6] = 0;
	result[ 7] = 0;

	result[ 8] = 0;
	result[ 9] = 0;
	result[10] = 1;
	result[11] = 0;

	result[12] = delta.x;
	result[13] = delta.y;
	result[14] = delta.z;
	result[15] = 1;

	return result;
}

// pi value.
template<typename T>
inline T pi() {
	return static_cast<T>(3.14159265358979323846264338327950288419716939937510);
}

// Degrees to radians.
template<typename T>
inline T degreesToRadians(const T& angle) {
	static const T aux = pi<T>() / 180;
	return angle * aux;
}

// Radians to degrees.
template<typename T>
inline T radiansToDegrees(const T& angle) {
	static const T aux = 180 / pi<T>();
	return angle * aux;
}

#endif // _BASIC_HEADER_
