#include "balloon.h"
#include <algorithm>		 // Para clamp
#include <cmath>			 // Para fabs
#include "animated_sprite.h" // Para AnimatedSprite
#include "param.h"			 // Para Param, param, ParamBalloon, ParamGame
#include "sprite.h"			 // Para Sprite
#include "texture.h"		 // Para Texture

// Constructor
Balloon::Balloon(float x, float y, BalloonType type, BalloonSize size, float vel_x, float speed, Uint16 creation_timer, std::shared_ptr<Texture> texture, const std::vector<std::string> &animation)
	: sprite_(std::make_unique<AnimatedSprite>(texture, animation)),
	  x_(x),
	  y_(y),
	  vx_(vel_x),
	  being_created_(creation_timer > 0),
	  invulnerable_(creation_timer > 0),
	  stopped_(creation_timer > 0),
	  creation_counter_(creation_timer),
	  creation_counter_ini_(creation_timer),
	  type_(type),
	  size_(size),
	  speed_(speed)
{
	switch (type_)
	{
	case BalloonType::BALLOON:
	{
		vy_ = 0;
		max_vy_ = 3.0f;

		const int index = static_cast<int>(size_);
		gravity_ = param.balloon.at(index).grav;
		default_vy_ = param.balloon.at(index).vel;
		h_ = w_ = BALLOON_SIZE[index];
		power_ = BALLOON_POWER[index];
		menace_ = BALLOON_MENACE[index];
		score_ = BALLOON_SCORE[index];

		break;
	}

	case BalloonType::FLOATER:
	{
		default_vy_ = max_vy_ = vy_ = fabs(vx_ * 2.0f);
		gravity_ = 0.00f;

		const int index = static_cast<int>(size_);
		h_ = w_ = BALLOON_SIZE[index];
		power_ = BALLOON_POWER[index];
		menace_ = BALLOON_MENACE[index];
		score_ = BALLOON_SCORE[index];

		break;
	}

	case BalloonType::POWERBALL:
	{
		const int index = 3;
		h_ = w_ = BALLOON_SIZE[index];
		power_ = score_ = menace_ = 0;

		vy_ = 0;
		max_vy_ = 3.0f;
		gravity_ = param.balloon.at(index).grav;
		default_vy_ = param.balloon.at(index).vel;

		sprite_->disableRotate();
		sprite_->setRotateSpeed(0);
		sprite_->setRotateAmount(vx_ > 0.0f ? 2.0 : -2.0);

		break;
	}

	default:
		break;
	}

	// Configura el sprite
	sprite_->setWidth(w_);
	sprite_->setHeight(h_);
	shiftSprite();

	// Alinea el circulo de colisión con el objeto
	collider_.r = w_ / 2;
	shiftColliders();

	// Establece la animación a usar
	setAnimation();
}

// Centra el globo en la posición X
void Balloon::alignTo(int x)
{
	x_ = static_cast<float>(x - (w_ / 2));
	const int min_x = param.game.play_area.rect.x;
	const int max_x = param.game.play_area.rect.w - w_;
	x_ = std::clamp(x_, static_cast<float>(min_x), static_cast<float>(max_x));
}

// Pinta el globo en la pantalla
void Balloon::render()
{
	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
	{
		if (bouncing_.enabled)
		{
			// Aplica efecto de bouncing
			sprite_->setPos(x_ + bouncing_.despX, y_ + bouncing_.despY);
			sprite_->render();
			sprite_->setPos(x_ - bouncing_.despX, y_ - bouncing_.despY);
		}
		else
			sprite_->render();
	}

	// Añade la máscara del borde a la PowerBall
	if (type_ == BalloonType::POWERBALL && !isBeingCreated())
	{
		auto sp = std::make_unique<Sprite>(sprite_->getTexture(), sprite_->getPosition());
		sp->setSpriteClip(BALLOON_SIZE[3], 0, BALLOON_SIZE[3], BALLOON_SIZE[3]);
		sp->render();
	}
}

