#include "base.h"
//#define _USE_MATH_DEFINES
#include <cmath>
#include <algorithm>
#include <iostream>
#include "utils.h"

#include "colors.h"

#pragma region OpenGLDrawFunctionality
void utils::SetColor(const Color4f& color) {
	glColor4f(color.r, color.g, color.b, color.a);
}

void utils::ClearBackground(const Color4f& color) {
	glClearColor(color.r, color.g, color.b, color.a);
	glClear(GL_COLOR_BUFFER_BIT);
}

void utils::DrawPoint(float x, float y, float pointSize) {
	glPointSize(pointSize);
	glBegin(GL_POINTS);
	{
		glVertex2f(x, y);
	}
	glEnd();
}

void utils::DrawPoint(const Vector2f& p, float pointSize) {
	DrawPoint(p.x, p.y, pointSize);
}

void utils::DrawPoints(Vector2f* pVertices, int nrVertices, float pointSize) {
	glPointSize(pointSize);
	glBegin(GL_POINTS);
	{
		for (int idx { 0 }; idx < nrVertices; ++idx) {
			glVertex2f(pVertices[idx].x, pVertices[idx].y);
		}
	}
	glEnd();
}

void utils::DrawLine(float x1, float y1, float x2, float y2, float lineWidth) {
	glLineWidth(lineWidth);
	glBegin(GL_LINES);
	{
		glVertex2f(x1, y1);
		glVertex2f(x2, y2);
	}
	glEnd();
}

void utils::DrawLine(const Vector2f& p1, const Vector2f& p2, float lineWidth) {
	DrawLine(p1.x, p1.y, p2.x, p2.y, lineWidth);
}

void utils::DrawTriangle(const Vector2f& p1, const Vector2f& p2, const Vector2f& p3, float lineWidth) {
	glLineWidth(lineWidth);
	glBegin(GL_LINE_LOOP);
	{
		glVertex2f(p1.x, p1.y);
		glVertex2f(p2.x, p2.y);
		glVertex2f(p3.x, p3.y);
	}
	glEnd();
}

void utils::FillTriangle(const Vector2f& p1, const Vector2f& p2, const Vector2f& p3) {
	glBegin(GL_TRIANGLES);
	{
		glVertex2f(p1.x, p1.y);
		glVertex2f(p2.x, p2.y);
		glVertex2f(p3.x, p3.y);
	}
	glEnd();
}

void utils::DrawRect(float left, float bottom, float width, float height, float lineWidth) {
	if (width > 0 && height > 0 && lineWidth > 0) {

		glLineWidth(lineWidth);
		glBegin(GL_LINE_LOOP);
		{
			glVertex2f(left, bottom);
			glVertex2f(left + width, bottom);
			glVertex2f(left + width, bottom + height);
			glVertex2f(left, bottom + height);
		}
		glEnd();
	}
}

void utils::DrawRect(const Vector2f& bottomLeft, float width, float height, float lineWidth) {
	DrawRect(bottomLeft.x, bottomLeft.y, width, height, lineWidth);
}

void utils::DrawRect(const Rectf& rect, float lineWidth) {
	DrawRect(rect.left, rect.bottom, rect.width, rect.height, lineWidth);
}

void utils::FillRect(float left, float bottom, float width, float height) {
	if (width > 0 && height > 0) {

		glBegin(GL_POLYGON);
		{
			glVertex2f(left, bottom);
			glVertex2f(left + width, bottom);
			glVertex2f(left + width, bottom + height);
			glVertex2f(left, bottom + height);
		}
		glEnd();
	}
}

void utils::FillRect(const Vector2f& bottomLeft, float width, float height) {
	FillRect(bottomLeft.x, bottomLeft.y, width, height);
}

void utils::FillRect(const Rectf& rect) {
	FillRect(rect.left, rect.bottom, rect.width, rect.height);
}

