#include "animated_sprite.h"
#include <fstream>	 // for basic_ostream, operator<<, basic_istream, basic...
#include <iostream>	 // for cout
#include <sstream>	 // for basic_stringstream
#include "texture.h" // for Texture

// Carga la animación desde un fichero
AnimatedFile loadAnimationFromFile(std::shared_ptr<Texture> texture, std::string file_path)
{
	// Inicializa variables
	AnimatedFile af;
	af.texture = texture;
	auto frames_per_row = 0;
	auto frame_width = 0;
	auto frame_height = 0;
	auto max_tiles = 0;

	const std::string file_name = file_path.substr(file_path.find_last_of("\\/") + 1);
	std::ifstream file(file_path);
	std::string line;

	// El fichero se puede abrir
	if (file.good())
	{
		// Procesa el fichero linea a linea
#ifdef VERBOSE
		std::cout << "Animation loaded: " << file_name << std::endl;
#endif
		while (std::getline(file, line))
		{
			// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
			if (line == "[animation]")
			{
				Animation buffer;
				buffer.counter = 0;
				buffer.current_frame = 0;
				buffer.completed = false;

				do
				{
					std::getline(file, line);

					// Encuentra la posición del caracter '='
					int pos = line.find("=");

					// Procesa las dos subcadenas
					if (pos != (int)line.npos)
					{
						if (line.substr(0, pos) == "name")
						{
							buffer.name = line.substr(pos + 1, line.length());
						}

						else if (line.substr(0, pos) == "speed")
						{
							buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
						}

						else if (line.substr(0, pos) == "loop")
						{
							buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
						}

						else if (line.substr(0, pos) == "frames")
						{
							// Se introducen los valores separados por comas en un vector
							std::stringstream ss(line.substr(pos + 1, line.length()));
							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 auto num_tile = std::stoi(tmp) > max_tiles ? 0 : std::stoi(tmp);
								rect.x = (num_tile % frames_per_row) * frame_width;
								rect.y = (num_tile / frames_per_row) * frame_height;
								buffer.frames.push_back(rect);
							}
						}

						else
						{
#ifdef VERBOSE
							std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
#endif
						}
					}
				} while (line != "[/animation]");

				// Añade la animación al vector de animaciones
				af.animations.push_back(buffer);
			}

			// En caso contrario se parsea el fichero para buscar las variables y los valores
			else
			{
				// Encuentra la posición del caracter '='
				int pos = line.find("=");

				// Procesa las dos subcadenas
				if (pos != (int)line.npos)
				{
					if (line.substr(0, pos) == "frames_per_row")
					{
						frames_per_row = std::stoi(line.substr(pos + 1, line.length()));
					}

					else if (line.substr(0, pos) == "frame_width")
					{
						frame_width = std::stoi(line.substr(pos + 1, line.length()));
					}

					else if (line.substr(0, pos) == "frame_height")
					{
						frame_height = std::stoi(line.substr(pos + 1, line.length()));
					}

					else
					{
#ifdef VERBOSE
						std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl;
#endif
					}

					// Normaliza valores
					if (frames_per_row == 0 && frame_width > 0)
					{
						frames_per_row = texture->getWidth() / frame_width;
					}

					if (max_tiles == 0 && frame_width > 0 && frame_height > 0)
					{
						const auto w = texture->getWidth() / frame_width;
						const auto h = texture->getHeight() / frame_height;
						max_tiles = w * h;
					}
				}
			}
		}

		// Cierra el fichero
		file.close();
	}
	// El fichero no se puede abrir
	else
	{
#ifdef VERBOSE
		std::cout << "Warning: Unable to open " << file_name.c_str() << " file" << std::endl;
#endif
	}

	return af;
}

// Constructor
AnimatedSprite::AnimatedSprite(std::shared_ptr<Texture> texture, std::string file, std::vector<std::string> *buffer)
{
	// Copia los punteros
	setTexture(texture);

	// Carga las animaciones
	if (file != "")
	{
		AnimatedFile as = loadAnimationFromFile(texture, file);

		// Copia los datos de las animaciones
		for (auto animation : as.animations)
		{
			animations_.push_back(animation);
		}
	}

	else if (buffer)
	{
		loadFromVector(buffer);
	}

	// Inicializa variables
	current_animation_ = 0;
}

// Constructor
AnimatedSprite::AnimatedSprite(AnimatedFile *animation)
{
	// Copia los punteros
	setTexture(animation->texture);

	// Inicializa variables
	current_animation_ = 0;

	// Copia los datos de las animaciones
	for (auto a : animation->animations)
	{
		animations_.push_back(a);
	}
}

// Destructor
AnimatedSprite::~AnimatedSprite()
{
	animations_.clear();
}

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

	for (auto a : animations_)
	{
		index++;
		if (a.name == name)
		{
			return index;
		}
	}
#ifdef VERBOSE
	std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
#endif
	return -1;
}

// Calcula el frame correspondiente a la animación
void AnimatedSprite::animate()
{
	if (!enabled_ || 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++;
	}
}

// Obtiene el número de frames de la animación actual
int AnimatedSprite::getNumFrames()
{
	return (int)animations_[current_animation_].frames.size();
}

