#include "balloon.h"
#include <math.h>			 // for abs
#include "animated_sprite.h" // for AnimatedSprite
#include "moving_sprite.h"	 // for MovingSprite
#include "param.h"			 // for param
#include "sprite.h"			 // for Sprite
#include "texture.h"		 // for Texture

// Constructor
Balloon::Balloon(float x, float y, Uint8 kind, float vel_x, float speed, Uint16 creation_timer, std::shared_ptr<Texture> texture, std::vector<std::string> *animation)
	: kind_(kind), speed_(speed)
{
	sprite_ = std::make_unique<AnimatedSprite>(texture, "", animation);

	enabled_ = true;

	switch (kind_)
	{
	case BALLOON_1:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_1;
		height_ = BALLOON_WIDTH_1;
		size_ = BALLOON_SIZE_1;
		power_ = 1;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = 0;
		max_vel_y_ = 3.0f;
		gravity_ = param.balloon_1.grav;
		default_vel_y_ = param.balloon_1.vel;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_1;

		// Amenaza que genera el globo
		menace_ = 1;

		break;

	case BALLOON_2:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_2;
		height_ = BALLOON_WIDTH_2;
		size_ = BALLOON_SIZE_2;
		power_ = 3;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = 0;
		max_vel_y_ = 3.0f;
		gravity_ = param.balloon_2.grav;
		default_vel_y_ = param.balloon_2.vel;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_2;

		// Amenaza que genera el globo
		menace_ = 2;

		break;

	case BALLOON_3:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_3;
		height_ = BALLOON_WIDTH_3;
		size_ = BALLOON_SIZE_3;
		power_ = 7;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = 0;
		max_vel_y_ = 3.0f;
		gravity_ = param.balloon_3.grav;
		default_vel_y_ = param.balloon_3.vel;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_3;

		// Amenaza que genera el globo
		menace_ = 4;

		break;

	case BALLOON_4:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_4;
		height_ = BALLOON_WIDTH_4;
		size_ = BALLOON_SIZE_4;
		power_ = 15;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = 0;
		max_vel_y_ = 3.0f;
		gravity_ = param.balloon_4.grav;
		default_vel_y_ = param.balloon_4.vel;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_4;

		// Amenaza que genera el globo
		menace_ = 8;

		break;

	case HEXAGON_1:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_1;
		height_ = BALLOON_WIDTH_1;
		size_ = BALLOON_SIZE_1;
		power_ = 1;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = abs(vel_x) * 2;
		max_vel_y_ = abs(vel_x) * 2;
		gravity_ = 0.00f;
		default_vel_y_ = abs(vel_x) * 2;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_1;

		// Amenaza que genera el globo
		menace_ = 1;

		break;

	case HEXAGON_2:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_2;
		height_ = BALLOON_WIDTH_2;
		size_ = BALLOON_SIZE_2;
		power_ = 3;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = abs(vel_x) * 2;
		max_vel_y_ = abs(vel_x) * 2;
		gravity_ = 0.00f;
		default_vel_y_ = abs(vel_x) * 2;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_2;

		// Amenaza que genera el globo
		menace_ = 2;

		break;

	case HEXAGON_3:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_3;
		height_ = BALLOON_WIDTH_3;
		size_ = BALLOON_SIZE_3;
		power_ = 7;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = abs(vel_x) * 2;
		max_vel_y_ = abs(vel_x) * 2;
		gravity_ = 0.00f;
		default_vel_y_ = abs(vel_x) * 2;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_3;

		// Amenaza que genera el globo
		menace_ = 4;

		break;

	case HEXAGON_4:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_4;
		height_ = BALLOON_WIDTH_4;
		size_ = BALLOON_SIZE_4;
		power_ = 15;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = abs(vel_x) * 2;
		max_vel_y_ = abs(vel_x) * 2;
		gravity_ = 0.00f;
		default_vel_y_ = abs(vel_x) * 2;

		// Puntos que da el globo al ser destruido
		score_ = BALLOON_SCORE_4;

		// Amenaza que genera el globo
		menace_ = 8;

		break;

	case POWER_BALL:
		// Alto y ancho del objeto
		width_ = BALLOON_WIDTH_4;
		height_ = BALLOON_WIDTH_4;
		size_ = 4;
		power_ = 0;

		// Inicializa los valores de velocidad y gravedad
		vel_x_ = vel_x;
		vel_y_ = 0;
		max_vel_y_ = 3.0f;
		gravity_ = param.balloon_4.grav;
		default_vel_y_ = param.balloon_4.vel;

		// Puntos que da el globo al ser destruido
		score_ = 0;

		// Amenaza que genera el globo
		menace_ = 0;

		// Añade rotación al sprite_
		sprite_->setRotate(false);
		sprite_->setRotateSpeed(0);
		vel_x_ > 0.0f ? sprite_->setRotateAmount(2.0) : sprite_->setRotateAmount(-2.0);

		break;

	default:
		break;
	}

	// Posición inicial
	pos_x_ = x;
	pos_y_ = y;

	// Valores para el efecto de rebote
	bouncing_.enabled = false;
	bouncing_.counter = 0;
	bouncing_.speed = 2;
	bouncing_.zoomW = 1.0f;
	bouncing_.zoomH = 1.0f;
	bouncing_.despX = 0.0f;
	bouncing_.despY = 0.0f;
	bouncing_.w = {1.10f, 1.05f, 1.00f, 0.95f, 0.90f, 0.95f, 1.00f, 1.02f, 1.05f, 1.02f};
	bouncing_.h = {0.90f, 0.95f, 1.00f, 1.05f, 1.10f, 1.05f, 1.00f, 0.98f, 0.95f, 0.98f};

	// Alto y ancho del sprite_
	sprite_->setWidth(width_);
	sprite_->setHeight(height_);

	// Posición X,Y del sprite_
	sprite_->setPosX((int)pos_x_);
	sprite_->setPosY((int)pos_y_);

	// Tamaño del circulo de colisión
	collider_.r = width_ / 2;

	// Alinea el circulo de colisión con el objeto
	updateColliders();

	// Inicializa variables
	stopped_ = true;
	stopped_counter_ = 0;
	blinking_ = false;
	visible_ = true;
	invulnerable_ = true;
	being_created_ = true;
	creation_counter_ = creation_timer;
	creation_counter_ini_ = creation_timer;

	// Actualiza valores
	being_created_ = creation_counter_ == 0 ? false : true;
	invulnerable_ = being_created_ == false ? false : true;

	counter_ = 0;
	travel_y_ = 1.0f;
}