void utils::DrawEllipse(float centerX, float centerY, float radX, float radY, float lineWidth) {
	if (radX > 0 && radY > 0 && lineWidth > 0) {

		float dAngle { radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };

		glLineWidth(lineWidth);
		glBegin(GL_LINE_LOOP);
		{
			for (float angle = 0.0; angle < float(2 * g_Pi); angle += dAngle) {
				glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
			}
		}
		glEnd();
	}
}

void utils::DrawEllipse(const Vector2f& center, float radX, float radY, float lineWidth) {
	DrawEllipse(center.x, center.y, radX, radY, lineWidth);
}

void utils::DrawEllipse(const Ellipsef& ellipse, float lineWidth) {
	DrawEllipse(ellipse.center.x, ellipse.center.y, ellipse.radiusX, ellipse.radiusY, lineWidth);
}

void utils::FillEllipse(float centerX, float centerY, float radX, float radY) {
	if (radX > 0 && radY > 0) {

		float dAngle { radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };

		glBegin(GL_POLYGON);
		{
			for (float angle = 0.0; angle < float(2 * g_Pi); angle += dAngle) {
				glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
			}
		}
		glEnd();
	}
}

void utils::FillEllipse(const Ellipsef& ellipse) {
	FillEllipse(ellipse.center.x, ellipse.center.y, ellipse.radiusX, ellipse.radiusY);
}

void utils::FillEllipse(const Vector2f& center, float radX, float radY) {
	FillEllipse(center.x, center.y, radX, radY);
}

void utils::DrawArc(float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle, float lineWidth) {
	if (fromAngle > tillAngle) {
		return;
	}

	float dAngle { radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };

	glLineWidth(lineWidth);
	glBegin(GL_LINE_STRIP);
	{
		for (float angle = fromAngle; angle < tillAngle; angle += dAngle) {
			glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
		}
		glVertex2f(centerX + radX * cos(tillAngle), centerY + radY * sin(tillAngle));
	}
	glEnd();

}

void utils::DrawArc(const Vector2f& center, float radX, float radY, float fromAngle, float tillAngle, float lineWidth) {
	DrawArc(center.x, center.y, radX, radY, fromAngle, tillAngle, lineWidth);
}

void utils::FillArc(float centerX, float centerY, float radX, float radY, float fromAngle, float tillAngle) {
	if (fromAngle > tillAngle) {
		return;
	}
	float dAngle { radX > radY ? float(g_Pi / radX) : float(g_Pi / radY) };

	glBegin(GL_POLYGON);
	{
		glVertex2f(centerX, centerY);
		for (float angle = fromAngle; angle < tillAngle; angle += dAngle) {
			glVertex2f(centerX + radX * cos(angle), centerY + radY * sin(angle));
		}
		glVertex2f(centerX + radX * cos(tillAngle), centerY + radY * sin(tillAngle));
	}
	glEnd();
}

void utils::FillArc(const Vector2f& center, float radX, float radY, float fromAngle, float tillAngle) {
	FillArc(center.x, center.y, radX, radY, fromAngle, tillAngle);
}

void utils::DrawPolygon(const std::vector<Vector2f>& vertices, bool closed, float lineWidth) {
	DrawPolygon(vertices.data(), vertices.size(), closed, lineWidth);
}

void utils::DrawPolygon(const Vector2f* pVertices, size_t nrVertices, bool closed, float lineWidth) {
	glLineWidth(lineWidth);
	closed ? glBegin(GL_LINE_LOOP) : glBegin(GL_LINE_STRIP);
	{
		for (size_t idx { 0 }; idx < nrVertices; ++idx) {
			glVertex2f(pVertices[idx].x, pVertices[idx].y);
		}
	}
	glEnd();
}

void utils::FillPolygon(const std::vector<Vector2f>& vertices) {
	FillPolygon(vertices.data(), vertices.size());
}
void utils::DrawArrow(const Vector2f& start, const Vector2f& end, float lineWidth, float arrowSize) {
	// Origin is bottom left
	utils::DrawLine(start, end);
	Vector2f arrowEnd = end - start;
	Vector2f dir = end - start;

	utils::SetColor(Colors::RED);
	utils::DrawLine(end, end + Vector2f{ dir.y, -dir.x }.Normalized() * arrowSize);
	utils::DrawLine(end, end + Vector2f{ -dir.y, dir.x }.Normalized() * arrowSize);

	
}

