#include "player.h"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, SDL_FlipMode, SDL_FRect

#include <algorithm>  // Para clamp, max, min
#include <cstdlib>    // Para rand

#include "animated_sprite.h"       // Para AnimatedSprite
#include "asset.h"                 // Para Asset
#include "audio.h"                 // Para Audio
#include "input.h"                 // Para InputAction
#include "manage_hiscore_table.h"  // Para ManageHiScoreTable, HiScoreEntry
#include "param.h"                 // Para Param, ParamGame, param
#include "scoreboard.h"            // Para ScoreboardMode, Scoreboard
#include "stage.h"                 // Para power_can_be_added
#include "texture.h"               // Para Texture

// Constructor
Player::Player(int id, float x, int y, bool demo, SDL_FRect &play_area, std::vector<std::shared_ptr<Texture>> texture, const std::vector<std::vector<std::string>> &animations)
    : player_sprite_(std::make_unique<AnimatedSprite>(texture.at(0), animations.at(0))),
      power_sprite_(std::make_unique<AnimatedSprite>(texture.at(1), animations.at(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_x_offset_ = (power_sprite_->getWidth() - player_sprite_->getWidth()) / 2;
    power_sprite_->setPosY(y - (power_sprite_->getHeight() - player_sprite_->getHeight()));

    // Inicializa variables
    pos_x_ = default_pos_x_;
    init();
}

// Iniciador
void Player::init() {
    // Inicializa variables de estado
    pos_y_ = default_pos_y_;
    walking_state_ = State::WALKING_STOP;
    firing_state_ = State::FIRING_NONE;
    invulnerable_ = false;
    invulnerable_counter_ = 0;
    power_up_ = false;
    power_up_counter_ = POWERUP_COUNTER;
    extra_hit_ = false;
    coffees_ = 0;
    continue_ticks_ = 0;
    continue_counter_ = 10;
    name_entry_ticks_ = 0;
    name_entry_idle_counter_ = 0;
    name_entry_total_counter_ = 0;
    shiftColliders();
    vel_x_ = 0;
    vel_y_ = 0;
    score_ = 0;
    score_multiplier_ = 1.0F;
    cant_fire_counter_ = 10;
    enter_name_->init(last_enter_name_);

    // 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(Input::Action action) {
    switch (playing_state_) {
        case State::PLAYING: {
            setInputPlaying(action);
            break;
        }
        case State::ENTERING_NAME:
        case State::ENTERING_NAME_GAME_COMPLETED: {
            setInputEnteringName(action);
            break;
        }
        default:
            break;
    }
}

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

// Procesa inputs para cuando está introduciendo el nombre
void Player::setInputEnteringName(Input::Action action) {
    switch (action) {
        case Input::Action::LEFT:
            enter_name_->decPosition();
            break;
        case Input::Action::RIGHT:
            enter_name_->incPosition();
            break;
        case Input::Action::UP:
            enter_name_->incIndex();
            break;
        case Input::Action::DOWN:
            enter_name_->decIndex();
            break;
        case Input::Action::START:
            last_enter_name_ = getRecordName();
            break;
        default:
            break;
    }
    name_entry_idle_counter_ = 0;
}

// Mueve el jugador a la posición y animación que le corresponde
void Player::move() {
    switch (playing_state_) {
        case State::PLAYING:
            handlePlayingMovement();
            break;
        case State::ROLLING:
            handleRollingMovement();
            break;
        case State::TITLE_ANIMATION:
            handleTitleAnimation();
            break;
        case State::CONTINUE_TIME_OUT:
            handleContinueTimeOut();
            break;
        case State::LEAVING_SCREEN:
            handleLeavingScreen();
            break;
        case State::ENTERING_SCREEN:
            handleEnteringScreen();
            break;
        case State::CREDITS:
            handleCreditsMovement();
            break;
        case State::WAITING:
            handleWaitingMovement();
            break;
        default:
            break;
    }
}

void Player::handlePlayingMovement() {
    // 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 = play_area_.x - 5;
    const float MAX_X = play_area_.w + 5 - WIDTH;
    pos_x_ = std::clamp(pos_x_, MIN_X, MAX_X);

    shiftSprite();
}

void Player::handleRollingMovement() {
    handleRollingBoundaryCollision();
    handleRollingGroundCollision();
}

void Player::handleRollingBoundaryCollision() {
    const int X = player_sprite_->getPosX();
    const int MIN_X = play_area_.x;
    const int MAX_X = play_area_.x + play_area_.w - WIDTH;

    if ((X < MIN_X) || (X > MAX_X)) {
        player_sprite_->setPosX(std::clamp(X, MIN_X, MAX_X));
        player_sprite_->setVelX(-player_sprite_->getVelX());
        playSound("jump.wav");
    }
}

void Player::handleRollingGroundCollision() {
    if (player_sprite_->getPosY() <= play_area_.h - HEIGHT) {
        return;
    }

    // const float VEL_Y = player_sprite_->getVelY();
    // if (VEL_Y >= 0.0F && VEL_Y < 2.0F) {
    if (player_sprite_->getVelY() < 2.0F) {
        handleRollingStop();
    } else {
        handleRollingBounce();
    }
}

void Player::handleRollingStop() {
    const auto NEXT_PLAYER_STATUS = isEligibleForHighScore() ? State::ENTERING_NAME : State::CONTINUE;
    const auto NEXT_STATE = demo_ ? State::LYING_ON_THE_FLOOR_FOREVER : NEXT_PLAYER_STATUS;

    setPlayingState(NEXT_STATE);
    pos_x_ = player_sprite_->getPosX();
    pos_y_ = default_pos_y_;
    player_sprite_->clear();
    shiftSprite();
    playSound("jump.wav");
}

void Player::handleRollingBounce() {
    player_sprite_->setPosY(play_area_.h - HEIGHT);
    player_sprite_->setVelY(player_sprite_->getVelY() * -0.5F);
    player_sprite_->setVelX(player_sprite_->getVelX() * 0.75F);
    player_sprite_->setAnimationSpeed(player_sprite_->getAnimationSpeed() * 2);
    playSound("jump.wav");
}

void Player::handleTitleAnimation() {
    setInputBasedOnPlayerId();

    pos_x_ += vel_x_ * 2.0F;
    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(State::TITLE_HIDDEN);
    }
}

void Player::handleContinueTimeOut() {
    // Si el cadaver desaparece por el suelo, cambia de estado
    if (player_sprite_->getPosY() > play_area_.h) {
        player_sprite_->stop();
        setPlayingState(State::WAITING);
    }
}

void Player::handleLeavingScreen() {
    updateStepCounter();
    setInputBasedOnPlayerId();

    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(State::GAME_OVER);
    }
}

