#include "animated_sprite.h"
#include <SDL3/SDL_log.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError
#include <stddef.h>		  // Para size_t
#include <fstream>		  // Para basic_istream, basic_ifstream, basic_ios
#include <sstream>		  // Para basic_stringstream
#include <stdexcept>	  // Para runtime_error
#include "texture.h"	  // Para Texture
#include "utils.h"		  // Para printWithDots

// Carga las animaciones en un vector(Animations) desde un fichero
AnimationsFileBuffer loadAnimationsFromFile(const std::string &file_path)
{
	std::ifstream file(file_path);
	if (!file)
	{
		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
		throw std::runtime_error("Fichero no encontrado: " + file_path);
	}

	printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");

	std::vector<std::string> buffer;
	std::string line;
	while (std::getline(file, line))
	{
		if (!line.empty())
			buffer.push_back(line);
	}

	return buffer;
}

// Constructor
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const std::string &file_path)
	: MovingSprite(texture)
{
	// Carga las animaciones
	if (!file_path.empty())
	{
		AnimationsFileBuffer v = loadAnimationsFromFile(file_path);
		loadFromAnimationsFileBuffer(v);
	}
}

// Constructor
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, const AnimationsFileBuffer &animations)
	: MovingSprite(texture)
{
	if (!animations.empty())
	{
		loadFromAnimationsFileBuffer(animations);
	}
}

// Obtiene el índice de la animación a partir del nombre
int AnimatedSprite::getIndex(const std::string &name)
{
	auto index = -1;

	for (const auto &a : animations_)
	{
		index++;
		if (a.name == name)
		{
			return index;
		}
	}
	SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "** Warning: could not find \"%s\" animation", name.c_str());
	return -1;
}

// Calcula el frame correspondiente a la animación
void AnimatedSprite::animate()
{
	if (animations_[current_animation_].speed == 0)
	{
		return;
	}

	// Calcula el frame actual a partir del contador
	animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;

	// Si alcanza el final de la animación, reinicia el contador de la animación
	// en función de la variable loop y coloca el nuevo frame
	if (animations_[current_animation_].current_frame >= (int)animations_[current_animation_].frames.size())
	{
		if (animations_[current_animation_].loop == -1)
		{ // Si no hay loop, deja el último frame
			animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
			animations_[current_animation_].completed = true;
		}
		else
		{ // Si hay loop, vuelve al frame indicado
			animations_[current_animation_].counter = 0;
			animations_[current_animation_].current_frame = animations_[current_animation_].loop;
		}
	}
	// En caso contrario
	else
	{
		// Escoge el frame correspondiente de la animación
		setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);

		// Incrementa el contador de la animacion
		animations_[current_animation_].counter++;
	}
}

// Comprueba si ha terminado la animación
bool AnimatedSprite::animationIsCompleted()
{
	return animations_[current_animation_].completed;
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(const std::string &name)
{
	const auto new_animation = getIndex(name);
	if (current_animation_ != new_animation)
	{
		current_animation_ = new_animation;
		animations_[current_animation_].current_frame = 0;
		animations_[current_animation_].counter = 0;
		animations_[current_animation_].completed = false;
	}
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(int index)
{
	const auto new_animation = index;
	if (current_animation_ != new_animation)
	{
		current_animation_ = new_animation;
		animations_[current_animation_].current_frame = 0;
		animations_[current_animation_].counter = 0;
		animations_[current_animation_].completed = false;
	}
}

// Actualiza las variables del objeto
void AnimatedSprite::update()
{
	animate();
	MovingSprite::update();
}

// Reinicia la animación
void AnimatedSprite::resetAnimation()
{
	animations_[current_animation_].current_frame = 0;
	animations_[current_animation_].counter = 0;
	animations_[current_animation_].completed = false;
}

// Carga la animación desde un vector de cadenas
void AnimatedSprite::loadFromAnimationsFileBuffer(const AnimationsFileBuffer &source)
{
	float frame_width = 1;
	float frame_height = 1;
	int frames_per_row = 1;
	int max_tiles = 1;

	size_t index = 0;
	while (index < source.size())
	{
		std::string line = source.at(index);

		// Parsea el fichero para buscar variables y valores
		if (line != "[animation]")
		{
			// Encuentra la posición del carácter '='
			size_t pos = line.find("=");

			// Procesa las dos subcadenas
			if (pos != std::string::npos)
			{
				std::string key = line.substr(0, pos);
				int value = std::stoi(line.substr(pos + 1));
				if (key == "frame_width")
					frame_width = value;
				else if (key == "frame_height")
					frame_height = value;
				else
					SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());

				frames_per_row = texture_->getWidth() / frame_width;
				const int w = texture_->getWidth() / frame_width;
				const int h = texture_->getHeight() / frame_height;
				max_tiles = w * h;
			}
		}

		// Si la línea contiene el texto [animation] se realiza el proceso de carga de una animación
		if (line == "[animation]")
		{
			Animation animation;
			do
			{
				index++;
				line = source.at(index);
				size_t pos = line.find("=");

				if (pos != std::string::npos)
				{
					std::string key = line.substr(0, pos);
					std::string value = line.substr(pos + 1);

					if (key == "name")
						animation.name = value;
					else if (key == "speed")
						animation.speed = std::stoi(value);
					else if (key == "loop")
						animation.loop = std::stoi(value);
					else if (key == "frames")
					{
						// Se introducen los valores separados por comas en un vector
						std::stringstream ss(value);
						std::string tmp;
						SDL_FRect rect = {0, 0, frame_width, frame_height};
						while (getline(ss, tmp, ','))
						{
							// Comprueba que el tile no sea mayor que el máximo índice permitido
							const int num_tile = std::stoi(tmp);
							if (num_tile <= max_tiles)
							{
								rect.x = (num_tile % frames_per_row) * frame_width;
								rect.y = (num_tile / frames_per_row) * frame_height;
								animation.frames.emplace_back(rect);
							}
						}
					}
					else
						SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());
				}
			} while (line != "[/animation]");

			// Añade la animación al vector de animaciones
			animations_.emplace_back(animation);
		}

		// Una vez procesada la línea, aumenta el índice para pasar a la siguiente
		index++;
	}

	// Pone un valor por defecto
	setWidth(frame_width);
	setHeight(frame_height);
}