void utils::FillPolygon(const Vector2f* pVertices, size_t nrVertices) {
	glBegin(GL_POLYGON);
	{
		for (size_t idx { 0 }; idx < nrVertices; ++idx) {
			glVertex2f(pVertices[idx].x, pVertices[idx].y);
		}
	}
	glEnd();
}
#pragma endregion OpenGLDrawFunctionality

#pragma region CollisionFunctionality
float utils::GetDistance(float x1, float y1, float x2, float y2) {
	return ( sqrtf(( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 )) );
}

float utils::GetDistance(const Vector2f& p1, const Vector2f& p2) {
	return GetDistance(p1.x, p1.y, p2.x, p2.y);
}

bool utils::IsPointInRect(const Vector2f& p, const Rectf& r) {
	return ( p.x >= r.left &&
		p.x <= r.left + r.width &&
		p.y >= r.bottom &&
		p.y <= r.bottom + r.height );
}

bool utils::IsPointInCircle(const Vector2f& p, const Circlef& c) {
	float squaredDist { ( p.x - c.center.x ) * ( p.x - c.center.x ) + ( p.y - c.center.y ) * ( p.y - c.center.y ) };
	float squaredRadius { c.radius * c.radius };
	return ( squaredRadius >= squaredDist );
}

bool utils::IsOverlapping(const Vector2f& a, const Vector2f& b, const Rectf& r) {
	// if one of the line segment end points is in the rect
	if (utils::IsPointInRect(a, r) || utils::IsPointInRect(b, r)) {
		return true;
	}

	HitInfo hitInfo {};
	Vector2f vertices[] { Vector2f { r.left, r.bottom },
		Vector2f { r.left + r.width, r.bottom },
		Vector2f { r.left + r.width, r.bottom + r.height },
		Vector2f { r.left, r.bottom + r.height } };

	return Raycast(vertices, 4, a, b, hitInfo);
}

bool utils::IsOverlapping(const Rectf& r1, const Rectf& r2) {
	// If one rectangle is on left side of the other
	if (( r1.left + r1.width ) < r2.left || ( r2.left + r2.width ) < r1.left) {
		return false;
	}

	// If one rectangle is under the other
	if (r1.bottom > ( r2.bottom + r2.height ) || r2.bottom > ( r1.bottom + r1.height )) {
		return false;
	}

	return true;
}

bool utils::IsOverlapping(const Rectf& r, const Circlef& c) {
	// Is center of circle in the rectangle?
	if (IsPointInRect(c.center, r)) {
		return true;
	}
	// Check line segments
	if (utils::DistPointLineSegment(c.center, Vector2f { r.left, r.bottom }, Vector2f { r.left, r.bottom + r.height }) <= c.radius) {
		return true;
	}
	if (utils::DistPointLineSegment(c.center, Vector2f { r.left, r.bottom }, Vector2f { r.left + r.width, r.bottom }) <= c.radius) {
		return true;
	}
	if (utils::DistPointLineSegment(c.center, Vector2f { r.left + r.width, r.bottom + r.height }, Vector2f { r.left, r.bottom + r.height }) <= c.radius) {
		return true;
	}
	if (utils::DistPointLineSegment(c.center, Vector2f { r.left + r.width, r.bottom + r.height }, Vector2f { r.left + r.width, r.bottom }) <= c.radius) {
		return true;
	}
	return false;
}

bool utils::IsOverlapping(const Circlef& c1, const Circlef& c2) {
	// squared distance between centers
	float xDistance { c1.center.x - c2.center.x };
	float yDistance { c1.center.y - c2.center.y };
	float squaredDistance { xDistance * xDistance + yDistance * yDistance };

	float squaredTouchingDistance { ( c1.radius + c2.radius ) * ( c1.radius + c2.radius ) };
	return ( squaredDistance < squaredTouchingDistance );
}