void Player::handleEnteringScreen() {
    updateStepCounter();

    if (id_ == 1) {
        handlePlayer1Entering();
    } else if (id_ == 2) {
        handlePlayer2Entering();
    }

    shiftSprite();
}

void Player::handlePlayer1Entering() {
    setInputPlaying(Input::Action::RIGHT);
    pos_x_ += vel_x_;
    if (pos_x_ > default_pos_x_) {
        pos_x_ = default_pos_x_;
        setPlayingState(State::PLAYING);
    }
}

void Player::handlePlayer2Entering() {
    setInputPlaying(Input::Action::LEFT);
    pos_x_ += vel_x_;
    if (pos_x_ < default_pos_x_) {
        pos_x_ = default_pos_x_;
        setPlayingState(State::PLAYING);
    }
}

void Player::handleCreditsMovement() {
    pos_x_ += vel_x_ / 2.0F;

    if (vel_x_ > 0) {
        handleCreditsRightMovement();
    } else {
        handleCreditsLeftMovement();
    }

    updateWalkingStateForCredits();
    shiftSprite();
}

void Player::handleCreditsRightMovement() {
    if (pos_x_ > param.game.game_area.rect.w - WIDTH) {
        pos_x_ = param.game.game_area.rect.w - WIDTH;
        vel_x_ *= -1;
    }
}

void Player::handleCreditsLeftMovement() {
    if (pos_x_ < param.game.game_area.rect.x) {
        pos_x_ = param.game.game_area.rect.x;
        vel_x_ *= -1;
    }
}

void Player::handleWaitingMovement() {
    ++waiting_counter_;
    if (waiting_counter_ == WAITING_COUNTER) {
        waiting_counter_ = 0;
        player_sprite_->resetAnimation();
    }
}

void Player::updateWalkingStateForCredits() {
    if (pos_x_ > param.game.game_area.center_x - WIDTH / 2) {
        setWalkingState(State::WALKING_LEFT);
    } else {
        setWalkingState(State::WALKING_RIGHT);
    }
}

