#include "player.h"
#include <SDL2/SDL_render.h>	  // Para SDL_FLIP_HORIZONTAL, SDL_FLIP_NONE
#include <SDL2/SDL_timer.h>		  // Para SDL_GetTicks
#include <stdlib.h>				  // Para rand
#include <algorithm>			  // Para max, min
#include "animated_sprite.h"	  // Para AnimatedSprite
#include "input.h"				  // Para InputType
#include "manage_hiscore_table.h" // Para HiScoreEntry
#include "options.h"			  // Para Options, OptionsGame, options
#include "param.h"				  // Para Param, ParamGame, param
#include "scoreboard.h"			  // Para Scoreboard, ScoreboardMode
#include "texture.h"			  // Para Texture

// Constructor
Player::Player(int id, float x, int y, bool demo, SDL_Rect &play_area, std::vector<std::shared_ptr<Texture>> texture, const std::vector<std::vector<std::string>> &animations)
	: player_sprite_(std::make_unique<AnimatedSprite>(texture[0], animations[0])),
	  power_sprite_(std::make_unique<AnimatedSprite>(texture[1], animations[1])),
	  enter_name_(std::make_unique<EnterName>()),
	  id_(id),
	  play_area_(play_area),
	  default_pos_x_(x),
	  default_pos_y_(y),
	  demo_(demo)
{
	// Configura objetos
	player_sprite_->getTexture()->setPalette(coffees_);
	power_sprite_->getTexture()->setAlpha(224);
	power_up_desp_x_ = (power_sprite_->getWidth() - player_sprite_->getWidth()) / 2;
	power_sprite_->setPosY(y - (power_sprite_->getHeight() - player_sprite_->getHeight()));

	// Inicializa variables
	setRecordName(enter_name_->getName());
	init();
}

// Iniciador
void Player::init()
{
	// Inicializa variables de estado
	pos_x_ = default_pos_x_;
	pos_y_ = default_pos_y_;
	walking_state_ = PlayerState::WALKING_STOP;
	firing_state_ = PlayerState::FIRING_NONE;
	playing_state_ = PlayerState::WAITING;
	invulnerable_ = true;
	invulnerable_counter_ = INVULNERABLE_COUNTER_;
	power_up_ = false;
	power_up_counter_ = POWERUP_COUNTER_;
	extra_hit_ = false;
	coffees_ = 0;
	continue_ticks_ = 0;
	continue_counter_ = 10;
	enter_name_ticks_ = 0;
	enter_name_counter_ = param.game.enter_name_seconds;
	shiftColliders();
	vel_x_ = 0;
	vel_y_ = 0;
	score_ = 0;
	score_multiplier_ = 1.0f;
	cooldown_ = 10;

	// Establece la posición del sprite
	player_sprite_->clear();
	shiftSprite();

	// Selecciona un frame para pintar
	player_sprite_->setCurrentAnimation("stand");
}

// Actua en consecuencia de la entrada recibida
void Player::setInput(InputType input)
{
	switch (playing_state_)
	{
	case PlayerState::PLAYING:
	{
		setInputPlaying(input);
		break;
	}
	case PlayerState::ENTERING_NAME:
	case PlayerState::ENTERING_NAME_GAME_COMPLETED:
	{
		setInputEnteringName(input);
		break;
	}
	default:
		break;
	}
}

// Procesa inputs para cuando está jugando
void Player::setInputPlaying(InputType input)
{
	switch (input)
	{
	case InputType::LEFT:
	{
		vel_x_ = -BASE_SPEED_;
		setWalkingState(PlayerState::WALKING_LEFT);
		break;
	}
	case InputType::RIGHT:
	{
		vel_x_ = BASE_SPEED_;
		setWalkingState(PlayerState::WALKING_RIGHT);
		break;
	}
	case InputType::FIRE_CENTER:
	{
		setFiringState(PlayerState::FIRING_UP);
		break;
	}
	case InputType::FIRE_LEFT:
	{
		setFiringState(PlayerState::FIRING_LEFT);
		break;
	}
	case InputType::FIRE_RIGHT:
	{
		setFiringState(PlayerState::FIRING_RIGHT);
		break;
	}
	default:
	{
		vel_x_ = 0;
		setWalkingState(PlayerState::WALKING_STOP);
		break;
	}
	}
}

