// This code is in the public domain -- castanyo@yahoo.es

#ifndef NV_MATH_VECTOR_H
#define NV_MATH_VECTOR_H

#include <nvmath/nvmath.h>
#include <nvcore/Containers.h> // min, max

namespace nv
{

enum zero_t { zero };
enum identity_t { identity };

// I should probably use templates.
typedef float scalar;

class NVMATH_CLASS Vector2
{
public:
	typedef Vector2 const & Arg;
	
	Vector2();
	explicit Vector2(zero_t);
	Vector2(scalar x, scalar y);
	Vector2(Vector2::Arg v);
	
	const Vector2 & operator=(Vector2::Arg v);
	
	scalar x() const;
	scalar y() const;
	
	void set(scalar x, scalar y);
	
	Vector2 operator-() const;
	void operator+=(Vector2::Arg v);
	void operator-=(Vector2::Arg v);
	void operator*=(scalar s);
	void operator*=(Vector2::Arg v);

	friend bool operator==(Vector2::Arg a, Vector2::Arg b);
	friend bool operator!=(Vector2::Arg a, Vector2::Arg b);
	
private:
	scalar m_x, m_y;
};


class NVMATH_CLASS Vector3
{
public:
	typedef Vector3 const & Arg;
	
	Vector3();
	explicit Vector3(zero_t);
	Vector3(scalar x, scalar y, scalar z);
	Vector3(Vector2::Arg v, scalar z);
	Vector3(Vector3::Arg v);
	
	const Vector3 & operator=(Vector3::Arg v);
	
	scalar x() const;
	scalar y() const;
	scalar z() const;

	const Vector2 & xy() const;
	
	// @@ temporary... should use an explicit method?
	const scalar * ptr() const;

	void set(scalar x, scalar y, scalar z);
	
	Vector3 operator-() const;
	void operator+=(Vector3::Arg v);
	void operator-=(Vector3::Arg v);
	void operator*=(scalar s);
	inline void operator/=(scalar s)
    { m_x /= s; m_y /= s; m_z /= s; }
	void operator*=(Vector3::Arg v);

	friend bool operator==(Vector3::Arg a, Vector3::Arg b);
	friend bool operator!=(Vector3::Arg a, Vector3::Arg b);
	
private:
	scalar m_x, m_y, m_z;
};


class NVMATH_CLASS Vector4
{
public:
	typedef Vector4 const & Arg;
	
	Vector4();
	explicit Vector4(zero_t);
	explicit Vector4(identity_t);
	Vector4(scalar x, scalar y, scalar z, scalar w);
	Vector4(Vector2::Arg v, scalar z, scalar w);
	Vector4(Vector3::Arg v, scalar w);
	Vector4(Vector4::Arg v);
//	Vector4(const Quaternion & v);
	
	const Vector4 & operator=(Vector4::Arg v);
	
	scalar x() const;
	scalar y() const;
	scalar z() const;
	scalar w() const;
	
	const Vector2 & xy() const;
	const Vector3 & xyz() const;
	
	void set(scalar x, scalar y, scalar z, scalar w);
	
	Vector4 operator-() const;
	void operator+=(Vector4::Arg v);
	void operator-=(Vector4::Arg v);
	void operator*=(scalar s);
	void operator*=(Vector4::Arg v);
	