void Player::setInputBasedOnPlayerId() {
    switch (id_) {
        case 1:
            setInputPlaying(Input::Action::LEFT);
            break;
        case 2:
            setInputPlaying(Input::Action::RIGHT);
            break;
        default:
            break;
    }
}

void Player::updateStepCounter() {
    ++step_counter_;
    if (step_counter_ % 10 == 0) {
        playSound("walk.wav");
    }
}

// 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() {
    switch (playing_state_) {
        case State::PLAYING:
        case State::ENTERING_NAME_GAME_COMPLETED:
        case State::ENTERING_SCREEN:
        case State::LEAVING_SCREEN:
        case State::TITLE_ANIMATION:
        case State::CREDITS: {
            // Crea cadenas de texto para componer el nombre de la animación
            const std::string WALK_ANIMATION = walking_state_ == State::WALKING_STOP ? "stand" : "walk";
            const std::string FIRE_ANIMATION = firing_state_ == State::FIRING_UP ? "-fire-center" : "-fire-side";
            const std::string RECOIL_ANIMATION = firing_state_ == State::RECOILING_UP ? "-recoil-center" : "-recoil-side";
            const std::string COOL_ANIMATION = firing_state_ == State::COOLING_UP ? "-cool-center" : "-cool-side";

            const SDL_FlipMode FLIP_WALK = walking_state_ == State::WALKING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            const SDL_FlipMode FLIP_FIRE = firing_state_ == State::FIRING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            const SDL_FlipMode FLIP_RECOIL = firing_state_ == State::RECOILING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
            const SDL_FlipMode FLIP_COOL = firing_state_ == State::COOLING_RIGHT ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;

            // Establece la animación a partir de las cadenas
            if (firing_state_ == State::FIRING_NONE) {
                // No esta disparando
                player_sprite_->setCurrentAnimation(WALK_ANIMATION, false);
                player_sprite_->setFlip(FLIP_WALK);
            } else if (isRecoiling()) {
                // Retroceso
                player_sprite_->setCurrentAnimation(WALK_ANIMATION + RECOIL_ANIMATION, false);
                player_sprite_->setFlip(FLIP_RECOIL);
            } else if (isCooling()) {
                // Acaba de disparar
                player_sprite_->setCurrentAnimation(WALK_ANIMATION + COOL_ANIMATION, false);
                player_sprite_->setFlip(FLIP_COOL);
            } else {
                // Está disparando
                player_sprite_->setCurrentAnimation(WALK_ANIMATION + FIRE_ANIMATION, false);
                // Si dispara de lado, invierte el sprite segun hacia donde dispara
                // Si dispara recto, invierte el sprite segun hacia donde camina
                player_sprite_->setFlip(FIRE_ANIMATION == "-fire-center" ? FLIP_WALK : FLIP_FIRE);
            }
            break;
        }
        case State::WAITING: {
            player_sprite_->setCurrentAnimation("hello");
            break;
        }
        case State::ROLLING:
        case State::CONTINUE_TIME_OUT: {
            player_sprite_->setCurrentAnimation("rolling");
            break;
        }
        case State::LYING_ON_THE_FLOOR_FOREVER:
        case State::ENTERING_NAME:
        case State::CONTINUE: {
            player_sprite_->setCurrentAnimation("dead");
            break;
        }
        case State::CELEBRATING: {
            player_sprite_->setCurrentAnimation("celebration");
            break;
        }
        default:
            break;
    }

    // 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 (playing_state_ != State::PLAYING) {
        return;
    }

    if (cant_fire_counter_ > 0) {
        handleFiringCooldown();
    } else {
        handleRecoilAndCooling();
    }
}

void Player::handleFiringCooldown() {
    cooling_state_counter_ = COOLING_DURATION;

    // Transition to recoiling state at halfway point
    if (cant_fire_counter_ == recoiling_state_duration_ / 2) {
        transitionToRecoiling();
    }

    --cant_fire_counter_;
    if (cant_fire_counter_ == 0) {
        recoiling_state_counter_ = recoiling_state_duration_;
    }
}

void Player::handleRecoilAndCooling() {
    if (recoiling_state_counter_ > 0) {
        --recoiling_state_counter_;
        return;
    }

    handleCoolingState();
}

void Player::handleCoolingState() {
    if (cooling_state_counter_ > COOLING_COMPLETE) {
        if (cooling_state_counter_ == COOLING_DURATION) {
            transitionToCooling();
        }
        --cooling_state_counter_;
    }

    if (cooling_state_counter_ == COOLING_COMPLETE) {
        completeCooling();
    }
}