// Procesa inputs para cuando está introduciendo el nombre
void Player::setInputEnteringName(InputType input)
{
	switch (input)
	{
	case InputType::LEFT:
		enter_name_->decPosition();
		break;
	case InputType::RIGHT:
		enter_name_->incPosition();
		break;
	case InputType::UP:
		enter_name_->incIndex();
		break;
	case InputType::DOWN:
		enter_name_->decIndex();
		break;
	case InputType::START:
		setRecordName(enter_name_->getName());
		break;
	default:
		break;
	}
	setRecordName(enter_name_->getName());
}

// Mueve el jugador a la posición y animación que le corresponde
void Player::move()
{
	switch (playing_state_)
	{
	case PlayerState::PLAYING:
	{
		// Mueve el jugador a derecha o izquierda
		pos_x_ += vel_x_;

		// Si el jugador abandona el area de juego por los laterales, restaura su posición
		const float min_x = param.game.play_area.rect.x - 5;
		const float max_x = play_area_.w + 5 - WIDTH_;
		pos_x_ = std::clamp(pos_x_, min_x, max_x);

		shiftSprite();
		break;
	}
	case PlayerState::DYING:
	{
		// Si el cadaver abandona el area de juego por los laterales lo hace rebotar
		if ((player_sprite_->getPosX() < param.game.play_area.rect.x) || (player_sprite_->getPosX() + WIDTH_ > play_area_.w))
			player_sprite_->setVelX(-player_sprite_->getVelX());

		// Si el cadaver abandona el area de juego por abajo
		if (player_sprite_->getPosY() > param.game.play_area.rect.h)
			setPlayingState(PlayerState::DIED);
		break;
	}
	case PlayerState::GAME_COMPLETED:
	{
		switch (id_)
		{
		case 1:
			setInputPlaying(InputType::LEFT);
			break;
		case 2:
			setInputPlaying(InputType::RIGHT);
			break;
		default:
			break;
		}
		pos_x_ += vel_x_;
		const float min_x = -WIDTH_;
		const float max_x = play_area_.w;
		pos_x_ = std::clamp(pos_x_, min_x, max_x);
		shiftSprite();

		if (pos_x_ == min_x || pos_x_ == max_x)
			setPlayingState(PlayerState::GAME_OVER);
		break;
	}
	default:
		break;
	}
}

// Pinta el jugador en pantalla
void Player::render()
{
	if (power_up_ && isPlaying())
		if (power_up_counter_ > (POWERUP_COUNTER_ / 4) || power_up_counter_ % 20 > 4)
			power_sprite_->render();

	if (isRenderable())
		player_sprite_->render();
}

// Establece la animación correspondiente al estado
void Player::setAnimation()
{
	// Crea cadenas de texto para componer el nombre de la animación
	const std::string a_walking = walking_state_ == PlayerState::WALKING_STOP ? "stand" : "walk";
	const std::string a_firing = firing_state_ == PlayerState::FIRING_UP ? "centershoot" : "sideshoot";
	const std::string a_cooling = firing_state_ == PlayerState::COOLING_UP ? "centershoot" : "sideshoot";

	const SDL_RendererFlip flip_walk = walking_state_ == PlayerState::WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
	const SDL_RendererFlip flip_fire = firing_state_ == PlayerState::FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
	const SDL_RendererFlip flip_cooling = firing_state_ == PlayerState::COOLING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;

	// Establece la animación a partir de las cadenas
	if (isPlaying() || isEnteringNameGameCompleted() || isGameCompleted())
	{
		if (firing_state_ == PlayerState::FIRING_NONE)
		{ // No esta disparando
			player_sprite_->setCurrentAnimation(a_walking);
			player_sprite_->setFlip(flip_walk);
		}
		else if (isCooling())
		{ // Acaba de disparar
			player_sprite_->setCurrentAnimation(a_walking + "-" + a_cooling + "-cooldown");
			player_sprite_->setFlip(flip_cooling);
		}
		else
		{ // Está disparando
			player_sprite_->setCurrentAnimation(a_walking + "-" + a_firing);
			// Si dispara de lado, invierte el sprite segun hacia donde dispara
			// Si dispara recto, invierte el sprite segun hacia donde camina
			player_sprite_->setFlip(a_firing == "centershoot" ? flip_walk : flip_fire);
		}
	}
	else if (isDying() || hasDied())
	{
		player_sprite_->setCurrentAnimation("death");
	}
	else if (isCelebrating())
	{
		player_sprite_->setCurrentAnimation("celebration");
	}

	// Actualiza las animaciones de los sprites
	player_sprite_->update(); // Hace avanzar las animaciones y mueve el cadaver del jugador
	power_sprite_->update();
}

