#ifndef EE_MATHVECTOR2_H
#define EE_MATHVECTOR2_H

#include <eepp/config.hpp>
#include <eepp/math/math.hpp>

namespace EE { namespace Math {

/** @brief Utility template class for manipulating 2-dimensional vectors */
template <typename T> class Vector2 {
  public:
	static const Vector2<T> Zero;

	static const Vector2<T> One;

	/** Default constructor creates Vector2(0,0) */
	Vector2();

	/** Creates a vector from its coordinates */
	Vector2( T X, T Y );

	Vector2( const Vector2<T>& copy ) : x( copy.x ), y( copy.y ) {}

	/** @return A copy of the Vector2 */
	Vector2<T> copy();

	/** @return The Dot product of the 2D vectors. */
	T dot( const Vector2<T>& V2 ) const;

	/** @return The Cross product of the 2D vectors.  */
	T cross( const Vector2<T>& V2 ) const;

	/** @return The perpendicular vector */
	Vector2<T> perp() const;

	/** @return The reveser perpendicular vector */
	Vector2<T> rPerp() const;

	/** Uses complex multiplication to rotate self by vec. Scaling will occur if self is not a unit
	 * vector. */
	Vector2<T> rotate( const Vector2<T>& V2 ) const;

	/** Inverse of Vector2::rotate */
	Vector2<T> unrotate( const Vector2<T>& V2 ) const;

	/** @return The vector Length */
	T length() const;

	/** @return The square of the length of the 2D vector. */
	T lengthSq() const;

	/** Normalize the vector */
	void normalize();

	/** @return Clamp the vector to a magnitude length. */
	void clamp( T len );

	/** @return The unit length vector for the given angle (radians) */
	Vector2<T> forAngle( const T& a ) const;

	/** @return The angular direction vector is pointing in (radians). */
	T toAngle();

	/** Rotates the vector */
	void rotate( const T& Angle );

	/** Rotates the vector against a defined rotation center */
	void rotate( const T& Angle, const Vector2<T>& RotationCenter );

	/** @return The distance between two vectors */
	T distance( const Vector2<T>& Vec ) const;

	/** @return The square of the distance between two vectors */
	T distanceSq( const Vector2<T>& Vec ) const;

	/** @return True if the distance between the two vectors is less than Dist */
	bool nearDist( const Vector2<T>& Vec, T Dist ) const;

	/** @return The spherical linear interpolation between two 2D vectors. */
	Vector2<T> sphericalLerp( const Vector2<T>& Vec, T Time ) const;

	/** Spherical linear interpolation between two vectors */
	Vector2<T> sphericalLerpConst( const Vector2<T>& Vec, T Angle ) const;

	/** Performs a linear interpolation between two 2D vectors. */
	Vector2<T> lerp( const Vector2<T>& Vec, T Time ) const;

	/**	@return A vector interpolated from self towards Vec with length Dist. */
	Vector2<T> lerpConst( const Vector2<T>& Vec, T Dist ) const;

	/** Scales the vector position against another vector */
	void scale( const Vector2<T>& scale, const Vector2<T>& Center );

	/** Scales the vector position against another vector */
	void scale( const T& scale, const Vector2<T>& Center );

	Vector2<T> ceil() const;

	Vector2<T> floor() const;

	Vector2<T> round() const;

	Vector2<T> roundUp() const;

	Vector2<T> roundDown() const;

	Vector2<T> abs() const;

	Vector2<Float> asFloat() const;

	Vector2<int> asInt() const;

	Vector2<T>& operator=( const Vector2<T>& right );

	T x;
	T y;

  private:
	T cosAng( const T& Ang );
	T sinAng( const T& Ang );
};

template <typename T> const Vector2<T> Vector2<T>::One = Vector2<T>( 1, 1 );

template <typename T> const Vector2<T> Vector2<T>::Zero = Vector2<T>( 0, 0 );

template <typename T> Vector2<T> Vector2<T>::lerp( const Vector2<T>& Vec, T Time ) const {
	return *this * ( 1 - Time ) + Vec * Time;
}

template <typename T> Vector2<T> Vector2<T>::lerpConst( const Vector2<T>& Vec, T Dist ) const {
	Vector2<T> t( *this + ( Vec - *this ) );
	t.clamp( Dist );
	return t;
}

template <typename T> Vector2<T> Vector2<T>::sphericalLerp( const Vector2<T>& Vec, T Time ) const {
	T omega = eeacos( dot( Vec ) );

	if ( omega ) {
		T denom = 1 / eesin( omega );

		return ( Vector2<T>( x, y ) * (T)( eesin( ( 1 - Time ) * omega ) * denom ) +
				 Vec * (T)( eesin( Time * omega ) * denom ) );
	} else {
		return Vector2<T>( x, y );
	}
}

template <typename T>
Vector2<T> Vector2<T>::sphericalLerpConst( const Vector2<T>& Vec, T Angle ) const {
	T angle = eeacos( dot( Vec ) );
	return lerp( Vec, ( ( Angle < angle ) ? Angle : angle ) / angle );
}

template <typename T> Vector2<T>::Vector2() : x( 0 ), y( 0 ) {}

template <typename T> Vector2<T>::Vector2( T X, T Y ) : x( X ), y( Y ) {}

template <typename T> Vector2<T> operator-( const Vector2<T>& V ) {
	return Vector2<T>( -V.x, -V.y );
}

template <typename T> Vector2<T>& operator+=( Vector2<T>& V1, const Vector2<T>& V2 ) {
	V1.x += V2.x;
	V1.y += V2.y;

	return V1;
}

template <typename T> Vector2<T>& operator-=( Vector2<T>& V1, const Vector2<T>& V2 ) {
	V1.x -= V2.x;
	V1.y -= V2.y;

	return V1;
}

template <typename T> Vector2<T> operator+( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return Vector2<T>( V1.x + V2.x, V1.y + V2.y );
}

template <typename T> Vector2<T> operator+( const Vector2<T>& V1, T X ) {
	return Vector2<T>( V1.x + X, V1.y + X );
}

template <typename T> Vector2<T> operator-( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return Vector2<T>( V1.x - V2.x, V1.y - V2.y );
}

template <typename T> Vector2<T> operator-( const Vector2<T>& V1, T X ) {
	return Vector2<T>( V1.x - X, V1.y - X );
}

template <typename T> Vector2<T> operator*( const Vector2<T>& V, T X ) {
	return Vector2<T>( V.x * X, V.y * X );
}

template <typename T> Vector2<T> operator*( T X, const Vector2<T>& V ) {
	return Vector2<T>( V.x * X, V.y * X );
}

template <typename T> Vector2<T> operator*( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return Vector2<T>( V1.x * V2.x, V1.y * V2.y );
}

template <typename T> Vector2<T>& operator*=( Vector2<T>& V, T X ) {
	V.x *= X;
	V.y *= X;

	return V;
}

template <typename T> Vector2<T>& operator*=( Vector2<T>& V, const Vector2<T>& V2 ) {
	V.x *= V2.x;
	V.y *= V2.y;

	return V;
}

template <typename T> Vector2<T> operator/( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return Vector2<T>( V1.x / V2.x, V1.y / V2.y );
}

template <typename T> Vector2<T> operator/( const Vector2<T>& V, T X ) {
	return Vector2<T>( V.x / X, V.y / X );
}

template <typename T> Vector2<T>& operator/=( Vector2<T>& V, T X ) {
	V.x /= X;
	V.y /= X;

	return V;
}

template <typename T> bool operator==( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x == V2.x ) && ( V1.y == V2.y );
}

template <typename T> bool operator==( const Vector2<T>& V1, const T& V ) {
	return ( V1.x == V ) && ( V1.y == V );
}

template <typename T> bool operator==( const T& V, const Vector2<T>& V1 ) {
	return ( V1.x == V ) && ( V1.y == V );
}

template <typename T> bool operator!=( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x != V2.x ) || ( V1.y != V2.y );
}

