// IWYU pragma: no_include <bits/std_abs.h>
#include "path_sprite.h"
#include <functional> // Para function
#include <utility>	  // Para move

// Devuelve un vector con los puntos que conforman la ruta
std::vector<SDL_FPoint> createPath(float start, float end, PathType type, float fixed_pos, int steps, const std::function<double(double)> &easingFunction)
{
	std::vector<SDL_FPoint> v;
	v.reserve(steps);

	for (int i = 0; i < steps; ++i)
	{
		double t = static_cast<double>(i) / (steps - 1);
		double value = start + (end - start) * easingFunction(t);

		if ((start > 0 && end < 0) || (start < 0 && end > 0))
		{
			value = start + (end > 0 ? 1 : -1) * std::abs(end - start) * easingFunction(t);
		}

		switch (type)
		{
		case PathType::HORIZONTAL:
			v.emplace_back(SDL_FPoint{static_cast<float>(value), fixed_pos});
			break;
		case PathType::VERTICAL:
			v.emplace_back(SDL_FPoint{fixed_pos, static_cast<float>(value)});
			break;
		default:
			break;
		}
	}

	return v;
}

// Actualiza la posición y comprueba si ha llegado a su destino
void PathSprite::update()
{
	if (enabled_ && !has_finished_)
	{
		moveThroughCurrentPath();
		goToNextPathOrDie();
	}
}

// Muestra el sprite por pantalla
void PathSprite::render()
{
	if (enabled_)
	{
		Sprite::render();
	}
}

// Añade un recorrido
void PathSprite::addPath(Path path, bool centered)
{
	PathCentered path_centered = PathCentered::NONE;
	if (centered)
		path_centered = (path.spots.back().x == path.spots.front().x) ? PathCentered::ON_X : PathCentered::ON_Y;

	switch (path_centered)
	{
	case PathCentered::ON_X:
	{
		const int x = path.spots.back().x - pos_.w / 2;
		for (auto &spot : path.spots)
			spot.x = x;
		paths_.emplace_back(path);
		break;
	}
	case PathCentered::ON_Y:
	{
		const int y = path.spots.back().y - pos_.h / 2;
		for (auto &spot : path.spots)
			spot.y = y;
		paths_.emplace_back(path);
		break;
	}
	default:
		paths_.emplace_back(path);
		break;
	}
}

// Añade un recorrido
void PathSprite::addPath(int start, int end, PathType type, int fixed_pos, int steps, const std::function<double(double)> &easingFunction, int waiting_counter)
{
	paths_.emplace_back(createPath(start, end, type, fixed_pos, steps, easingFunction), waiting_counter);
}

// Añade un recorrido
void PathSprite::addPath(std::vector<SDL_FPoint> spots, int waiting_counter)
{
	paths_.emplace_back(std::move(spots), waiting_counter);
}

// Habilita el objeto
void PathSprite::enable()
{
	if (paths_.size() == 0 || enabled_)
	{
		return;
	}

	enabled_ = true;

	// Establece la posición
	auto &path = paths_.at(current_path_);
	const auto &P = path.spots.at(path.counter);
	setPosition(P);
}

// Coloca el sprite en los diferentes puntos del recorrido
void PathSprite::moveThroughCurrentPath()
{
	auto &path = paths_.at(current_path_);

	// Establece la posición
	const auto &p = path.spots.at(path.counter);
	setPosition(p);

	// Comprobar si ha terminado el recorrido
	if (!path.on_destination)
	{
		++path.counter;
		if (path.counter >= static_cast<int>(path.spots.size()))
		{
			path.on_destination = true;
			path.counter = static_cast<int>(path.spots.size()) - 1;
		}
	}

	// Comprobar si ha terminado la espera
	if (path.on_destination)
	{
		if (path.waiting_counter == 0)
		{
			path.finished = true;
		}
		else
		{
			--path.waiting_counter;
		}
	}
}

// Cambia de recorrido o finaliza
void PathSprite::goToNextPathOrDie()
{
	// Comprueba si ha terminado el recorrdo actual
	if (paths_.at(current_path_).finished)
	{
		++current_path_;
	}

	// Comprueba si quedan mas recorridos
	if (current_path_ >= static_cast<int>(paths_.size()))
	{
		has_finished_ = true;
		current_path_ = 0;
	}
}

// Indica si ha terminado todos los recorridos
bool PathSprite::hasFinished() { return has_finished_; }