#include "animated_sprite.h"
#include <stddef.h>	 // Para size_t
#include <fstream>	 // Para basic_ostream, basic_istream, operator<<, basic...
#include <iostream>	 // Para cout, cerr
#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
Animations loadAnimationsFromFile(const std::string &file_path)
{
	std::ifstream file(file_path);
	if (!file)
	{
		std::cerr << "Error: Fichero no encontrado " << file_path << std::endl;
		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())
	{
		Animations v = loadAnimationsFromFile(file_path);
		setAnimations(v);
	}
}

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

// Obtiene el indice 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;
		}
	}
	std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
	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 >= static_cast<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
		setClip(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;
		setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
	}
}

// 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;
		setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
	}
}

// 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::setAnimations(const Animations &animations)
{
	int frame_width = 1;
	int frame_height = 1;
	int frames_per_row = 1;
	int max_tiles = 1;

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

		// Parsea el fichero para buscar variables y valores
		if (line != "[animation]")
		{
			// Encuentra la posición del caracter '='
			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
					std::cout << "Warning: unknown parameter " << key << std::endl;

				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 linea contiene el texto [animation] se realiza el proceso de carga de una animación
		if (line == "[animation]")
		{
			AnimationData animation;
			do
			{
				index++;
				line = animations.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_Rect rect = {0, 0, frame_width, frame_height};
						while (getline(ss, tmp, ','))
						{
							// Comprueba que el tile no sea mayor que el maximo indice 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
						std::cout << "Warning: unknown parameter " << key << std::endl;
				}
			} while (line != "[/animation]");

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

		// Una vez procesada la linea, aumenta el indice para pasar a la siguiente
		index++;
	}

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

// Establece el frame actual de la animación
void AnimatedSprite::setCurrentAnimationFrame(int num)
{
	// Descarta valores fuera de rango
	if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size()))
	{
		num = 0;
	}

	// Cambia el valor de la variable
	animations_[current_animation_].current_frame = num;
	animations_[current_animation_].counter = 0;

	// Escoge el frame correspondiente de la animación
	setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}