#include "base.h"
#include <cmath>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include "Vector2f.h"

//-----------------------------------------------------------------
// Vector2f Constructors
//-----------------------------------------------------------------
Vector2f::Vector2f( )
	:Vector2f{ 0.0f, 0.0f }
{
}

Vector2f::Vector2f( float x, float y )
	: x{ x }
	, y{ y }
{
}

Vector2f::Vector2f( const Point2f& fromPoint, const Point2f& tillPoint )
	: Vector2f{ tillPoint.x - fromPoint.x, tillPoint.y - fromPoint.y }
{
}

Vector2f::Vector2f(const Point2f & point) 
	: Vector2f{ Point2f{ 0.0f, 0.0f }, point }
{
}

// -------------------------
// Methods
// -------------------------
bool Vector2f::Equals(const Vector2f& other, float epsilon) const
{
	return ( abs(x - other.x) < epsilon ) && ( abs(y - other.y) < epsilon );
}

Point2f Vector2f::ToPoint2f() const
{
	return Point2f{ x, y };
}

float Vector2f::DotProduct(const Vector2f& other) const
{
	return x * other.x + y * other.y;
}

float Vector2f::CrossProduct(const Vector2f& other) const
{
	return x * other.y - y * other.x;
}

std::string Vector2f::ToString() const
{
	std::stringstream buffer;

	buffer << std::fixed;
	buffer << std::setprecision( 2 );
	buffer << "Vector2f(" <<  x  << ", " <<  y  << ")";
	return buffer.str();
}

float Vector2f::Norm() const
{
	return Length();
}

float Vector2f::Length() const
{
	return sqrt( x * x + y * y );
}

float Vector2f::SquaredLength() const
{
	return x * x + y * y;
}

float Vector2f::AngleWith(const Vector2f& other) const
{
	//https://stackoverflow.com/questions/21483999/using-atan2-to-find-angle-between-two-vectors
	// not calling dot and cross functions to prevent the overhead
	return atan2(x * other.y - other.x * y, x * other.x + y * other.y);
}

Vector2f Vector2f::Normalized(float epsilon) const
{
	float length{ Length( ) };
	if ( length < epsilon )
	{
		return Vector2f{ 0, 0 };
	}
	else
	{
		return Vector2f{ x / length, y / length };
	}
}

Vector2f Vector2f::Orthogonal() const
{
	return Vector2f{ -y, x };
}

Vector2f Vector2f::Reflect( const Vector2f& surfaceNormal ) const
{
	return (*this) - 2 * ( this->DotProduct( surfaceNormal ) * surfaceNormal );
}

void Vector2f::Set(float newX, float newY)
{
	x = newX;
	y = newY;
}

// -------------------------
// Member operators
// -------------------------
Vector2f Vector2f::operator-( ) const
{
	return Vector2f{ -x, -y };
}
Vector2f Vector2f::operator+ ( ) const
{
	return Vector2f{ x, y };
}

Vector2f& Vector2f::operator*=(float rhs)
{
	x *= rhs;
	y *= rhs;
	return *this;
}

Vector2f& Vector2f::operator/=(float rhs)
{
	*this *= 1 / rhs;
	return *this;
}

Vector2f& Vector2f::operator+=(const Vector2f& rhs)
{
	x += rhs.x;
	y += rhs.y;
	return *this;
}

Vector2f& Vector2f::operator-=(const Vector2f& rhs)
{
	*this += -rhs;
	return *this;
}

Vector2f::operator Point2f()
{
	return Point2f{ x,y };
}

// -------------------------
// Non-member operators
// -------------------------
Vector2f operator*( float lhs, Vector2f rhs )
{
	return rhs *= lhs;
}

Vector2f operator*( Vector2f lhs, float rhs )
{
	return lhs *= rhs;
}

Vector2f operator/( Vector2f lhs, float rhs )
{
	return lhs *= (1 / rhs);
}

Vector2f operator+( Vector2f lhs, const Vector2f& rhs )
{
	return lhs += rhs;
}

Vector2f operator-( Vector2f lhs, const Vector2f& rhs )
{
	return lhs += -rhs;
}

bool operator==( const Vector2f& lhs, const Vector2f& rhs )
{
	return ( lhs.Equals( rhs ) );
}

bool operator!=( const  Vector2f& lhs, const Vector2f& rhs )
{
	return !( lhs == rhs );
}

std::ostream& operator<< ( std::ostream& lhs, const Vector2f& rhs )
{
	lhs << rhs.ToString( );
	return lhs;
}

// Point2f related operators
Point2f& operator+=(Point2f& lhs, const Vector2f& rhs)
{
	lhs.x += rhs.x;
	lhs.y += rhs.y;
	return lhs;
}

Point2f operator+(Point2f lhs, const Vector2f& rhs)
{
	lhs += rhs;
	return lhs;
}

Point2f& operator-=(Point2f& lhs, const Vector2f& rhs)
{
	lhs.x -= rhs.x;
	lhs.y -= rhs.y;
	return lhs;
}

Point2f operator-(Point2f lhs, const Vector2f& rhs)
{
	lhs -= rhs;
	return lhs;
}


Vector2f operator-(const Point2f& lhs, const Point2f& rhs)
{
	Vector2f v{ lhs.x - rhs.x, lhs.y - rhs.y };
	return v;
}