#pragma once

#include "scalar.h"

class Degree;
class SinCosPair;

class Radian
{
public:
	Scalar angle;

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

	Radian()				{}
	Radian(Scalar angle)	{ this->angle = angle; }

	Radian& operator=(Scalar angle)
	{
		Check_Pointer(this);
		this->angle = angle;
		return *this;
	}
	Radian& operator=(const Radian &radian)
	{
		Check_Pointer(this);
		Check(&radian);
		angle = radian.angle;
		return *this;
	}
	Radian& operator=(const Degree &degree);
	Radian& operator=(const SinCosPair &pair);

	operator Scalar() const
	{
		Check(this);
		return angle;
	}

	//
	// These comparator functions are not designed to make exact comparisons
	// of Scalaring point numbers, but rather to compare them to within some
	// specified error threshold
	//
	Logical operator!() const					{ return Small_Enough(angle); }
	Logical operator==(const Radian &r) const	{ return Close_Enough(angle, r.angle); }
	Logical operator==(float r) const			{ return Close_Enough(angle, r); }
	Logical operator!=(const Radian &r) const	{ return !Close_Enough(angle, r.angle); }
	Logical operator!=(float r) const			{ return !Close_Enough(angle, r); }

	Radian& Negate(Scalar r)
	{
		Check_Pointer(this);
		angle = -r;
		return *this;
	}

	Radian& Add(Scalar r1, Scalar r2)
	{
		Check_Pointer(this);
		angle = r1 + r2;
		return *this;
	}
	Radian& operator+=(Scalar r)
	{
		Check(this);
		angle += r;
		return *this;
	}

	Radian& Subtract(Scalar r1, Scalar r2)
	{
		Check_Pointer(this);
		angle = r1 - r2;
		return *this;
	}
	Radian& operator-=(Scalar r)
	{
		Check(this);
		angle -= r;
		return *this;
	}

	Radian& Multiply(Scalar r1, Scalar r2)
	{
		Check_Pointer(this);
		angle = r1 * r2;
		return *this;
	}
	Radian& operator*=(Scalar r)
	{
		Check(this);
		angle *= r;
		return *this;
	}

	Radian& Divide(Scalar r1, Scalar r2)
	{
		Check_Pointer(this);
		Verify(!Small_Enough(r2));
		angle = r1 / r2;
		return *this;
	}
	Radian& operator/=(Scalar r)
	{
		Check(this);
		Verify(!Small_Enough(r));
		angle /= r;
		return *this;
	}

	Radian& Lerp(const Radian &a, const Radian &b, Scalar t);

	static Scalar Normalize(Scalar value);
	Radian& Normalize();

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

class Degree
{
public:
	Scalar angle;

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

	//
	// constructors
	//
	Degree()				{}
	Degree(Scalar angle)	{ this->angle = angle; }

	//
	// Assignment operators
	//
	Degree& operator=(const Degree &degree)
	{
		Check(this);
		Check(&degree);
		angle = degree.angle;
		return *this;
	}
	Degree& operator=(Scalar angle)
	{
		Check(this);
		this->angle = angle;
		return *this;
	}
	Degree& operator=(const Radian &radian)
	{
		Check(this);
		Check(&radian);
		angle = radian.angle * DEG_PER_RAD;
		return *this;
	}

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

inline Radian& Radian::operator=(const Degree& degree)
{
	Check_Pointer(this);
	Check(&degree);
	angle = degree.angle * RAD_PER_DEG;
	return *this;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SinCosPair ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class SinCosPair
{
 public:
	Scalar sine, cosine;

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

	//
	// Constructors
	//
	SinCosPair()	{}
	SinCosPair(Scalar sin, Scalar cos)
	{
		Check_Pointer(this);
		sine = sin;
		cosine = cos;
		Check(this);
	}

	//
	// Assignment operators
	//
	SinCosPair& operator=(const SinCosPair &pair)
	{
		Check_Pointer(this);
		Check(&pair);
		sine = pair.sine; cosine = pair.cosine;
		return *this;
	}
	SinCosPair& operator=(const Radian &radian);

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

inline Radian& Radian::operator=(const SinCosPair& pair)
{
	Check_Pointer(this);
	Check(&pair);
	angle = Arctan(pair.sine, pair.cosine);
	return *this;
}