void Player::transitionToRecoiling() {
    switch (firing_state_) {
        case State::FIRING_LEFT:
            setFiringState(State::RECOILING_LEFT);
            break;
        case State::FIRING_RIGHT:
            setFiringState(State::RECOILING_RIGHT);
            break;
        case State::FIRING_UP:
            setFiringState(State::RECOILING_UP);
            break;
        default:
            break;
    }
}

void Player::transitionToCooling() {
    switch (firing_state_) {
        case State::RECOILING_LEFT:
            setFiringState(State::COOLING_LEFT);
            break;
        case State::RECOILING_RIGHT:
            setFiringState(State::COOLING_RIGHT);
            break;
        case State::RECOILING_UP:
            setFiringState(State::COOLING_UP);
            break;
        default:
            break;
    }
}

void Player::completeCooling() {
    setFiringState(State::FIRING_NONE);
    cooling_state_counter_ = -1;
}

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

// 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 State::CONTINUE: {
            Scoreboard::get()->setContinue(scoreboard_panel_, getContinueCounter());
            break;
        }
        case State::ENTERING_NAME:
        case State::ENTERING_NAME_GAME_COMPLETED: {
            Scoreboard::get()->setRecordName(scoreboard_panel_, enter_name_->getCurrentName());
            Scoreboard::get()->setSelectorPos(scoreboard_panel_, getRecordNamePos());
            break;
        }
        default:
            break;
    }
}

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

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

    switch (playing_state_) {
        case State::RESPAWNING: {
            playSound("voice_thankyou.wav");
            setPlayingState(State::PLAYING);
            setInvulnerable(true);
            break;
        }
        case State::PLAYING: {
            init();
            setInvulnerable(true);
            setScoreboardMode(Scoreboard::Mode::SCORE);
            Stage::power_can_be_added = true;
            break;
        }
        case State::CONTINUE: {
            // Inicializa el contador de continuar
            continue_ticks_ = SDL_GetTicks();
            continue_counter_ = 9;
            playSound("continue_clock.wav");
            setScoreboardMode(Scoreboard::Mode::CONTINUE);
            break;
        }
        case State::WAITING: {
            switch (id_) {
                case 1:
                    pos_x_ = param.game.game_area.rect.x;
                    break;
                case 2:
                    pos_x_ = param.game.game_area.rect.w - WIDTH;
                    break;
                default:
                    pos_x_ = 0;
                    break;
            }
            pos_y_ = default_pos_y_;
            waiting_counter_ = 0;
            shiftSprite();
            player_sprite_->setCurrentAnimation("hello");
            player_sprite_->animtionPause();
            setScoreboardMode(Scoreboard::Mode::WAITING);
            break;
        }
        case State::ENTERING_NAME: {
            setScoreboardMode(Scoreboard::Mode::ENTER_NAME);
            break;
        }
        case State::SHOWING_NAME: {
            showing_name_ticks_ = SDL_GetTicks();
            setScoreboardMode(Scoreboard::Mode::SHOW_NAME);
            Scoreboard::get()->setRecordName(scoreboard_panel_, last_enter_name_);
            addScoreToScoreBoard();
            break;
        }
        case State::ROLLING: {
            // Activa la animación de rodar dando botes
            player_sprite_->setCurrentAnimation("rolling");
            player_sprite_->setAnimationSpeed(4);
            player_sprite_->setVelY(-6.6F);       // Velocidad inicial
            player_sprite_->setAccelY(0.2F);      // Gravedad
            player_sprite_->setPosY(pos_y_ - 2);  // Para "sacarlo" del suelo, ya que está hundido un pixel para ocultar el outline de los pies
            (rand() % 2 == 0) ? player_sprite_->setVelX(3.3F) : player_sprite_->setVelX(-3.3F);
            break;
        }
        case State::TITLE_ANIMATION: {
            // Activa la animación de rodar
            player_sprite_->setCurrentAnimation("walk");
            playSound("voice_thankyou.wav");
            break;
        }
        case State::TITLE_HIDDEN: {
            player_sprite_->setVelX(0.0F);
            player_sprite_->setVelY(0.0F);
            break;
        }
        case State::CONTINUE_TIME_OUT: {
            // Activa la animación de sacar al jugador de la zona de juego
            player_sprite_->setAccelY(0.2F);
            player_sprite_->setVelY(-4.0F);
            player_sprite_->setVelX(0.0F);
            player_sprite_->setCurrentAnimation("rolling");
            player_sprite_->setAnimationSpeed(5);
            setScoreboardMode(Scoreboard::Mode::GAME_OVER);
            playSound("voice_aw_aw_aw.wav");
            playSound("jump.wav");
            break;
        }
        case State::GAME_OVER: {
            setScoreboardMode(Scoreboard::Mode::GAME_OVER);
            break;
        }
        case State::CELEBRATING: {
            game_completed_ = true;
            setScoreboardMode(Scoreboard::Mode::SCORE);
            break;
        }
        case State::ENTERING_NAME_GAME_COMPLETED: {
            setWalkingState(State::WALKING_STOP);
            setFiringState(State::FIRING_NONE);
            setScoreboardMode(Scoreboard::Mode::ENTER_NAME);
            break;
        }
        case State::LEAVING_SCREEN: {
            step_counter_ = 0;
            setScoreboardMode(Scoreboard::Mode::GAME_COMPLETED);
            break;
        }
        case State::ENTERING_SCREEN: {
            init();
            step_counter_ = 0;
            setScoreboardMode(Scoreboard::Mode::SCORE);
            switch (id_) {
                case 1:
                    pos_x_ = param.game.game_area.rect.x - WIDTH;
                    break;

                case 2:
                    pos_x_ = param.game.game_area.rect.x + param.game.game_area.rect.w;
                    break;

                default:
                    break;
            }
            break;
        }
        case State::CREDITS: {
            vel_x_ = (walking_state_ == State::WALKING_RIGHT) ? BASE_SPEED : -BASE_SPEED;
            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 (playing_state_ == State::PLAYING) {
        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 (playing_state_ == State::PLAYING) {
        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;
}

// 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 = (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_ == State::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_ == State::ENTERING_NAME || playing_state_ == State::ENTERING_NAME_GAME_COMPLETED) {
        constexpr int TICKS_SPEED = 1000;
        if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
            decNameEntryCounter();
        }
    }
}