// Centra el globo en la posición X
void Balloon::allignTo(int x)
{
	pos_x_ = float(x - (width_ / 2));

	if (pos_x_ < param.game.play_area.rect.x)
	{
		pos_x_ = param.game.play_area.rect.x + 1;
	}
	else if ((pos_x_ + width_) > param.game.play_area.rect.w)
	{
		pos_x_ = float(param.game.play_area.rect.w - width_ - 1);
	}

	// Posición X,Y del sprite_
	sprite_->setPosX(getPosX());
	sprite_->setPosY(getPosY());

	// Alinea el circulo de colisión con el objeto
	updateColliders();
}

// Pinta el globo en la pantalla
void Balloon::render()
{
	if (visible_ && enabled_)
	{
		if (bouncing_.enabled)
		{
			if (kind_ != POWER_BALL)
			{
				// Aplica desplazamiento para el zoom
				sprite_->setPosX(getPosX() + bouncing_.despX);
				sprite_->setPosY(getPosY() + bouncing_.despY);
				sprite_->render();
				sprite_->setPosX(getPosX() - bouncing_.despX);
				sprite_->setPosY(getPosY() - bouncing_.despY);
			}
		}
		else if (isBeingCreated())
		{
			// Aplica alpha blending
			sprite_->getTexture()->setAlpha(255 - (int)((float)creation_counter_ * (255.0f / (float)creation_counter_ini_)));
			sprite_->render();
			sprite_->getTexture()->setAlpha(255);
		}
		else
		{
			sprite_->render();
		}

		if (kind_ == POWER_BALL && !isBeingCreated())
		{
			auto sp = std::make_unique<Sprite>(sprite_->getRect(), sprite_->getTexture());
			sp->setSpriteClip(BALLOON_WIDTH_4, 0, BALLOON_WIDTH_4, BALLOON_WIDTH_4);
			sp->render();
		}
	}
}

