#pragma once

#include <ostream>
#include "memstrm.h"
#include "scalar.h"

class AbstractVector3D;
class Vector3D;
class LBE3Vector3D;
class TIVector3D;
class AffineMatrix;
class LinearMatrix;
class Matrix4x4;
class Point3D;

enum Axes
{
	X_Axis,
	Y_Axis,
	Z_Axis,
	W_Axis
};

//~~~~~~~~~~~~~~~~~~~~~~~~~ AbstractVector3D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class AbstractVector3D
{
public:
	Scalar x, y , z;

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

	AbstractVector3D& operator = (const AbstractVector3D& v);

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

	friend Logical Close_Enough(const AbstractVector3D &v1, const AbstractVector3D &v2, Scalar e=SMALL);
	Logical operator == (const AbstractVector3D& v) const	{ return Close_Enough(*this, v, SMALL); }
	Logical	operator != (const AbstractVector3D& v) const	{ return !Close_Enough(*this,v,SMALL); }
	Logical TestInstance() const;

protected:
	AbstractVector3D() {}
	AbstractVector3D(Scalar x, Scalar y, Scalar z) : x(x), y(y), z(z) {}
};

inline MemoryStream& MemoryStream_Read(MemoryStream* stream, AbstractVector3D *output)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream& MemoryStream_Write(MemoryStream* stream, const AbstractVector3D *input)
{
	return stream->WriteBytes(input, sizeof(*input));
}

class LBE3Vector3D;
class TIVector3D;
class AffineMatrix;
class LinearMatrix;
class Matrix4x4;
class Point3D;

class Vector3D : public AbstractVector3D
{
public:
	static const Vector3D Identity;

	//
	// Constructors
	//
	Vector3D()	{}
	Vector3D(Scalar x, Scalar y, Scalar z) : AbstractVector3D(x,y,z) {}

	//
	// Assignment operators
	//
	Vector3D& operator = (const Vector3D &vector)
	{
		AbstractVector3D::operator=(vector);
		return *this;
	}

	Vector3D& operator = (const LBE3Vector3D &vector);
	Vector3D& operator = (const TIVector3D &vector);

	// Comparison operator (for sorting by length, GDU 1/26/96)
	Logical operator > (const Vector3D &v) const
	{
		return LengthSquared() > v.LengthSquared();
	}

	Logical operator < (const Vector3D &v) const
	{
		return LengthSquared() < v.LengthSquared();
	}

	//
	// Indexing operations
	//
	const Scalar& operator[](size_t index) const
	{
		Warn(index>Z_Axis);
		return (&x)[index];
	}
	
	Scalar&	operator[](size_t index)
	{
		Warn(index>Z_Axis);
		return (&x)[index];
	}

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

	Vector3D& Add(const Vector3D& v1, const Vector3D& v2);
	Vector3D& AddScaled(const Vector3D& v1, const Vector3D& v2, Scalar scale);
	Vector3D& operator+=(const Vector3D& v) { return Add(*this,v); }

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

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

	Vector3D& Cross(const Vector3D& v1, const Vector3D& v2);

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

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

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

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

	//
	// Transform operators
	//
	Vector3D& Multiply(const Vector3D &v, const AffineMatrix &m);
	Vector3D& operator *= (const AffineMatrix &m)
	{
		Vector3D src(*this);
		return Multiply(src, m);
	}
	Vector3D& MultiplyByInverse(const Vector3D &v, const LinearMatrix &m);

	//
	// Miscellaneous functions
	//
	Scalar LengthSquared() const
	{
		Check(this);
		return x * x + y * y + z * z;
	}
	Scalar Length() const
	{
		return sqrt(LengthSquared());
	}

	Vector3D& Normalize(const Vector3D &v);

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

	//
	// Template support
	//
	Vector3D& Lerp(const Vector3D& v1, const Vector3D& v2, Scalar t)
	{
		return Combine(v1, 1.0f - t, v2, t);
	}

	//
	// Support functions
	//
	friend std::ostream& operator << (std::ostream& stream, const Vector3D& v);
	static Logical TestClass();
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~ Vector3D functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void Convert_From_Ascii(const char *str, Vector3D *vector_3D);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ LBE3Vector3D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class LBE3Vector3D : public AbstractVector3D
{
public:
	//
	// Constructors
	//
	LBE3Vector3D() {}
	LBE3Vector3D(Scalar x, Scalar y, Scalar z) : AbstractVector3D(x, y, z) {}

	//
	// Assignment operators
	//
	LBE3Vector3D& operator = (const LBE3Vector3D &vector)
	{
		Check_Pointer(this);
		Check(&vector);
		AbstractVector3D::operator=(vector);
		return *this;
	}
	LBE3Vector3D& operator = (const Vector3D& vector);
	LBE3Vector3D& operator = (const TIVector3D &vector);

	//
	// Support functions
	//
	friend std::ostream& operator << (std::ostream &stream, const LBE3Vector3D &vector);
	static Logical TestClass();
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TIVector3D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TIVector3D : public AbstractVector3D
{
public:
	//
	// Constructors
	//
	TIVector3D() {}
	TIVector3D(Scalar x, Scalar y, Scalar z) : AbstractVector3D(x,y,z) {}

	//
	// Assignment operators
	//
	TIVector3D& operator = (const TIVector3D &vector)
	{
		AbstractVector3D::operator=(vector);
		return *this;
	}
	TIVector3D& operator = (const Vector3D &vector);
	TIVector3D& operator = (const LBE3Vector3D &vector);

	//
	// Support functions
	//
	friend std::ostream& operator << (std::ostream &stream, const TIVector3D &vector);
	static Logical TestClass();
};