// Actualiza la posición y estados del globo
void Balloon::move()
{
	// Comprueba si se puede mover
	if (!isStopped())
	{
		// Mueve el globo en horizontal
		x_ += vx_ * speed_;

		// Colisión en las partes laterales de la zona de juego
		const int clip = 2;
		const float min_x = param.game.play_area.rect.x - clip;
		const float max_x = param.game.play_area.rect.w - w_ + clip;
		if (x_ < min_x || x_ > max_x)
		{
			x_ = std::clamp(x_, min_x, max_x);
			vx_ = -vx_;
			// Activa el efecto de rebote o invierte la rotación
			if (type_ == BalloonType::POWERBALL)
				sprite_->switchRotate();
			else
				enableBounce();
		}

		// Mueve el globo en vertical
		y_ += vy_ * speed_;

		// Colisión en la parte superior de la zona de juego
		const int min_y = param.game.play_area.rect.y;
		if (y_ < min_y)
		{
			y_ = min_y;
			vy_ = -vy_;
			if (type_ != BalloonType::POWERBALL)
				enableBounce();
		}

		// Colisión en la parte inferior de la zona de juego
		const int max_y = param.game.play_area.rect.h - h_;
		if (y_ > max_y)
		{
			y_ = max_y;
			vy_ = -default_vy_;
			if (type_ != BalloonType::POWERBALL)
				enableBounce();
		}

		/*

		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
			vy_ += gravity_;
		}
	}
}

// Deshabilita el globo
void Balloon::disable() { enabled_ = false; }

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

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

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

		if (creation_counter_ > 0)
		{
			// Desplaza lentamente el globo hacia abajo y hacia un lado
			if (creation_counter_ % 10 == 0)
			{
				y_++;
				x_ += vx_;

				// Comprueba no se salga por los laterales
				const int min_x = param.game.play_area.rect.x;
				const int max_x = param.game.play_area.rect.w - w_;

				if (x_ < min_x || x_ > max_x)
				{
					// Corrige y cambia el sentido de la velocidad
					x_ -= vx_;
					vx_ = -vx_;
				}
			}
			--creation_counter_;
		}

		else
		{
			// El contador ha llegado a cero
			being_created_ = false;
			start();
			setInvulnerable(false);
			setAnimation();
		}
	}
}

// Establece la animación correspondiente al estado
void Balloon::setAnimation()
{
	std::string creating_animation;
	std::string normal_animation;

	switch (type_)
	{
	case BalloonType::POWERBALL:
		creating_animation = "powerball";
		normal_animation = "powerball";
		break;
	case BalloonType::FLOATER:
		creating_animation = "red";
		normal_animation = "green";
		break;
	default:
		creating_animation = "blue";
		normal_animation = "orange";
		break;
	}

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

// Detiene el globo
void Balloon::stop()
{
	stopped_ = true;
	if (type_ == BalloonType::POWERBALL)
		sprite_->disableRotate();
}

// Pone el globo en movimiento
void Balloon::start()
{
	stopped_ = false;
	if (type_ == BalloonType::POWERBALL)
		sprite_->enableRotate();
}

// Alinea el circulo de colisión con la posición del objeto globo
void Balloon::shiftColliders()
{
	collider_.x = static_cast<int>(x_) + collider_.r;
	collider_.y = static_cast<int>(y_) + collider_.r;
}

// Alinea el sprite con la posición del objeto globo
void Balloon::shiftSprite()
{
	sprite_->setPosX(x_);
	sprite_->setPosY(y_);
}

// Establece el nivel de zoom del sprite
void Balloon::zoomSprite()
{
	sprite_->setZoomW(bouncing_.zoomW);
	sprite_->setZoomH(bouncing_.zoomH);
}

// Activa el efecto
void Balloon::enableBounce()
{
	bouncing_.enabled = true;
	bouncing_.reset();
	zoomSprite();
}

// Detiene el efecto
void Balloon::disableBounce()
{
	bouncing_.enabled = false;
	bouncing_.reset();
	zoomSprite();
}

// Aplica el efecto
void Balloon::updateBounce()
{
	if (bouncing_.enabled)
	{
		const int index = bouncing_.counter / bouncing_.speed;
		bouncing_.zoomW = bouncing_.w[index];
		bouncing_.zoomH = bouncing_.h[index];

		zoomSprite();

		const auto spriteClip = sprite_->getSpriteClip();
		bouncing_.despX = spriteClip.w * (1.0f - bouncing_.zoomW);
		bouncing_.despY = spriteClip.h * (1.0f - bouncing_.zoomH);

		if (++bouncing_.counter / bouncing_.speed >= MAX_BOUNCE)
			disableBounce();
	}
}

// Pone el color alternativo en el globo
void Balloon::useReverseColor()
{
	if (!isBeingCreated())
	{
		use_reversed_colors_ = true;
		setAnimation();
	}
}

// Pone el color normal en el globo
void Balloon::useNormalColor()
{
	use_reversed_colors_ = false;
	setAnimation();
}

// Getters
float Balloon::getPosX() const { return x_; }
float Balloon::getPosY() const { return y_; }
int Balloon::getWidth() const { return w_; }
int Balloon::getHeight() const { return h_; }
BalloonSize Balloon::getSize() const { return size_; }
BalloonType Balloon::getType() const { return type_; }
Uint16 Balloon::getScore() const { return score_; }
Circle &Balloon::getCollider() { return collider_; }
Uint8 Balloon::getMenace() const { return isEnabled() ? menace_ : 0; }
Uint8 Balloon::getPower() const { return power_; }
bool Balloon::isStopped() const { return stopped_; }
bool Balloon::isInvulnerable() const { return invulnerable_; }
bool Balloon::isBeingCreated() const { return being_created_; }
bool Balloon::isEnabled() const { return enabled_; }
bool Balloon::isUsingReversedColor() { return use_reversed_colors_; }
bool Balloon::canBePopped() const { return !isBeingCreated(); }

// Setters
void Balloon::setVelY(float vel_y) { vy_ = vel_y; }
void Balloon::setSpeed(float speed) { speed_ = speed; }
void Balloon::setInvulnerable(bool value) { invulnerable_ = value; }