// Establece el frame actual de la animación
void AnimatedSprite::setCurrentFrame(int num)
{
	// Descarta valores fuera de rango
	if (num >= (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
	setSpriteClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}

// Establece el valor del contador
void AnimatedSprite::setAnimationCounter(std::string name, int num)
{
	animations_[getIndex(name)].counter = num;
}

// Establece la velocidad de una animación
void AnimatedSprite::setAnimationSpeed(std::string name, int speed)
{
	animations_[getIndex(name)].counter = speed;
}

// Establece la velocidad de una animación
void AnimatedSprite::setAnimationSpeed(int index, int speed)
{
	animations_[index].counter = speed;
}

// Establece si la animación se reproduce en bucle
void AnimatedSprite::setAnimationLoop(std::string name, int loop)
{
	animations_[getIndex(name)].loop = loop;
}

// Establece si la animación se reproduce en bucle
void AnimatedSprite::setAnimationLoop(int index, int loop)
{
	animations_[index].loop = loop;
}

// Establece el valor de la variable
void AnimatedSprite::setAnimationCompleted(std::string name, bool value)
{
	animations_[getIndex(name)].completed = value;
}

// OLD - Establece el valor de la variable
void AnimatedSprite::setAnimationCompleted(int index, bool value)
{
	animations_[index].completed = value;
}

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

// Devuelve el rectangulo de una animación y frame concreto
SDL_Rect AnimatedSprite::getAnimationClip(std::string name, Uint8 index)
{
	return animations_[getIndex(name)].frames[index];
}

// Devuelve el rectangulo de una animación y frame concreto
SDL_Rect AnimatedSprite::getAnimationClip(int indexA, Uint8 indexF)
{
	return animations_[indexA].frames[indexF];
}

// Carga la animación desde un vector
bool AnimatedSprite::loadFromVector(std::vector<std::string> *source)
{
	// Inicializa variables
	auto frames_per_row = 0;
	auto frame_width = 0;
	auto frame_height = 0;
	auto max_tiles = 0;

	// Indicador de éxito en el proceso
	auto success = true;
	std::string line;

	// Recorre todo el vector
	auto index = 0;
	while (index < (int)source->size())
	{
		// Lee desde el vector
		line = source->at(index);

		// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
		if (line == "[animation]")
		{
			Animation buffer;
			buffer.counter = 0;
			buffer.current_frame = 0;
			buffer.completed = false;

			do
			{
				// Aumenta el indice para leer la siguiente linea
				index++;
				line = source->at(index);

				// Encuentra la posición del caracter '='
				int pos = line.find("=");

				// Procesa las dos subcadenas
				if (pos != (int)line.npos)
				{
					if (line.substr(0, pos) == "name")
					{
						buffer.name = line.substr(pos + 1, line.length());
					}

					else if (line.substr(0, pos) == "speed")
					{
						buffer.speed = std::stoi(line.substr(pos + 1, line.length()));
					}

					else if (line.substr(0, pos) == "loop")
					{
						buffer.loop = std::stoi(line.substr(pos + 1, line.length()));
					}

					else if (line.substr(0, pos) == "frames")
					{
						// Se introducen los valores separados por comas en un vector
						std::stringstream ss(line.substr(pos + 1, line.length()));
						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) > max_tiles ? 0 : std::stoi(tmp);
							rect.x = (num_tile % frames_per_row) * frame_width;
							rect.y = (num_tile / frames_per_row) * frame_height;
							buffer.frames.push_back(rect);
						}
					}

					else
					{
#ifdef VERBOSE
						std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << std::endl;
#endif
						success = false;
					}
				}
			} while (line != "[/animation]");

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

		// En caso contrario se parsea el fichero para buscar las variables y los valores
		else
		{
			// Encuentra la posición del caracter '='
			int pos = line.find("=");

			// Procesa las dos subcadenas
			if (pos != (int)line.npos)
			{
				if (line.substr(0, pos) == "frames_per_row")
				{
					frames_per_row = std::stoi(line.substr(pos + 1, line.length()));
				}

				else if (line.substr(0, pos) == "frame_width")
				{
					frame_width = std::stoi(line.substr(pos + 1, line.length()));
				}

				else if (line.substr(0, pos) == "frame_height")
				{
					frame_height = std::stoi(line.substr(pos + 1, line.length()));
				}

				else
				{
#ifdef VERBOSE
					std::cout << "Warning: unknown parameter " << line.substr(0, pos).c_str() << std::endl;
#endif
					success = false;
				}

				// Normaliza valores
				if (frames_per_row == 0 && frame_width > 0)
				{
					frames_per_row = texture_->getWidth() / frame_width;
				}

				if (max_tiles == 0 && frame_width > 0 && frame_height > 0)
				{
					const int w = texture_->getWidth() / frame_width;
					const int h = texture_->getHeight() / frame_height;
					max_tiles = w * h;
				}
			}
		}

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

	// Pone un valor por defecto
	setRect({0, 0, frame_width, frame_height});

	return success;
}

// Establece la animacion actual
void AnimatedSprite::setCurrentAnimation(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()
{
	if (enabled_)
	{
		animate();
		MovingSprite::update();
	}
}

// Establece el rectangulo para un frame de una animación
void AnimatedSprite::setAnimationFrames(Uint8 index_animation, Uint8 index_frame, int x, int y, int w, int h)
{
	animations_[index_animation].frames.push_back({x, y, w, h});
}

// OLD - Establece el contador para todas las animaciones
void AnimatedSprite::setAnimationCounter(int value)
{
	for (auto &a : animations_)
	{
		a.counter = value;
	}
}

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