// Actualiza el estado de SHOWING_NAME
void Player::updateShowingName() {
    if (playing_state_ == State::SHOWING_NAME) {
        constexpr int TICKS_SPEED = 5000;
        if (SDL_GetTicks() - name_entry_ticks_ > TICKS_SPEED) {
            game_completed_ ? setPlayingState(State::LEAVING_SCREEN) : setPlayingState(State::CONTINUE);
        }
    }
}

// Decrementa el contador de continuar
void Player::decContinueCounter() {
    continue_ticks_ = SDL_GetTicks();
    --continue_counter_;
    if (continue_counter_ < 0) {
        setPlayingState(State::CONTINUE_TIME_OUT);
    } else {
        playSound("continue_clock.wav");
    }
}

// Decrementa el contador de entrar nombre
void Player::decNameEntryCounter() {
    name_entry_ticks_ = SDL_GetTicks();

    // Actualiza contadores
    ++name_entry_idle_counter_;
    ++name_entry_total_counter_;

    // Comprueba los contadores
    if ((name_entry_total_counter_ >= param.game.name_entry_total_time) ||
        (name_entry_idle_counter_ >= param.game.name_entry_idle_time)) {
        name_entry_total_counter_ = 0;
        name_entry_idle_counter_ = 0;
        if (playing_state_ == State::ENTERING_NAME) {
            last_enter_name_ = getRecordName();
            setPlayingState(State::SHOWING_NAME);
        } else {
            setPlayingState(State::LEAVING_SCREEN);
        }
    }
}

// Obtiene la posición que se está editando del nombre del jugador para la tabla de mejores puntuaciones
auto Player::getRecordNamePos() const -> int {
    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_x_offset_);
}

// Hace sonar un sonido
void Player::playSound(const std::string &name) const {
    if (demo_) {
        return;
    }

    static auto *audio_ = Audio::get();
    audio_->playSound(name);
}

// Indica si se puede dibujar el objeto
auto Player::isRenderable() const -> bool {
    return !isGameOver() && !isTitleHidden();
};

// Añade una puntuación a la tabla de records
void Player::addScoreToScoreBoard() const {
    const auto ENTRY = HiScoreEntry(trim(getLastEnterName()), getScore(), get1CC());
    auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
    Options::settings.last_hi_score_entry.at(getId() - 1) = manager->add(ENTRY);
    manager->saveToFile(Asset::get()->get("score.bin"));
}