template <typename T> bool operator!=( const Vector2<T>& V1, const T& V ) {
	return ( V1.x != V ) || ( V1.y != V );
}

template <typename T> bool operator!=( const T& V, const Vector2<T>& V1 ) {
	return ( V1.x != V ) || ( V1.y != V );
}

template <typename T> bool operator<( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x < V2.x ) && ( V1.y < V2.y );
}

template <typename T> bool operator>( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x > V2.x ) && ( V1.y > V2.y );
}

template <typename T> bool operator<=( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x <= V2.x ) && ( V1.y <= V2.y );
}

template <typename T> bool operator>=( const Vector2<T>& V1, const Vector2<T>& V2 ) {
	return ( V1.x >= V2.x ) && ( V1.y >= V2.y );
}

template <typename T> T Vector2<T>::cosAng( const T& Ang ) {
	return eecos( Ang * EE_PI_180 );
}

template <typename T> T Vector2<T>::sinAng( const T& Ang ) {
	return eesin( Ang * EE_PI_180 );
}

template <typename T> void Vector2<T>::rotate( const T& Angle ) {
	T nx = x * cosAng( Angle ) - y * sinAng( Angle );
	y = y * cosAng( Angle ) + x * sinAng( Angle );
	x = nx;
}

template <typename T> void Vector2<T>::rotate( const T& Angle, const Vector2<T>& RotationCenter ) {
	if ( 1.f != Angle ) {
		x -= RotationCenter.x;
		y -= RotationCenter.y;

		rotate( Angle );

		x += RotationCenter.x;
		y += RotationCenter.y;
	}
}