// Actualiza el valor de la variable
void Player::updateCooldown()
{
	if (cooldown_ > 0)
	{
		cooldown_ -= power_up_ ? 2 : 1;
	}
	else
	{
		if (!isCooling())
		{
			cooling_status_counter_ = 40;
			switch (firing_state_)
			{
			case PlayerState::FIRING_LEFT:
				firing_state_ = PlayerState::COOLING_LEFT;
				break;
			case PlayerState::FIRING_RIGHT:
				firing_state_ = PlayerState::COOLING_RIGHT;
				break;
			case PlayerState::FIRING_UP:
				firing_state_ = PlayerState::COOLING_UP;
				break;
			default:
				break;
			}
		}
		else if (cooling_status_counter_ > 0)
		{
			--cooling_status_counter_;
		}
		else
		{
			setFiringState(PlayerState::FIRING_NONE);
		}
	}
}

// Actualiza al jugador a su posicion, animación y controla los contadores
void Player::update()
{
	move();
	setAnimation();
	shiftColliders();
	updateCooldown();
	updatePowerUp();
	updateInvulnerable();
	updateContinueCounter();
	updateEnterNameCounter();
	updateScoreboard();
}

// Incrementa la puntuación del jugador
void Player::addScore(int score)
{
	if (isPlaying())
	{
		score_ += score;
	}
}

// Actualiza el panel del marcador
void Player::updateScoreboard()
{
	switch (playing_state_)
	{
	case PlayerState::CONTINUE:
	{
		Scoreboard::get()->setContinue(getScoreBoardPanel(), getContinueCounter());
		break;
	}
	case PlayerState::ENTERING_NAME:
	case PlayerState::ENTERING_NAME_GAME_COMPLETED:
	{
		Scoreboard::get()->setRecordName(getScoreBoardPanel(), getRecordName());
		Scoreboard::get()->setSelectorPos(getScoreBoardPanel(), getRecordNamePos());
		break;
	}
	default:
		break;
	}
}

// Cambia el modo del marcador
void Player::setScoreboardMode(ScoreboardMode mode)
{
	if (!demo_)
	{
		Scoreboard::get()->setMode(getScoreBoardPanel(), mode);
	}
}

// Establece el estado del jugador en el juego
void Player::setPlayingState(PlayerState state)
{
	playing_state_ = state;

	switch (playing_state_)
	{
	case PlayerState::PLAYING:
	{
		init();
		playing_state_ = PlayerState::PLAYING;
		setScoreboardMode(ScoreboardMode::SCORE);
		break;
	}
	case PlayerState::CONTINUE:
	{
		// Inicializa el contador de continuar
		continue_ticks_ = SDL_GetTicks();
		continue_counter_ = 9;
		enter_name_->init();
		setScoreboardMode(ScoreboardMode::CONTINUE);
		break;
	}
	case PlayerState::WAITING:
	{
		setScoreboardMode(ScoreboardMode::WAITING);
		break;
	}
	case PlayerState::ENTERING_NAME:
	{
		setScoreboardMode(ScoreboardMode::ENTER_NAME);
		break;
	}
	case PlayerState::DYING:
	{
		// Activa la animación de morir
		player_sprite_->setAccelY(0.2f);
		player_sprite_->setVelY(-6.6f);
		rand() % 2 == 0 ? player_sprite_->setVelX(3.3f) : player_sprite_->setVelX(-3.3f);
		break;
	}
	case PlayerState::DIED:
	{
		const auto nextPlayerStatus = IsEligibleForHighScore() ? PlayerState::ENTERING_NAME : PlayerState::CONTINUE;
		demo_ ? setPlayingState(PlayerState::WAITING) : setPlayingState(nextPlayerStatus);
		break;
	}
	case PlayerState::GAME_OVER:
	{
		setScoreboardMode(ScoreboardMode::GAME_OVER);
		break;
	}
	case PlayerState::CELEBRATING:
	{
		setScoreboardMode(ScoreboardMode::SCORE);
		break;
	}
	case PlayerState::ENTERING_NAME_GAME_COMPLETED:
	{
		setWalkingState(PlayerState::WALKING_STOP);
		setFiringState(PlayerState::FIRING_NONE);
		setScoreboardMode(ScoreboardMode::ENTER_NAME);
		break;
	}
	case PlayerState::GAME_COMPLETED:
	{
		setScoreboardMode(ScoreboardMode::GAME_COMPLETED);
		break;
	}
	default:
		break;
	}
}

