#pragma once

#include "scalar.h"

class Vector3D;
class Point3D;
class AffineMatrix;
class Matrix4x4;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vector4D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class Vector4D
{
 public:
	Scalar
		x,
		y,
		z,
		w;

	#if defined(USE_SIGNATURE)
		friend int
			Is_Signature_Bad(const volatile Vector4D *);
	#endif

	static const Vector4D
		Identity;

	//
	// Constructors
	//
	Vector4D()
		{}
	Vector4D(
		Scalar X,
		Scalar Y,
		Scalar Z,
		Scalar W)
			{x=X; y=Y; z=Z; w=W;}

	//
	// Assignment operators
	//
	Vector4D&
		operator=(const Vector4D &v);
	Vector4D&
		operator=(const Vector3D &v);
	Vector4D&
		operator=(const Point3D &p);

	//
	// Index operators
	//
	const Scalar&
		operator[](size_t index) const
			{Check_Pointer(this); Warn(index>W_Axis); return (&x)[index];}
	Scalar&
		operator[](size_t index)
			{Check_Pointer(this); Warn(index>W_Axis); return (&x)[index];}

	friend Logical
		Small_Enough(const Vector4D &v,Scalar e=SMALL);
	Logical
		operator!() const
			{return Small_Enough(*this);}

	//
	// "Close-enough" comparison operators
	//
	friend Logical
		Close_Enough(
			const Vector4D &v1,
			const Vector4D &v2,
			Scalar e=SMALL
		);
	Logical
		operator==(const Vector4D& v) const
			{return Close_Enough(*this,v);}
	Logical
		operator!=(const Vector4D& v) const
			{return !Close_Enough(*this,v);}

	//
	// The following operators all assume that this points to the destination
	// of the operation results
	//
	Vector4D&
		Negate(const Vector4D &v);

	Vector4D&
		Add(
			const Vector4D& v1,
			const Vector4D& v2
		);
	Vector4D&
		operator+=(const Vector4D& v)
			{return Add(*this,v);}

	Vector4D&
		Subtract(
			const Vector4D& v1,
			const Vector4D& v2
		);
	Vector4D&
		operator-=(const Vector4D& v)
			{return Subtract(*this,v);}

	Scalar
		operator*(const Vector4D& v) const
			{Check(this); return x*v.x + y*v.y + z*v.z + w*v.w;}

	Vector4D&
		Multiply(
			const Vector4D& v,
			Scalar scale
		);
	Vector4D&
		operator*=(Scalar v)
			{return Multiply(*this,v);}

	Vector4D&
		Multiply(
			const Vector4D& v1,
			const Vector4D& v2
		);
	Vector4D&
		operator*=(const Vector4D &v)
			{return Multiply(*this,v);}

	Vector4D&
		Divide(
			const Vector4D& v,
			Scalar scale
		);
	Vector4D&
		operator/=(Scalar v)
			{return Divide(*this,v);}

	Vector4D&
		Divide(
			const Vector4D& v1,
			const Vector4D& v2
		);
	Vector4D&
		operator/=(const Vector4D &v)
			{return Divide(*this,v);}

	//
	//  Transforms
	//
	Vector4D&
		Multiply(
			const Vector4D &v,
			const AffineMatrix &m
		);
	Vector4D&
		operator*=(const AffineMatrix &M)
			{Vector4D src(*this); return Multiply(src,M);}
	Vector4D& Multiply(
		const Vector4D &v,
		const Matrix4x4 &m
	);
	Vector4D&
		operator*=(const Matrix4x4 &m)
			{Vector4D src(*this); return Multiply(src,m);}
	Vector4D& Multiply(
		const Vector3D &v,
		const Matrix4x4 &m
	);
	Vector4D& Multiply(
		const Point3D &p,
		const Matrix4x4 &m
	);

	//
	// Support functions
	//
	Scalar
		LengthSquared() const
			{return operator*(*this);}
	Scalar
		Length() const
			{return Sqrt(LengthSquared());}

	Vector4D&
		Combine(
			const Vector4D& v1,
			Scalar t1,
			const Vector4D& v2,
			Scalar t2
		);

	Vector4D&
		Lerp(
			const Vector4D& v1,
			const Vector4D& v2,
			Scalar t
		)
			{return Combine(v1,1.0f-t,v2,t);}

	friend std::ostream& operator<<(std::ostream& stream, const Vector4D& v);
	Logical TestInstance() const;
	static Logical TestClass();
};
