#include "Light.h"

void Light::draw(sf::RenderWindow& window)
{
	if (hasFlags(Flags::visibility))
	{
		window.draw(m_light.data(), m_light.size(), sf::PrimitiveType::TriangleFan);
	}

	if (hasFlags(Flags::drawRays))
	{
		sf::VertexArray tempRay(sf::PrimitiveType::Lines, 2);
		tempRay[start].position = m_origin;
		tempRay[start].color = m_rayColor;
		tempRay[end].color = m_rayColor;

		for (auto& ray : m_rays)
		{
			tempRay[end].position = ray.position;
			window.draw(tempRay);
		}
	}

	if (hasFlags(Flags::drawEndPoints))
	{
		sf::CircleShape endPoint(5.f, 20u);
		endPoint.setOrigin({ 5, 5 });
		endPoint.setFillColor(m_endPointColor);

		for (auto& ray : m_rays)
		{
			endPoint.setPosition(ray.position);
			window.draw(endPoint);
		}
	}
}


void Light::processRays(const std::vector<sf::VertexArray>& polygons)
{
	m_light[0].position = m_origin;
	if (m_center and m_offset)
	{
		constructPartialLight(polygons);
	}
	else
	{
		constructFullLight(polygons);
	}
}


void Light::constructFullLight(const std::vector<sf::VertexArray>& polygons)
{
	createAndSortRays(polygons);

	calculateIntersects(polygons);

	for (auto& ray : m_rays)
	{
		m_light.emplace_back(ray);
	}

	m_light.emplace_back(m_rays.front());//fully connect the triangle fan

}


void Light::constructPartialLight(const std::vector<sf::VertexArray>& polygons)
{
	createAndSortRays(polygons, m_center.value() - m_offset.value(), m_center.value() + m_offset.value());

	calculateIntersects(polygons);

	for (auto& ray : m_rays)
	{
		m_light.emplace_back(ray);
	}

}


void Light::createAndSortRays(const std::vector<sf::VertexArray>& polygons)
{

	if (not hasFlags(Flags::infiniteLength))
	{
		if (m_rayCount > 0)
		{
			float angleIncrement = 360.f / (float)m_rayCount;
			for (int i = 0; i < m_rayCount; i++) m_rays.emplace_back(sf::Vertex{ constructVector( m_origin, sf::degrees(angleIncrement * i), m_rayLength ), m_lightColor });
		}
	}

	sf::Angle angle;
	const float infiniteLength = 5000.f * hasFlags(Flags::infiniteLength);

	for (auto& poly : polygons)
	{
		for (std::size_t i = 0; i < poly.getVertexCount() - 1; i++)
		{
			if (hasFlags(Flags::infiniteLength) ? false : distanceBetween(m_origin, poly[i].position) >= m_rayLength) continue;
			
			angle = sf::radians(std::atan2f(poly[i].position.y - m_origin.y, poly[i].position.x - m_origin.x));

			m_rays.emplace_back(sf::Vertex{ poly[i].position, m_lightColor });
			m_rays.emplace_back(sf::Vertex{ constructVector( m_origin, angle + sf::radians(.00001f), m_rayLength + infiniteLength), m_lightColor });
			m_rays.emplace_back(sf::Vertex{ constructVector( m_origin, angle - sf::radians(.00001f), m_rayLength + infiniteLength), m_lightColor });
		}
	}

	std::sort(m_rays.begin(), m_rays.end(), [&](const auto& lhs, const auto& rhs) { return (lhs.position - m_origin).angle().wrapUnsigned().asRadians() < (rhs.position - m_origin).angle().wrapUnsigned().asRadians(); });
}

// overload for bounded light
void Light::createAndSortRays(const std::vector<sf::VertexArray>& polygons, sf::Angle lowerAngleBounds, sf::Angle upperAngleBounds)
{
	const float infiniteLength = 5000.f * hasFlags(Flags::infiniteLength);
	sf::Vertex lowerBound{ constructVector( m_origin, lowerAngleBounds, m_rayLength + infiniteLength), m_lightColor };
	sf::Vertex upperBound{ constructVector( m_origin, upperAngleBounds, m_rayLength + infiniteLength), m_lightColor };
	auto lowerBoundVector = lowerBound.position - m_origin;
	auto upperBoundVector = upperBound.position - m_origin;

	if (not hasFlags(Flags::infiniteLength))
	{
		sf::Vertex temp;
		float angleIncrement = 360.f / (float)m_rayCount;
		for (int i = 0; i < m_rayCount; i++)
		{
			temp = sf::Vertex{ constructVector(m_origin, sf::degrees(angleIncrement * i), m_rayLength), m_lightColor };

			if (withinBounds(temp.position - m_origin, lowerBoundVector, upperBoundVector))	m_rays.emplace_back(temp);
		}
	}

	sf::Angle angle;

	for (auto& poly : polygons)
	{
		for (std::size_t i = 0; i < poly.getVertexCount() - 1; i++)
		{
			if (poly[i].position == m_origin) continue;// cannot pass zero vector to sf::Vector2::angleTo()

			if (hasFlags(Flags::infiniteLength) ? false : distanceBetween(m_origin, poly[i].position) >= m_rayLength) continue;
			
			if (not withinBounds(poly[i].position - m_origin, lowerBoundVector, upperBoundVector)) continue;
				
			angle = sf::radians(std::atan2f(poly[i].position.y - m_origin.y, poly[i].position.x - m_origin.x));

			m_rays.emplace_back(sf::Vertex{ poly[i].position, m_lightColor });
			m_rays.emplace_back(sf::Vertex{ constructVector( m_origin, angle + sf::radians(.00001f), m_rayLength + infiniteLength), m_lightColor });
			m_rays.emplace_back(sf::Vertex{ constructVector( m_origin, angle - sf::radians(.00001f), m_rayLength + infiniteLength), m_lightColor });
		}
	}

	m_rays.emplace_back(lowerBound);
	m_rays.emplace_back(upperBound);

	std::sort(m_rays.begin(), m_rays.end(), [&](const auto& lhs, const auto& rhs) { return (lhs.position - m_origin).angleTo(upperBoundVector).wrapUnsigned().asRadians() < (rhs.position - m_origin).angleTo(upperBoundVector).wrapUnsigned().asRadians(); });
}

void Light::calculateIntersects(const std::vector<sf::VertexArray>& polygons)
{
	std::optional<sf::Vector2f> currentIntersect{};

	for (auto& ray : m_rays)
	{
		sf::Vector2f closestIntersection = ray.position;
		currentIntersect.reset();

		for (auto& poly : polygons)
		{
			for (std::size_t i = 1; i <= poly.getVertexCount() - 1; i++) 
			{
				currentIntersect = lineIntersect({ m_origin, ray.position }, { poly[i - 1].position , poly[i].position });

				if (not currentIntersect) continue;
				if ((closestIntersection - m_origin).length() < (currentIntersect.value() - m_origin).length())	continue;

				closestIntersection = currentIntersect.value();
			}
		}

		ray.position = closestIntersection;
	}

}

bool Light::withinBounds(sf::Vector2f point, sf::Vector2f lower, sf::Vector2f upper)
{
	return point.angleTo(upper).wrapUnsigned().asRadians() < (lower).angleTo(upper).wrapUnsigned().asRadians();
}