// Actualiza la posición y estados del globo
void Balloon::move()
{
	// Comprueba si se puede mover
	if (!isStopped())
	{
		// Lo mueve a izquierda o derecha
		pos_x_ += (vel_x_ * speed_);

		// Si queda fuera de pantalla, corregimos su posición y cambiamos su sentido
		if ((pos_x_ < param.game.play_area.rect.x) || (pos_x_ + width_ > param.game.play_area.rect.w))
		{
			// Corrige posición
			pos_x_ -= (vel_x_ * speed_);

			// Invierte sentido
			vel_x_ = -vel_x_;

			// Invierte la rotación
			sprite_->switchRotate();

			// Activa el efecto de rebote
			if (kind_ != POWER_BALL)
			{
				bounceStart();
			}
		}

		// Mueve el globo hacia arriba o hacia abajo
		pos_y_ += (vel_y_ * speed_);

		// Si se sale por arriba
		if (pos_y_ < param.game.play_area.rect.y)
		{
			// Corrige
			pos_y_ = param.game.play_area.rect.y;

			// Invierte sentido
			vel_y_ = -vel_y_;

			// Activa el efecto de rebote
			if (kind_ != POWER_BALL)
			{
				bounceStart();
			}
		}

		// Si el globo se sale por la parte inferior
		if (pos_y_ + height_ > param.game.play_area.rect.h)
		{
			// Corrige
			pos_y_ = param.game.play_area.rect.h - height_;

			// Invierte colocando una velocidad por defecto
			vel_y_ = -default_vel_y_;

			// Activa el efecto de rebote
			if (kind_ != POWER_BALL)
			{
				bounceStart();
			}
		}

		/*

		Para aplicar la gravedad, el diseño original la aplicaba en cada iteración del bucle
		Al añadir el modificador de velocidad se reduce la distancia que recorre el objeto y por
		tanto recibe mas gravedad. Para solucionarlo se va a aplicar la gravedad cuando se haya
		recorrido una distancia igual a la velocidad en Y, que era el cálculo inicial

		*/

		// Incrementa la variable que calcula la distancia acumulada en Y
		travel_y_ += speed_;

		// Si la distancia acumulada en Y es igual a la velocidad, se aplica la gravedad
		if (travel_y_ >= 1.0f)
		{
			// Quita el excedente
			travel_y_ -= 1.0f;

			// Aplica la gravedad al objeto sin pasarse de una velocidad máxima
			vel_y_ += gravity_;

			// Al parecer esta asignación se quedó sin hacer y ahora el juego no funciona
			// correctamente si se aplica, así que se deja sin efecto
			// vel_y_ = std::min(vel_y_, max_vel_y_);
		}

		// Actualiza la posición del sprite_
		sprite_->setPosX(getPosX());
		sprite_->setPosY(getPosY());
	}
}

// Deshabilita el globo y pone a cero todos los valores
void Balloon::disable()
{
	being_created_ = false;
	blinking_ = false;
	collider_.r = 0;
	collider_.x = 0;
	collider_.y = 0;
	counter_ = 0;
	creation_counter_ = 0;
	creation_counter_ini_ = 0;
	default_vel_y_ = 0.0f;
	enabled_ = false;
	gravity_ = 0.0f;
	height_ = 0;
	invulnerable_ = false;
	kind_ = 0;
	max_vel_y_ = 0.0f;
	menace_ = 0;
	pos_x_ = 0.0f;
	pos_y_ = 0.0f;
	power_ = 0;
	score_ = 0;
	size_ = 0;
	speed_ = 0;
	stopped_ = false;
	stopped_counter_ = 0;
	travel_y_ = 0;
	vel_x_ = 0.0f;
	vel_y_ = 0.0f;
	visible_ = false;
	width_ = 0;
	sprite_->clear();
}