bool utils::IsOverlapping(const Vector2f& a, const Vector2f& b, const Circlef& c) {
	return utils::DistPointLineSegment(c.center, a, b) <= c.radius;
}

bool utils::IsOverlapping(const std::vector<Vector2f>& vertices, const Circlef& c) {
	return IsOverlapping(vertices.data(), vertices.size(), c);
}

bool utils::IsOverlapping(const Vector2f* vertices, size_t nrVertices, const Circlef& c) {
	// Verify whether one of vertices is in circle
	for (size_t i { 0 }; i < nrVertices; ++i) {
		if (IsPointInCircle(vertices[i], c)) {
			return true;
		}
	}

	// Verify whether one of the polygon edges overlaps with circle
	for (size_t i { 0 }; i < nrVertices; ++i) {
		if (DistPointLineSegment(c.center, vertices[i], vertices[( i + 1 ) % nrVertices]) <= c.radius) {
			return true;
		}
	}

	// No overlapping with edges, verify whether circle is completely inside the polygon
	if (IsPointInPolygon(c.center, vertices, nrVertices)) {
		return true;
	}
	return false;
}

bool utils::IsPointInPolygon(const Vector2f& p, const std::vector<Vector2f>& vertices) {
	return IsPointInPolygon(p, vertices.data(), vertices.size());
}

bool utils::IsPointInPolygon(const Vector2f& p, const Vector2f* vertices, size_t nrVertices) {
	if (nrVertices < 2) {
		return false;
	}
	// 1. First do a simple test with axis aligned bounding box around the polygon
	float xMin { vertices[0].x };
	float xMax { vertices[0].x };
	float yMin { vertices[0].y };
	float yMax { vertices[0].y };
	for (size_t idx { 1 }; idx < nrVertices; ++idx) {
		if (xMin > vertices[idx].x) {
			xMin = vertices[idx].x;
		}
		if (xMax < vertices[idx].x) {
			xMax = vertices[idx].x;
		}
		if (yMin > vertices[idx].y) {
			yMin = vertices[idx].y;
		}
		if (yMax < vertices[idx].y) {
			yMax = vertices[idx].y;
		}
	}
	if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) {
		return false;
	}

	// 2. Draw a virtual ray from anywhere outside the polygon to the point 
	//    and count how often it hits any side of the polygon. 
	//    If the number of hits is even, it's outside of the polygon, if it's odd, it's inside.
	int numberOfIntersectionPoints { 0 };
	Vector2f p2 { xMax + 10.0f, p.y }; // Horizontal line from point to point outside polygon (p2)

	// Count the number of intersection points
	float lambda1 {}, lambda2 {};
	for (size_t i { 0 }; i < nrVertices; ++i) {
		if (IntersectLineSegments(vertices[i], vertices[( i + 1 ) % nrVertices], p, p2, lambda1, lambda2)) {
			if (lambda1 > 0 && lambda1 <= 1 && lambda2 > 0 && lambda2 <= 1) {
				++numberOfIntersectionPoints;
			}
		}
	}
	if (numberOfIntersectionPoints % 2 == 0) {
		return false;
	}
	else {
		return true;
	}
}

bool utils::IntersectLineSegments(const Vector2f& p1, const Vector2f& p2, const Vector2f& q1, const Vector2f& q2, float& outLambda1, float& outLambda2, float epsilon) {
	bool intersecting { false };

	Vector2f p1p2 { p1, p2 };
	Vector2f q1q2 { q1, q2 };

	// Cross product to determine if parallel
	float denom = p1p2.CrossProduct(q1q2);

	// Don't divide by zero
	if (std::abs(denom) > epsilon) {
		intersecting = true;

		Vector2f p1q1 { p1, q1 };

		float num1 = p1q1.CrossProduct(q1q2);
		float num2 = p1q1.CrossProduct(p1p2);
		outLambda1 = num1 / denom;
		outLambda2 = num2 / denom;
	}
	else // are parallel
	{
		// Connect start points
		Vector2f p1q1 { p1, q1 };

		// Cross product to determine if segments and the line connecting their start points are parallel, 
		// if so, than they are on a line
		// if not, then there is no intersection
		if (std::abs(p1q1.CrossProduct(q1q2)) > epsilon) {
			return false;
		}

		// Check the 4 conditions
		outLambda1 = 0;
		outLambda2 = 0;
		if (utils::IsPointOnLineSegment(p1, q1, q2) ||
			utils::IsPointOnLineSegment(p2, q1, q2) ||
			utils::IsPointOnLineSegment(q1, p1, p2) ||
			utils::IsPointOnLineSegment(q2, p1, p2)) {
			intersecting = true;
		}
	}
	return intersecting;
}