// Aumenta el valor de la variable hasta un máximo
void Player::incScoreMultiplier()
{
	score_multiplier_ += 0.1f;
	score_multiplier_ = std::min(score_multiplier_, 5.0f);
}

// Decrementa el valor de la variable hasta un mínimo
void Player::decScoreMultiplier()
{
	score_multiplier_ -= 0.1f;
	score_multiplier_ = std::max(score_multiplier_, 1.0f);
}

// Establece el valor del estado
void Player::setInvulnerable(bool value)
{
	invulnerable_ = value;
	invulnerable_counter_ = invulnerable_ ? INVULNERABLE_COUNTER_ : 0;
}

// Monitoriza el estado
void Player::updateInvulnerable()
{
	if (invulnerable_)
	{
		if (invulnerable_counter_ > 0)
		{
			--invulnerable_counter_;
			invulnerable_counter_ % 8 > 3 ? player_sprite_->getTexture()->setPalette(coffees_) : player_sprite_->getTexture()->setPalette(3);
		}
		else
		{
			setInvulnerable(false);
			player_sprite_->getTexture()->setPalette(coffees_);
		}
	}
}

// Establece el valor de la variable
void Player::setPowerUp()
{
	power_up_ = true;
	power_up_counter_ = POWERUP_COUNTER_;
}

// Actualiza el valor de la variable
void Player::updatePowerUp()
{
	if (power_up_)
	{
		--power_up_counter_;
		power_up_ = power_up_counter_ > 0;
	}
}

// Concede un toque extra al jugador
void Player::giveExtraHit()
{
	extra_hit_ = true;
	if (coffees_ < 2)
	{
		coffees_++;
		player_sprite_->getTexture()->setPalette(coffees_);
	}
}

// Quita el toque extra al jugador
void Player::removeExtraHit()
{
	if (coffees_ > 0)
	{
		coffees_--;
		setInvulnerable(true);
		player_sprite_->getTexture()->setPalette(coffees_);
	}

	extra_hit_ = coffees_ == 0 ? false : true;
}

// Actualiza el circulo de colisión a la posición del jugador
void Player::shiftColliders()
{
	collider_.x = static_cast<int>(pos_x_ + (WIDTH_ / 2));
	collider_.y = static_cast<int>(pos_y_ + (HEIGHT_ / 2));
}

// Pone las texturas del jugador
void Player::setPlayerTextures(const std::vector<std::shared_ptr<Texture>> &texture)
{
	player_sprite_->setTexture(texture[0]);
	power_sprite_->setTexture(texture[1]);
}

// Actualiza el contador de continue
void Player::updateContinueCounter()
{
	if (playing_state_ == PlayerState::CONTINUE)
	{
		constexpr int TICKS_SPEED = 1000;
		if (SDL_GetTicks() - continue_ticks_ > TICKS_SPEED)
		{
			decContinueCounter();
		}
	}
}

// Actualiza el contador de entrar nombre
void Player::updateEnterNameCounter()
{
	if (playing_state_ == PlayerState::ENTERING_NAME || playing_state_ == PlayerState::ENTERING_NAME_GAME_COMPLETED)
	{
		constexpr int TICKS_SPEED = 1000;
		if (SDL_GetTicks() - enter_name_ticks_ > TICKS_SPEED)
		{
			decEnterNameCounter();
		}
	}
}

// Decrementa el contador de continuar
void Player::decContinueCounter()
{
	continue_ticks_ = SDL_GetTicks();
	--continue_counter_;
	if (continue_counter_ < 0)
	{
		setPlayingState(PlayerState::GAME_OVER);
	}
}