// Explosiona el globo
void Balloon::pop()
{
	disable();
}

// Actualiza al globo a su posicion, animación y controla los contadores
void Balloon::update()
{
	if (enabled_)
	{
		sprite_->MovingSprite::update();
		move();
		updateAnimation();
		updateColliders();
		updateState();
		updateBounce();
		counter_++;
	}
}

// Actualiza los estados del globo
void Balloon::updateState()
{
	// Si se está creando
	if (isBeingCreated())
	{
		// Actualiza el valor de las variables
		setStop(true);
		setInvulnerable(true);

		// Todavia tiene tiempo en el contador
		if (creation_counter_ > 0)
		{
			// Desplaza lentamente el globo hacia abajo y hacia un lado
			if (creation_counter_ % 10 == 0)
			{
				pos_y_++;
				pos_x_ += vel_x_;

				// Comprueba no se salga por los laterales
				if ((pos_x_ < param.game.play_area.rect.x) || (pos_x_ > (param.game.play_area.rect.w - width_)))
				{
					// Corrige y cambia el sentido de la velocidad
					pos_x_ -= vel_x_;
					vel_x_ = -vel_x_;
				}

				// Actualiza la posición del sprite_
				sprite_->setPosX(getPosX());
				sprite_->setPosY(getPosY());

				// Actualiza la posición del circulo de colisión
				updateColliders();
			}

			creation_counter_--;
		}
		// El contador ha llegado a cero
		else
		{
			setBeingCreated(false);
			setStop(false);
			setVisible(true);
			setInvulnerable(false);
			if (kind_ == POWER_BALL)
			{
				sprite_->setRotate(true);
			}
		}
	}
	// Solo comprueba el estado detenido cuando no se está creando
	else if (isStopped())
	{
		// Si es una powerball deja de rodar
		if (kind_ == POWER_BALL)
		{
			sprite_->setRotate(false);
		}

		// Reduce el contador
		if (stopped_counter_ > 0)
		{
			stopped_counter_--;
		}
		// Si el contador ha llegado a cero
		else
		{ // Quitarles el estado "detenido"
			setStop(false);

			// Si es una powerball vuelve a rodar
			if (kind_ == POWER_BALL)
			{
				sprite_->setRotate(true);
			}
		}
	}
}

// Establece la animación correspondiente al estado
void Balloon::updateAnimation()
{
	std::string creating_animation = "blue";
	std::string normal_animation = "orange";

	if (kind_ == POWER_BALL)
	{
		creating_animation = "powerball";
		normal_animation = "powerball";
	}
	else if (getClass() == HEXAGON_CLASS)
	{
		creating_animation = "red";
		normal_animation = "green";
	}

	// Establece el frame de animación
	if (isBeingCreated())
	{
		sprite_->setCurrentAnimation(creating_animation);
	}
	else
	{
		sprite_->setCurrentAnimation(normal_animation);
	}

	sprite_->animate();
}

// Comprueba si el globo está habilitado
bool Balloon::isEnabled() const
{
	return enabled_;
}

// Obtiene del valor de la variable
float Balloon::getPosX() const
{
	return pos_x_;
}

// Obtiene del valor de la variable
float Balloon::getPosY() const
{
	return pos_y_;
}

// Obtiene del valor de la variable
float Balloon::getVelY() const
{
	return vel_y_;
}

// Obtiene del valor de la variable
int Balloon::getWidth() const
{
	return width_;
}

// Obtiene del valor de la variable
int Balloon::getHeight() const
{
	return height_;
}

// Establece el valor de la variable
void Balloon::setVelY(float vel_y_)
{
	this->vel_y_ = vel_y_;
}

// Establece el valor de la variable
void Balloon::setSpeed(float speed_)
{
	this->speed_ = speed_;
}

// Obtiene del valor de la variable
int Balloon::getKind() const
{
	return kind_;
}

// Obtiene del valor de la variable
Uint8 Balloon::getSize() const
{
	return size_;
}