bool utils::Raycast(const std::vector<Vector2f>& vertices, const Vector2f& rayP1, const Vector2f& rayP2, HitInfo& hitInfo) {
	return Raycast(vertices.data(), vertices.size(), rayP1, rayP2, hitInfo);
}

bool utils::Raycast(const Vector2f* vertices, const size_t nrVertices, const Vector2f& rayP1, const Vector2f& rayP2, HitInfo& hitInfo) {
	if (nrVertices == 0) {
		return false;
	}

	std::vector<HitInfo> hits;

	Rectf r1, r2;
	// r1: minimal AABB rect enclosing the ray
	r1.left = std::min(rayP1.x, rayP2.x);
	r1.bottom = std::min(rayP1.y, rayP2.y);
	r1.width = std::max(rayP1.x, rayP2.x) - r1.left;
	r1.height = std::max(rayP1.y, rayP2.y) - r1.bottom;

	// Line-line intersections.
	for (size_t idx { 0 }; idx <= nrVertices; ++idx) {
		// Consider line segment between 2 consecutive vertices
		// (modulo to allow closed polygon, last - first vertice)
		Vector2f q1 = vertices[( idx + 0 ) % nrVertices];
		Vector2f q2 = vertices[( idx + 1 ) % nrVertices];

		// r2: minimal AABB rect enclosing the 2 vertices
		r2.left = std::min(q1.x, q2.x);
		r2.bottom = std::min(q1.y, q2.y);
		r2.width = std::max(q1.x, q2.x) - r2.left;
		r2.height = std::max(q1.y, q2.y) - r2.bottom;

		if (IsOverlapping(r1, r2)) {
			float lambda1 {};
			float lambda2 {};
			if (IntersectLineSegments(rayP1, rayP2, q1, q2, lambda1, lambda2)) {
				if (lambda1 > 0 && lambda1 <= 1 && lambda2 > 0 && lambda2 <= 1) {
					HitInfo linesHitInfo {};
					linesHitInfo.lambda = lambda1;
					linesHitInfo.intersectPoint = Vector2f { rayP1.x + ( ( rayP2.x - rayP1.x ) * lambda1 ), rayP1.y + ( ( rayP2.y - rayP1.y ) * lambda1 ) };
					linesHitInfo.normal = Vector2f { q2.x - q1.x, q2.y - q2.y }.Orthogonal().Normalized();
					hits.push_back(linesHitInfo);
				}
			}
		}
	}

	if (hits.size() == 0) {
		return false;
	}

	// Get closest intersection point and copy it into the hitInfo parameter
	hitInfo = *std::min_element
	(
		hits.begin(), hits.end(),
		[](const HitInfo& first, const HitInfo& last)
		{
			return first.lambda < last.lambda;
		}
	);
	return true;
}

bool utils::IsPointOnLineSegment(const Vector2f& p, const Vector2f& a, const Vector2f& b) {
	Vector2f ap { a, p }, bp { b, p };
	// If not on same line, return false
	if (abs(ap.CrossProduct(bp)) > 0.001f) {
		return false;
	}

	// Both vectors must point in opposite directions if p is between a and b
	if (ap.DotProduct(bp) > 0) {
		return false;
	}

	return true;
}