// Decrementa el contador de entrar nombre
void Player::decEnterNameCounter()
{
	enter_name_ticks_ = SDL_GetTicks();
	--enter_name_counter_;
	if (enter_name_counter_ < 0)
	{
		enter_name_counter_ = param.game.enter_name_seconds;
		if (playing_state_ == PlayerState::ENTERING_NAME)
			setPlayingState(PlayerState::CONTINUE);
		else
			setPlayingState(PlayerState::GAME_COMPLETED);
	}
}

// Obtiene la posición que se está editando del nombre del jugador para la tabla de mejores puntuaciones
int Player::getRecordNamePos() const
{
	if (enter_name_)
		return enter_name_->getPosition();

	return 0;
}

// Recoloca los sprites
void Player::shiftSprite()
{
	player_sprite_->setPosX(pos_x_);
	player_sprite_->setPosY(pos_y_);
	power_sprite_->setPosX(getPosX() - power_up_desp_x_);
}

int Player::getScore() const { return score_; }
void Player::setScore(int score) { score_ = score; }
PlayerState Player::getPlayingState() const { return playing_state_; }
float Player::getScoreMultiplier() const { return score_multiplier_; }
void Player::setScoreMultiplier(float value) { score_multiplier_ = value; }
bool Player::isInvulnerable() const { return invulnerable_; }
int Player::getInvulnerableCounter() const { return invulnerable_counter_; }
void Player::setInvulnerableCounter(int value) { invulnerable_counter_ = value; }
bool Player::isPowerUp() const { return power_up_; }
int Player::getPowerUpCounter() const { return power_up_counter_; }
void Player::setPowerUpCounter(int value) { power_up_counter_ = value; }
bool Player::hasExtraHit() const { return extra_hit_; }
void Player::setScoreBoardPanel(int panel) { scoreboard_panel_ = panel; }
int Player::getScoreBoardPanel() const { return scoreboard_panel_; }
int Player::getCoffees() const { return coffees_; }
Circle &Player::getCollider() { return collider_; }
int Player::getContinueCounter() const { return continue_counter_; }
void Player::setName(const std::string &name) { name_ = name; }
void Player::setRecordName(const std::string &record_name) { record_name_ = record_name.substr(0, 8); }
std::string Player::getName() const { return name_; }
std::string Player::getRecordName() const { return record_name_; }
void Player::setController(int index) { controller_index_ = index; }
int Player::getController() const { return controller_index_; }
int Player::getId() const { return id_; }
bool Player::isRenderable() const { return isPlaying() || isDying() || isCelebrating() || isEnteringNameGameCompleted() || isGameCompleted(); }
bool Player::IsEligibleForHighScore() { return score_ > options.game.hi_score_table.back().score; }
bool Player::isCooling() { return firing_state_ == PlayerState::COOLING_LEFT || firing_state_ == PlayerState::COOLING_UP || firing_state_ == PlayerState::COOLING_RIGHT; }
int Player::getPosX() const { return static_cast<int>(pos_x_); }
int Player::getPosY() const { return pos_y_; }
int Player::getWidth() const { return WIDTH_; }
int Player::getHeight() const { return HEIGHT_; }
bool Player::canFire() const { return cooldown_ > 0 ? false : true; }
void Player::setFireCooldown(int time) { cooldown_ = time; }
void Player::setWalkingState(PlayerState state) { walking_state_ = state; }
void Player::setFiringState(PlayerState state) { firing_state_ = state; }
bool Player::isPlaying() const { return playing_state_ == PlayerState::PLAYING; }
bool Player::isContinue() const { return playing_state_ == PlayerState::CONTINUE; }
bool Player::isWaiting() const { return playing_state_ == PlayerState::WAITING; }
bool Player::isEnteringName() const { return playing_state_ == PlayerState::ENTERING_NAME; }
bool Player::isDying() const { return playing_state_ == PlayerState::DYING; }
bool Player::hasDied() const { return playing_state_ == PlayerState::DIED; }
bool Player::isGameOver() const { return playing_state_ == PlayerState::GAME_OVER; }
bool Player::isEnteringNameGameCompleted() const { return playing_state_ == PlayerState::ENTERING_NAME_GAME_COMPLETED; }
bool Player::isGameCompleted() const { return playing_state_ == PlayerState::GAME_COMPLETED; }
bool Player::isCelebrating() const { return playing_state_ == PlayerState::CELEBRATING; }