	friend bool operator==(Vector4::Arg a, Vector4::Arg b);
	friend bool operator!=(Vector4::Arg a, Vector4::Arg b);
	
private:
	scalar m_x, m_y, m_z, m_w;
};


// Vector2

inline Vector2::Vector2() {}
inline Vector2::Vector2(zero_t) : m_x(0.0f), m_y(0.0f) {}
inline Vector2::Vector2(scalar x, scalar y) : m_x(x), m_y(y) {}
inline Vector2::Vector2(Vector2::Arg v) : m_x(v.x()), m_y(v.y()) {}

inline const Vector2 & Vector2::operator=(Vector2::Arg v)
{
	m_x = v.x();
	m_y = v.y();
	return *this;
}

inline scalar Vector2::x() const { return m_x; }
inline scalar Vector2::y() const { return m_y; }

inline void Vector2::set(scalar x, scalar y)
{
	m_x = x;
	m_y = y;
}

inline Vector2 Vector2::operator-() const
{
	return Vector2(-m_x, -m_y);
}

inline void Vector2::operator+=(Vector2::Arg v)
{
	m_x += v.m_x;
	m_y += v.m_y;
}

inline void Vector2::operator-=(Vector2::Arg v)
{
	m_x -= v.m_x;
	m_y -= v.m_y;
}

inline void Vector2::operator*=(scalar s)
{
	m_x *= s;
	m_y *= s;
}

inline void Vector2::operator*=(Vector2::Arg v)
{
	m_x *= v.m_x;
	m_y *= v.m_y;
}

inline bool operator==(Vector2::Arg a, Vector2::Arg b)
{
	return a.m_x == b.m_x && a.m_y == b.m_y; 
}
inline bool operator!=(Vector2::Arg a, Vector2::Arg b)
{
	return a.m_x != b.m_x || a.m_y != b.m_y; 
}


// Vector3

inline Vector3::Vector3() {}
inline Vector3::Vector3(zero_t) : m_x(0.0f), m_y(0.0f), m_z(0.0f) {}
inline Vector3::Vector3(scalar x, scalar y, scalar z) : m_x(x), m_y(y), m_z(z) {}
inline Vector3::Vector3(Vector2::Arg v, scalar z) : m_x(v.x()), m_y(v.y()), m_z(z) {}
inline Vector3::Vector3(Vector3::Arg v) : m_x(v.x()), m_y(v.y()), m_z(v.z()) {}

inline const Vector3 & Vector3::operator=(Vector3::Arg v)
{
	m_x = v.m_x;
	m_y = v.m_y;
	m_z = v.m_z;
	return *this;
}
	
inline scalar Vector3::x() const { return m_x; } 
inline scalar Vector3::y() const { return m_y; }
inline scalar Vector3::z() const { return m_z; }
	
inline const Vector2 & Vector3::xy() const
{
	return *(Vector2 *)this;
}
	
inline const scalar * Vector3::ptr() const
{
	return &m_x;
}

	
inline void Vector3::set(scalar x, scalar y, scalar z)
{
	m_x = x;
	m_y = y;
	m_z = z;
}

inline Vector3 Vector3::operator-() const
{
	return Vector3(-m_x, -m_y, -m_z);
}

inline void Vector3::operator+=(Vector3::Arg v)
{
	m_x += v.m_x;
	m_y += v.m_y;
	m_z += v.m_z;
}

inline void Vector3::operator-=(Vector3::Arg v)
{
	m_x -= v.m_x;
	m_y -= v.m_y;
	m_z -= v.m_z;
}

inline void Vector3::operator*=(scalar s)
{
	m_x *= s;
	m_y *= s;
	m_z *= s;
}

inline void Vector3::operator*=(Vector3::Arg v)
{
	m_x *= v.m_x;
	m_y *= v.m_y;
	m_z *= v.m_z;
}

inline bool operator==(Vector3::Arg a, Vector3::Arg b)
{
	return a.m_x == b.m_x && a.m_y == b.m_y && a.m_z == b.m_z; 
}
inline bool operator!=(Vector3::Arg a, Vector3::Arg b)
{
	return a.m_x != b.m_x || a.m_y != b.m_y || a.m_z != b.m_z; 
}


// Vector4

inline Vector4::Vector4() {}
inline Vector4::Vector4(zero_t) : m_x(0.0f), m_y(0.0f), m_z(0.0f), m_w(0.0f) {}
inline Vector4::Vector4(identity_t) : m_x(0.0f), m_y(0.0f), m_z(0.0f), m_w(1.0f) {}
inline Vector4::Vector4(scalar x, scalar y, scalar z, scalar w) : m_x(x), m_y(y), m_z(z), m_w(w) {}
inline Vector4::Vector4(Vector2::Arg v, scalar z, scalar w) : m_x(v.x()), m_y(v.y()), m_z(z), m_w(w) {}
inline Vector4::Vector4(Vector3::Arg v, scalar w) : m_x(v.x()), m_y(v.y()), m_z(v.z()), m_w(w) {}
inline Vector4::Vector4(Vector4::Arg v) : m_x(v.x()), m_y(v.y()), m_z(v.z()), m_w(v.w()) {}

inline const Vector4 & Vector4::operator=(const Vector4 & v)
{
	m_x = v.m_x;
	m_y = v.m_y;
	m_z = v.m_z;
	m_w = v.m_w;
	return *this;
}

inline scalar Vector4::x() const { return m_x; }
inline scalar Vector4::y() const { return m_y; }
inline scalar Vector4::z() const { return m_z; }
inline scalar Vector4::w() const { return m_w; }

inline const Vector2 & Vector4::xy() const
{
	return *(Vector2 *)this;
}

inline const Vector3 & Vector4::xyz() const
{
	return *(Vector3 *)this;
}

inline void Vector4::set(scalar x, scalar y, scalar z, scalar w)
{
	m_x = x;
	m_y = y;
	m_z = z;
	m_w = w;
}

inline Vector4 Vector4::operator-() const
{
	return Vector4(-m_x, -m_y, -m_z, -m_w);
}

inline void Vector4::operator+=(Vector4::Arg v)
{
	m_x += v.m_x;
	m_y += v.m_y;
	m_z += v.m_z;
	m_w += v.m_w;
}

inline void Vector4::operator-=(Vector4::Arg v)
{
	m_x -= v.m_x;
	m_y -= v.m_y;
	m_z -= v.m_z;
	m_w -= v.m_w;
}

inline void Vector4::operator*=(scalar s)
{
	m_x *= s;
	m_y *= s;
	m_z *= s;
	m_w *= s;
}

inline void Vector4::operator*=(Vector4::Arg v)
{
	m_x *= v.m_x;
	m_y *= v.m_y;
	m_z *= v.m_z;
	m_w *= v.m_w;
}

inline bool operator==(Vector4::Arg a, Vector4::Arg b)
{
	return a.m_x == b.m_x && a.m_y == b.m_y && a.m_z == b.m_z && a.m_w == b.m_w; 
}
inline bool operator!=(Vector4::Arg a, Vector4::Arg b)
{
	return a.m_x != b.m_x || a.m_y != b.m_y || a.m_z != b.m_z || a.m_w != b.m_w; 
}



// Functions


// Vector2

inline Vector2 add(Vector2::Arg a, Vector2::Arg b)
{
	return Vector2(a.x() + b.x(), a.y() + b.y());
}
inline Vector2 operator+(Vector2::Arg a, Vector2::Arg b)
{
	return add(a, b);
}

inline Vector2 sub(Vector2::Arg a, Vector2::Arg b)
{
	return Vector2(a.x() - b.x(), a.y() - b.y());
}
inline Vector2 operator-(Vector2::Arg a, Vector2::Arg b)
{
	return sub(a, b);
}

inline Vector2 scale(Vector2::Arg v, scalar s)
{
	return Vector2(v.x() * s, v.y() * s);
}

inline Vector2 scale(Vector2::Arg v, Vector2::Arg s)
{
	return Vector2(v.x() * s.x(), v.y() * s.y());
}

inline Vector2 operator*(Vector2::Arg v, scalar s)
{
	return scale(v, s);
}

inline Vector2 operator*(scalar s, Vector2::Arg v)
{
	return scale(v, s);
}

inline scalar dot(Vector2::Arg a, Vector2::Arg b)
{
	return a.x() * b.x() + a.y() * b.y();
}

inline scalar length_squared(Vector2::Arg v)
{
	return v.x() * v.x() + v.y() * v.y();
}

inline scalar length(Vector2::Arg v)
{
	return sqrtf(length_squared(v));
}

inline Vector2 min(Vector2::Arg a, Vector2::Arg b)
{
	return Vector2(min(a.x(), b.x()), min(a.y(), b.y()));
}

inline Vector2 max(Vector2::Arg a, Vector2::Arg b)
{
	return Vector2(max(a.x(), b.x()), max(a.y(), b.y()));
}

inline bool isValid(Vector2::Arg v)
{
	return isFinite(v.x()) && isFinite(v.y());
}


// Vector3

inline Vector3 add(Vector3::Arg a, Vector3::Arg b)
{
	return Vector3(a.x() + b.x(), a.y() + b.y(), a.z() + b.z());
}
inline Vector3 operator+(Vector3::Arg a, Vector3::Arg b)
{
	return add(a, b);
}

inline Vector3 sub(Vector3::Arg a, Vector3::Arg b)
{
	return Vector3(a.x() - b.x(), a.y() - b.y(), a.z() - b.z());
}
inline Vector3 operator-(Vector3::Arg a, Vector3::Arg b)
{
	return sub(a, b);
}

inline Vector3 cross(Vector3::Arg a, Vector3::Arg b)
{
	return Vector3(a.y() * b.z() - a.z() * b.y(), a.z() * b.x() - a.x() * b.z(), a.x() * b.y() - a.y() * b.x());
}

inline Vector3 scale(Vector3::Arg v, scalar s)
{
	return Vector3(v.x() * s, v.y() * s, v.z() * s);
}

inline Vector3 scale(Vector3::Arg v, Vector3::Arg s)
{
	return Vector3(v.x() * s.x(), v.y() * s.y(), v.z() * s.z());
}

inline Vector3 operator*(Vector3::Arg v, scalar s)
{
	return scale(v, s);
}

inline Vector3 operator*(scalar s, Vector3::Arg v)
{
	return scale(v, s);
}

inline Vector3 operator/(Vector3::Arg v, scalar s)
{
	return scale(v, 1.0f/s);
}

inline Vector3 add_scaled(Vector3::Arg a, Vector3::Arg b, scalar s)
{
	return Vector3(a.x() + b.x() * s, a.y() + b.y() * s, a.z() + b.z() * s);
}

inline Vector3 lerp(Vector3::Arg v1, Vector3::Arg v2, scalar t)
{
	const scalar s = 1.0f - t;
	return Vector3(v1.x() * s + t * v2.x(), v1.y() * s + t * v2.y(), v1.z() * s + t * v2.z());
}

inline scalar dot(Vector3::Arg a, Vector3::Arg b)
{
	return a.x() * b.x() + a.y() * b.y() + a.z() * b.z();
}

inline scalar length_squared(Vector3::Arg v)
{
	return v.x() * v.x() + v.y() * v.y() + v.z() * v.z();
}

inline scalar length(Vector3::Arg v)
{
	return sqrtf(length_squared(v));
}

inline bool isNormalized(Vector3::Arg v, float epsilon = NV_NORMAL_EPSILON)
{
	return equal(length(v), 1, epsilon);
}

inline Vector3 normalize(Vector3::Arg v, float epsilon = NV_EPSILON)
{
	float l = length(v);
	nvDebugCheck(!isZero(l, epsilon));
	Vector3 n = scale(v, 1.0f / l);
	nvDebugCheck(isNormalized(n));
	return n;
}

inline Vector3 normalizeSafe(Vector3::Arg v, Vector3::Arg fallback, float epsilon = NV_EPSILON)
{
	float l = length(v);
	if (isZero(l, epsilon)) {
		return fallback;
	}
	return scale(v, 1.0f / l);
}

inline Vector3 min(Vector3::Arg a, Vector3::Arg b)
{
	return Vector3(min(a.x(), b.x()), min(a.y(), b.y()), min(a.z(), b.z()));
}

inline Vector3 max(Vector3::Arg a, Vector3::Arg b)
{
	return Vector3(max(a.x(), b.x()), max(a.y(), b.y()), max(a.z(), b.z()));
}

inline bool isValid(Vector3::Arg v)
{
	return isFinite(v.x()) && isFinite(v.y()) && isFinite(v.z());
}

/*
Vector3 transform(Quaternion, vector3);
Vector3 transform_point(matrix34, vector3);
Vector3 transform_vector(matrix34, vector3);
Vector3 transform_point(matrix44, vector3);
Vector3 transform_vector(matrix44, vector3);
*/

// Vector4

inline Vector4 add(Vector4::Arg a, Vector4::Arg b)
{
	return Vector4(a.x() + b.x(), a.y() + b.y(), a.z() + b.z(), a.w() + b.w());
}
inline Vector4 operator+(Vector4::Arg a, Vector4::Arg b)
{
	return add(a, b);
}

inline Vector4 sub(Vector4::Arg a, Vector4::Arg b)
{
	return Vector4(a.x() - b.x(), a.y() - b.y(), a.z() - b.z(), a.w() - b.w());
}
inline Vector4 operator-(Vector4::Arg a, Vector4::Arg b)
{
	return sub(a, b);
}

inline Vector4 scale(Vector4::Arg v, scalar s)
{
	return Vector4(v.x() * s, v.y() * s, v.z() * s, v.w() * s);
}

inline Vector4 scale(Vector4::Arg v, Vector4::Arg s)
{
	return Vector4(v.x() * s.x(), v.y() * s.y(), v.z() * s.z(), v.w() * s.w());
}

inline Vector4 operator*(Vector4::Arg v, scalar s)
{
	return scale(v, s);
}

inline Vector4 operator*(scalar s, Vector4::Arg v)
{
	return scale(v, s);
}

inline Vector4 operator/(Vector4::Arg v, scalar s)
{
	return scale(v, 1.0f/s);
}

inline Vector4 add_scaled(Vector4::Arg a, Vector4::Arg b, scalar s)
{
	return Vector4(a.x() + b.x() * s, a.y() + b.y() * s, a.z() + b.z() * s, a.w() + b.w() * s);
}

inline scalar dot(Vector4::Arg a, Vector4::Arg b)
{
	return a.x() * b.x() + a.y() * b.y() + a.z() * b.z() + a.w() * b.w();
}

inline scalar length_squared(Vector4::Arg v)
{
	return v.x() * v.x() + v.y() * v.y() + v.z() * v.z() + v.w() * v.w();
}

inline scalar length(Vector4::Arg v)
{
	return sqrtf(length_squared(v));
}

inline bool isNormalized(Vector4::Arg v, float epsilon = NV_NORMAL_EPSILON)
{
	return equal(length(v), 1, epsilon);
}

inline Vector4 normalize(Vector4::Arg v, float epsilon = NV_EPSILON)
{
	float l = length(v);
	nvDebugCheck(!isZero(l, epsilon));
	Vector4 n = scale(v, 1.0f / l);
	nvDebugCheck(isNormalized(n));
	return n;
}

inline Vector4 normalizeSafe(Vector4::Arg v, Vector4::Arg fallback, float epsilon = NV_EPSILON)
{
	float l = length(v);
	if (isZero(l, epsilon)) {
		return fallback;
	}
	return scale(v, 1.0f / l);
}

inline Vector4 min(Vector4::Arg a, Vector4::Arg b)
{
	return Vector4(min(a.x(), b.x()), min(a.y(), b.y()), min(a.z(), b.z()), min(a.w(), b.w()));
}

inline Vector4 max(Vector4::Arg a, Vector4::Arg b)
{
	return Vector4(max(a.x(), b.x()), max(a.y(), b.y()), max(a.z(), b.z()), max(a.w(), b.w()));
}

inline bool isValid(Vector4::Arg v)
{
	return isFinite(v.x()) && isFinite(v.y()) && isFinite(v.z()) && isFinite(v.w());
}



/*
vector4 transform(matrix34, vector4);
vector4 transform(matrix44, vector4);
*/

/*
Quaternion mul(Quaternion, Quaternion);   // rotational composition
Quaternion conjugate(Quaternion);
Quaternion inverse(Quaternion);
Quaternion axis_angle(const Vector3 & v, scalar s);
*/

/*
matrix34 add(matrix34, matrix34);            // note: implicit '1' stays as '1'
matrix34 operator+(matrix34, matrix34);
matrix34 sub(matrix34, matrix34);            // note: implicit '1' stays as '1'
matrix34 operator-(matrix34, matrix34);
matrix34 mul(matrix34, matrix34);
matrix34 operator*(matrix34, matrix34);
matrix34 mul(matrix34, quaternion4);         //  rotation multiplication
matrix34 operator*(matrix34, quaternion4);   //  rotation multiplication
matrix34 translation(vector3);
matrix34 rotation(quaternion4);
matrix34 rotation(vector3, scalar);          //  axis/angle

matrix44 add(matrix44, matrix44);
matrix44 operator+(matrix44, matrix44);
matrix44 sub(matrix44, matrix44);
matrix44 operator-(matrix44, matrix44);
matrix44 mul(matrix44, matrix44);
matrix44 operator*(matrix44, matrix44);
matrix44 mul(matrix44, quaternion4);         //  rotation multiplication
matrix44 operator*(matrix44, quaternion4);   //  rotation multiplication
matrix44 invert(matrix34);
matrix44 invert(matrix44);
matrix44 transpose(matrix34);
matrix44 transpose(matrix44);
*/

} // nv namespace

#endif // NV_MATH_VECTOR_H