float utils::DistPointLineSegment(const Vector2f& p, const Vector2f& a, const Vector2f& b) {
	Vector2f ab { a, b };
	Vector2f ap { a, p };
	Vector2f abNorm { ab.Normalized() };
	float distToA { abNorm.DotProduct(ap) };

	// If distToA is negative, then the closest point is A
	// return the distance a, p
	if (distToA < 0) {
		return ap.Length();
	}
	// If distToA is > than dist(a,b) then the closest point is B
	// return the distance b, p
	float distAB { ab.Length() };
	if (distToA > distAB) {
		return Vector2f { b, p }.Length();
	}

	// Closest point is between A and B, calc intersection point
	Vector2f intersection { abNorm.DotProduct(ap) * abNorm + Vector2f { a } };
	return Vector2f { p.x - intersection.x, p.y - intersection.y }.Length();
}

bool utils::IntersectRectLine(const Rectf& r, const Vector2f& p1, const Vector2f& p2, float& intersectMin, float& intersectMax) {
	// Parameters
	// input: 
	//   r: axis aligned bounding box, start and end points of line segment.
	//   p1, p2: line
	// output: 
	//   intersectMin and intersectMax: in the interval [0,1] if intersection point is on the line segment. 
	// return
	//   true if there is an intersection

	// Example of how to use
	//float min{};
	//float max{};
	//if (utils::IntersectRectLine(rect, p1, p2, min, max))
	//{
	//	Vector2f intersectP1{ p1 + (Vector2f(p2) - Vector2f(p1)) * min };
	//	Vector2f intersectP2{ p1 + (Vector2f(p2) - Vector2f(p1)) * max };
	//}

	// 4 floats to convert rect space to line space
	// x1: value between 0 and 1 where 0 is on p1 and 1 is on p2, <0 and >1 means intersection is not on line segment
	float x1 { ( r.left - p1.x ) / ( p2.x - p1.x ) };
	float x2 { ( r.left + r.width - p1.x ) / ( p2.x - p1.x ) };
	float y1 { ( r.bottom - p1.y ) / ( p2.y - p1.y ) };
	float y2 { ( r.bottom + r.height - p1.y ) / ( p2.y - p1.y ) };

	using std::max;
	using std::min;
	float tMin { max(min(x1, x2), min(y1, y2)) };
	float tMax { min(max(x1, x2), max(y1, y2)) };
	if (tMin > tMax) {
		return false;
	}
	intersectMin = tMin;
	intersectMax = tMax;
	return true;
}
bool utils::IsRectInRect(const Rectf& r1, const Rectf& r2) {
	// the origin of both rectangles is in bottom left
	return ( r1.left < r2.left + r2.width && r1.left + r1.width > r2.left && r1.bottom < r2.bottom + r2.height && r1.bottom + r1.height > r2.bottom );
}
bool utils::RayVsRect(const Vector2f& rayOrigin, const Vector2f& rayDir, const Rectf& target,
	Vector2f& contactPoint, Vector2f& contactNormal, float& t_hit_near) {

	// Vector2f t_near = Vector2f{(target.BottomLeft() - rayOrigin).x / rayDir.x, (target.BottomLeft() - rayOrigin).y / rayDir.y};
	// Vector2f t_far = Vector2f{(target.BottomLeft() + Vector2f{target.width, target.height} - rayOrigin).x / rayDir.x, (target.BottomLeft() + Vector2f{target.width, target.height} - rayOrigin).y / rayDir.y};

	Vector2f t_near {};
	Vector2f t_far {};

	if (std::isnan(t_far.y) || std::isnan(t_far.x))
		return false;
	if (std::isnan(t_near.y) || std::isnan(t_near.x))
		return false;

	if (t_near.x > t_far.x)
		std::swap(t_near.x, t_far.x);
	if (t_near.y > t_far.y)
		std::swap(t_near.y, t_far.y);

	if (t_near.x > t_far.y || t_near.y > t_far.x)
		return false;

	t_hit_near = std::max(t_near.x, t_near.y);
	float t_hit_far = std::min(t_far.x, t_far.y);

	if (t_hit_far < 0)
		return false;

	contactPoint = rayOrigin + rayDir * t_hit_near;

	if (t_near.x > t_near.y) {
		if (rayDir.x < 0) {
			contactNormal = Vector2f { 1, 0 };
		}
		else {
			contactNormal = Vector2f { -1, 0 };
		}
	}
	else if (t_near.x < t_near.y) {
		if (rayDir.y < 0) {
			contactNormal = Vector2f { 0, 1 };
		}
		else {
			contactNormal = Vector2f { 0, -1 };
		}
	}
	return true;
}