template <typename T> void Vector2<T>::scale( const Vector2<T>& scale, const Vector2<T>& Center ) {
	if ( 1.f != scale ) {
		if ( x < Center.x )
			x = Center.x - eeabs( Center.x - x ) * scale.x;
		else
			x = Center.x + eeabs( Center.x - x ) * scale.x;

		if ( y < Center.y )
			y = Center.y - eeabs( Center.y - y ) * scale.y;
		else
			y = Center.y + eeabs( Center.y - y ) * scale.y;
	}
}

template <typename T> void Vector2<T>::scale( const T& scale, const Vector2<T>& Center ) {
	scale( Vector2<T>( scale, scale ), Center );
}

template <typename T> T Vector2<T>::dot( const Vector2<T>& V2 ) const {
	return x * V2.x + y * V2.y;
}

template <typename T> T Vector2<T>::cross( const Vector2<T>& V2 ) const {
	return x * V2.x - y * V2.y;
}

template <typename T> Vector2<T> Vector2<T>::perp() const {
	return Vector2<T>( -y, x );
}

template <typename T> Vector2<T> Vector2<T>::rPerp() const {
	return Vector2<T>( y, -x );
}

template <typename T> Vector2<T> Vector2<T>::rotate( const Vector2<T>& V2 ) const {
	return Vector2<T>( x * V2.x - y * V2.y, x * V2.y + y * V2.x );
}

template <typename T> Vector2<T> Vector2<T>::unrotate( const Vector2<T>& V2 ) const {
	return Vector2<T>( x * V2.x - y * V2.y, x * V2.x + y * V2.y );
}

template <typename T> T Vector2<T>::length() const {
	return eesqrt( dot( Vector2<T>( x, y ) ) );
}

template <typename T> T Vector2<T>::lengthSq() const {
	return dot( Vector2<T>( x, y ) );
}

template <typename T> void Vector2<T>::normalize() {
	T s = eesqrt( x * x + y * y );
	if ( s == 0 ) {
		x = 0;
		y = 0;
	} else {
		x = x / s;
		y = y / s;
	}
}

template <typename T> Vector2<T> Vector2<T>::forAngle( const T& a ) const {
	return Vector2<T>( eecos( a ), eesin( a ) );
}

template <typename T> T Vector2<T>::toAngle() {
	return eeatan2( y, x );
}

template <typename T> T Vector2<T>::distance( const Vector2<T>& Vec ) const {
	return eesqrt( ( x - Vec.x ) * ( x - Vec.x ) + ( y - Vec.y ) * ( y - Vec.y ) );
}

template <typename T> T Vector2<T>::distanceSq( const Vector2<T>& Vec ) const {
	return ( *this - Vec ).lengthSq();
}

template <typename T> void Vector2<T>::clamp( T len ) {
	if ( dot( Vector2<T>( x, y ) ) > len * len ) {
		normalize();

		x *= len;
		y *= len;
	}
}

template <typename T> Vector2<T> Vector2<T>::ceil() const {
	return Vector2<T>( eeceil( x ), eeceil( y ) );
}

template <typename T> Vector2<T> Vector2<T>::floor() const {
	return Vector2<T>( eefloor( x ), eefloor( y ) );
}

template <typename T> Vector2<T> Vector2<T>::round() const {
	return Vector2<T>( Math::round( x ), Math::round( y ) );
}

template <typename T> Vector2<T> Vector2<T>::roundUp() const {
	return Vector2<T>( Math::roundUp( x ), Math::roundUp( y ) );
}

template <typename T> Vector2<T> Vector2<T>::roundDown() const {
	return Vector2<T>( Math::roundDown( x ), Math::roundDown( y ) );
}

template <typename T> Vector2<T> Vector2<T>::abs() const {
	return Vector2<T>( eeabs( x ), eeabs( y ) );
}

template <typename T> Vector2<Float> Vector2<T>::asFloat() const {
	return Vector2<Float>( x, y );
}

template <typename T> Vector2<int> Vector2<T>::asInt() const {
	return Vector2<int>( x, y );
}

template <typename T> Vector2<T>& Vector2<T>::operator=( const Vector2<T>& right ) {
	this->x = right.x;
	this->y = right.y;
	return *this;
}

template <typename T> bool Vector2<T>::nearDist( const Vector2<T>& Vec, T Dist ) const {
	return 0 != ( distanceSq( Vec ) < Dist * Dist );
}

template <typename T> Vector2<T> Vector2<T>::copy() {
	return Vector2<T>( x, y );
}

typedef Vector2<Int32> Vector2if;
typedef Vector2<int> Vector2i;
typedef Vector2<Float> Vector2f;
typedef Vector2<double> Vector2d;
typedef Vector2<float> Vector2ff;
typedef Vector2<Uint32> Vector2u;

}} // namespace EE::Math

#endif