// Obtiene la clase a la que pertenece el globo
Uint8 Balloon::getClass() const
{
	if ((kind_ >= BALLOON_1) && (kind_ <= BALLOON_4))
	{
		return BALLOON_CLASS;
	}

	else if ((kind_ >= HEXAGON_1) && (kind_ <= HEXAGON_4))
	{
		return HEXAGON_CLASS;
	}

	return BALLOON_CLASS;
}

// Establece el valor de la variable
void Balloon::setStop(bool state)
{
	stopped_ = state;
}

// Obtiene del valor de la variable
bool Balloon::isStopped() const
{
	return stopped_;
}

// Establece el valor de la variable
void Balloon::setBlink(bool value)
{
	blinking_ = value;
}

// Obtiene del valor de la variable
bool Balloon::isBlinking() const
{
	return blinking_;
}

// Establece el valor de la variable
void Balloon::setVisible(bool value)
{
	visible_ = value;
}

// Obtiene del valor de la variable
bool Balloon::isVisible() const
{
	return visible_;
}

// Establece el valor de la variable
void Balloon::setInvulnerable(bool value)
{
	invulnerable_ = value;
}

// Obtiene del valor de la variable
bool Balloon::isInvulnerable() const
{
	return invulnerable_;
}

// Establece el valor de la variable
void Balloon::setBeingCreated(bool value)
{
	being_created_ = value;
}

// Obtiene del valor de la variable
bool Balloon::isBeingCreated() const
{
	return being_created_;
}

// Establece el valor de la variable
void Balloon::setStoppedTimer(Uint16 time)
{
	stopped_counter_ = time;
}

// Obtiene del valor de la variable
Uint16 Balloon::getStoppedTimer() const
{
	return stopped_counter_;
}

// Obtiene del valor de la variable
Uint16 Balloon::getScore() const
{
	return score_;
}

// Obtiene el circulo de colisión
Circle &Balloon::getCollider()
{
	return collider_;
}

// Alinea el circulo de colisión con la posición del objeto globo
void Balloon::updateColliders()
{
	collider_.x = Uint16(pos_x_ + collider_.r);
	collider_.y = pos_y_ + collider_.r;
}

// Obtiene le valor de la variable
Uint8 Balloon::getMenace() const
{
	return isEnabled() ? menace_ : 0;
}

// Obtiene le valor de la variable
Uint8 Balloon::getPower() const
{
	return power_;
}

void Balloon::bounceStart()
{
	bouncing_.enabled = true;
	bouncing_.zoomW = 1;
	bouncing_.zoomH = 1;
	sprite_->setZoomW(bouncing_.zoomW);
	sprite_->setZoomH(bouncing_.zoomH);
	bouncing_.despX = 0;
	bouncing_.despY = 0;
}

void Balloon::bounceStop()
{
	bouncing_.enabled = false;
	bouncing_.counter = 0;
	bouncing_.zoomW = 1.0f;
	bouncing_.zoomH = 1.0f;
	sprite_->setZoomW(bouncing_.zoomW);
	sprite_->setZoomH(bouncing_.zoomH);
	bouncing_.despX = 0.0f;
	bouncing_.despY = 0.0f;
}

void Balloon::updateBounce()
{
	if (bouncing_.enabled)
	{
		bouncing_.zoomW = bouncing_.w[bouncing_.counter / bouncing_.speed];
		bouncing_.zoomH = bouncing_.h[bouncing_.counter / bouncing_.speed];
		sprite_->setZoomW(bouncing_.zoomW);
		sprite_->setZoomH(bouncing_.zoomH);
		bouncing_.despX = (sprite_->getSpriteClip().w - (sprite_->getSpriteClip().w * bouncing_.zoomW));
		bouncing_.despY = (sprite_->getSpriteClip().h - (sprite_->getSpriteClip().h * bouncing_.zoomH));
		++bouncing_.counter;
		if ((bouncing_.counter / bouncing_.speed) > (MAX_BOUNCE - 1))
		{
			bounceStop();
		}
	}
}

// Indica si el globo se puede explotar
bool Balloon::canBePopped() const
{
	return isEnabled() && !isBeingCreated();
}

// Indica si el globo se puede destruir
bool Balloon::canBeDestroyed() const
{
	return isEnabled();
}