bool utils::DynamicRectVsRect(const MovingRectf& in, const Rectf& target, Vector2f& contactPoint, Vector2f& contactNormal, float& contactTime, float dt) {
	if (in.velocity.x == 0 && in.velocity.y == 0)
		return false;


	Rectf expanded_target {};
	expanded_target.left = target.left - in.width / 2;
	expanded_target.bottom = target.bottom - in.height / 2;
	expanded_target.width = target.width + in.width;
	expanded_target.height = target.height + in.height;

	if (RayVsRect(Vector2f { in.bottomLeft.x + in.width / 2, in.bottomLeft.y + in.height / 2 }, in.velocity * dt, expanded_target, contactPoint, contactNormal, contactTime)) {
		if (contactTime <= 1.0f && contactTime >= 0.0f) {
			return true;
		}
	}
	return false;
}
float utils::DotProduct(const Vector2f& a, const Vector2f& b) {
	return a.x * b.x + a.y * b.y;
}
#pragma endregion CollisionFunctionality

int utils::randRange(int min, int max) {
	return min + rand() % ( ( max + 1 ) - min );
}
float utils::lerp(float a, float b, float t) {
	return a + t * ( b - a );
}
Vector2f utils::lerp(const Vector2f& a, const Vector2f& b, float t) {
	return Vector2f { lerp(a.x, b.x, t), lerp(a.y, b.y, t) };
}
float utils::map(float value, float start1, float stop1, float start2, float stop2) {
	float newVal =  (value - start1) / (stop1 - start1) * (stop2 - start2) + start2;
	return newVal;
}
bool utils::isKeyDown(int keycode) {
	const Uint8* pStates = SDL_GetKeyboardState(nullptr);
	if (pStates != nullptr) {
		return pStates[keycode];
	}
	return false;
}
static bool S_PrevKeyStates[256] = { false };
bool utils::isKeyPressed(int keycode) {
	const Uint8* pStates = SDL_GetKeyboardState(nullptr);
	if (pStates == nullptr) {
		return false;
	}
	bool pressed { false };

	bool currentPressed = pStates[keycode];
	bool lastPressed = S_PrevKeyStates[keycode];

	if (!lastPressed && currentPressed) {
		pressed = true;
	}
	S_PrevKeyStates[keycode] = currentPressed;
	return pressed;
}

bool utils::isKeyUp(int keycode) {
	const Uint8* pStates = SDL_GetKeyboardState(nullptr);
	if (pStates[keycode] == 0) {
		return true;
	}
	return false;
}
Vector2f utils::GetMousePos() {
	int x, y;
	SDL_GetMouseState(&x, &y);
	//TODO: make the screen size a global or something
	return Vector2f { float(x), float(500.f - y) };
}
bool utils::IsMouseButtonDown(int button) {
	const Uint32 pStates = SDL_GetMouseState(nullptr, nullptr);
	if (pStates & SDL_BUTTON(button)) {
		return true;
	}
	return false;
}
static Vector2f ViewportSize{ 900.f, 500.f }; //TODO: somehow move this (Ask teacher)
Vector2f utils::GetViewport() {
	return ViewportSize;	
}

bool utils::isMouseDown(int button) {
	const Uint32 pStates = SDL_GetMouseState(nullptr, nullptr);
	if (pStates & SDL_BUTTON(button)) {
		return true;
	}
	return false;
}

//utils::getScrollMovement()

