#include "game/gameplay/balloon_manager.hpp"

#include <algorithm>  // Para remove_if
#include <array>
#include <cstdlib>  // Para rand
#include <numeric>  // Para accumulate

#include "core/rendering/screen.hpp"             // Para Screen
#include "core/resources/resource.hpp"           // Para Resource
#include "core/system/stage_interface.hpp"       // Para IStageInfo
#include "game/entities/balloon.hpp"             // Para Balloon, Balloon::SCORE.at( )ALLOON_VELX...
#include "game/entities/explosions.hpp"          // Para Explosions
#include "game/gameplay/balloon_formations.hpp"  // Para BalloonFormationParams, BalloonForma...
#include "utils/color.hpp"                       // Para Zone, Color, flash_color
#include "utils/param.hpp"                       // Para Param, ParamGame, param
#include "utils/utils.hpp"

// Constructor
BalloonManager::BalloonManager(IStageInfo* stage_info)
    : explosions_(std::make_unique<Explosions>()),
      balloon_formations_(std::make_unique<BalloonFormations>()),
      stage_info_(stage_info) { init(); }

// Inicializa
void BalloonManager::init() {
    // Limpia
    balloon_textures_.clear();
    balloon_animations_.clear();
    explosions_textures_.clear();
    explosions_animations_.clear();

    // Texturas - Globos
    balloon_textures_.emplace_back(Resource::get()->getTexture("balloon0.png"));
    balloon_textures_.emplace_back(Resource::get()->getTexture("balloon1.png"));
    balloon_textures_.emplace_back(Resource::get()->getTexture("balloon2.png"));
    balloon_textures_.emplace_back(Resource::get()->getTexture("balloon3.png"));
    balloon_textures_.emplace_back(Resource::get()->getTexture("powerball.png"));

    // Animaciones -- Globos
    balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon0.ani"));
    balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon1.ani"));
    balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon2.ani"));
    balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon3.ani"));
    balloon_animations_.emplace_back(Resource::get()->getAnimation("powerball.ani"));

    // Texturas - Explosiones
    explosions_textures_.emplace_back(Resource::get()->getTexture("explosion0.png"));
    explosions_textures_.emplace_back(Resource::get()->getTexture("explosion1.png"));
    explosions_textures_.emplace_back(Resource::get()->getTexture("explosion2.png"));
    explosions_textures_.emplace_back(Resource::get()->getTexture("explosion3.png"));

    // Animaciones -- Explosiones
    explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion0.ani"));
    explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion1.ani"));
    explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion2.ani"));
    explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion3.ani"));

    // Añade texturas
    explosions_->addTexture(0, explosions_textures_.at(0), explosions_animations_.at(0));
    explosions_->addTexture(1, explosions_textures_.at(1), explosions_animations_.at(1));
    explosions_->addTexture(2, explosions_textures_.at(2), explosions_animations_.at(2));
    explosions_->addTexture(3, explosions_textures_.at(3), explosions_animations_.at(3));
}

// Actualiza (time-based)
void BalloonManager::update(float delta_time) {
    for (const auto& balloon : balloons_) {
        balloon->update(delta_time);
    }
    updateBalloonDeployCounter(delta_time);
    explosions_->update(delta_time);
}

// Renderiza los objetos
void BalloonManager::render() {
    for (auto& balloon : balloons_) {
        balloon->render();
    }
    explosions_->render();
}

// Crea una formación de globos
void BalloonManager::deployRandomFormation(int stage) {
    // Solo despliega una formación enemiga si el timer ha llegado a cero
    if (balloon_deploy_counter_ <= 0.0F) {
        // En este punto se decide entre crear una powerball o una formación enemiga
        if ((rand() % 100 < 15) && (canPowerBallBeCreated())) {
            createPowerBall();                                 // Crea una powerball
            balloon_deploy_counter_ = POWERBALL_DEPLOY_DELAY;  // Resetea con pequeño retraso
        } else {
            // Decrementa el contador de despliegues de globos necesarios para la siguiente PowerBall
            if (power_ball_counter_ > 0) {
                --power_ball_counter_;
            }

            // Elige una formación enemiga la azar
            const auto NUM_FORMATIONS = balloon_formations_->getPoolSize(stage);
            int formation_id = rand() % NUM_FORMATIONS;

            // Evita repetir la ultima formación enemiga desplegada
            if (formation_id == last_balloon_deploy_) {
                ++formation_id %= NUM_FORMATIONS;
            }

            last_balloon_deploy_ = formation_id;

            // Crea los globos de la formación
            const auto BALLOONS = balloon_formations_->getFormationFromPool(stage, formation_id).balloons;
            for (auto balloon : BALLOONS) {
                Balloon::Config config = {
                    .x = balloon.x,
                    .y = balloon.y,
                    .type = balloon.type,
                    .size = balloon.size,
                    .vel_x = balloon.vel_x,
                    .game_tempo = balloon_speed_,
                    .creation_counter = creation_time_enabled_ ? balloon.creation_counter : 0.0F};
                createBalloon(config);
            }

            // Reinicia el timer para el próximo despliegue
            balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_DELAY;
        }
    }
}

// Crea una formación de globos específica
void BalloonManager::deployFormation(int formation_id) {
    const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
    for (auto balloon : BALLOONS) {
        Balloon::Config config = {
            .x = balloon.x,
            .y = balloon.y,
            .type = balloon.type,
            .size = balloon.size,
            .vel_x = balloon.vel_x,
            .game_tempo = balloon_speed_,
            .creation_counter = balloon.creation_counter};
        createBalloon(config);
    }
}

// Crea una formación de globos específica a una altura determinada
void BalloonManager::deployFormation(int formation_id, float y) {
    const auto BALLOONS = balloon_formations_->getFormation(formation_id).balloons;
    for (auto balloon : BALLOONS) {
        Balloon::Config config = {
            .x = balloon.x,
            .y = y,
            .type = balloon.type,
            .size = balloon.size,
            .vel_x = balloon.vel_x,
            .game_tempo = balloon_speed_,
            .creation_counter = balloon.creation_counter};
        createBalloon(config);
    }
}

// Vacia del vector de globos los globos que ya no sirven
void BalloonManager::freeBalloons() {
    std::erase_if(balloons_, [](const auto& balloon) -> auto {
        return !balloon->isEnabled();
    });
}

// Actualiza el timer de despliegue de globos (time-based)
void BalloonManager::updateBalloonDeployCounter(float delta_time) {
    // DeltaTime en segundos - timer decrementa hasta llegar a cero
    balloon_deploy_counter_ -= delta_time;
}

// Indica si se puede crear una powerball
auto BalloonManager::canPowerBallBeCreated() -> bool { return (!power_ball_enabled_) && (calculateScreenPower() > Balloon::POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); }

// Calcula el poder actual de los globos en pantalla
auto BalloonManager::calculateScreenPower() -> int {
    return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto& balloon) -> auto { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); });
}

// Crea un globo nuevo en el vector de globos
auto BalloonManager::createBalloon(Balloon::Config config) -> std::shared_ptr<Balloon> {
    if (can_deploy_balloons_) {
        const int INDEX = static_cast<int>(config.size);
        config.play_area = play_area_;
        config.texture = balloon_textures_.at(INDEX);
        config.animation = balloon_animations_.at(INDEX);
        config.sound.enabled = sound_enabled_;
        config.sound.bouncing_enabled = bouncing_sound_enabled_;
        config.sound.poping_enabled = poping_sound_enabled_;
        balloons_.emplace_back(std::make_shared<Balloon>(config));
        return balloons_.back();
    }

    return nullptr;
}

// Crea un globo a partir de otro globo
void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon>& parent_balloon, const std::string& direction) {
    if (can_deploy_balloons_) {
        // Calcula parametros
        const int PARENT_HEIGHT = parent_balloon->getHeight();
        const int CHILD_HEIGHT = Balloon::WIDTH.at(static_cast<size_t>(parent_balloon->getSize()) - 1);
        const int CHILD_WIDTH = CHILD_HEIGHT;

        const float X = direction == "LEFT" ? parent_balloon->getPosX() + (parent_balloon->getWidth() / 3) : parent_balloon->getPosX() + (2 * (parent_balloon->getWidth() / 3));
        const float MIN_X = play_area_.x;
        const float MAX_X = play_area_.w - CHILD_WIDTH;

        Balloon::Config config = {
            .x = std::clamp(X - (CHILD_WIDTH / 2), MIN_X, MAX_X),
            .y = parent_balloon->getPosY() + ((PARENT_HEIGHT - CHILD_HEIGHT) / 2),
            .type = parent_balloon->getType(),
            .size = static_cast<Balloon::Size>(static_cast<int>(parent_balloon->getSize()) - 1),
            .vel_x = direction == "LEFT" ? Balloon::VELX_NEGATIVE : Balloon::VELX_POSITIVE,
            .game_tempo = balloon_speed_,
            .creation_counter = 0};

        // Crea el globo hijo
        auto child_balloon = createBalloon(config);

        // Configura el globo hijo
        if (child_balloon != nullptr) {
            // Establece parametros
            constexpr float VEL_Y_BALLOON_PER_S = -150.0F;
            switch (child_balloon->getType()) {
                case Balloon::Type::BALLOON: {
                    child_balloon->setVelY(VEL_Y_BALLOON_PER_S);
                    break;
                }
                case Balloon::Type::FLOATER: {
                    child_balloon->setVelY(Balloon::VELX_NEGATIVE * 2.0F);
                    break;
                }
                default:
                    break;
            }

            // Herencia de estados
            if (parent_balloon->isStopped()) { child_balloon->stop(); }
            if (parent_balloon->isUsingReversedColor()) { child_balloon->useReverseColor(); }
        }
    }
}

// Crea una PowerBall
void BalloonManager::createPowerBall() {
    if (can_deploy_balloons_) {
        constexpr int VALUES = 6;
        const int LUCK = rand() % VALUES;

        const float LEFT = param.game.play_area.rect.x;
        const float CENTER = param.game.play_area.center_x - (Balloon::WIDTH.at(4) / 2);
        const float RIGHT = param.game.play_area.rect.w - Balloon::WIDTH.at(4);

        const std::array<float, VALUES> POS_X = {LEFT, LEFT, CENTER, CENTER, RIGHT, RIGHT};
        const std::array<float, VALUES> VEL_X = {Balloon::VELX_POSITIVE, Balloon::VELX_POSITIVE, Balloon::VELX_POSITIVE, Balloon::VELX_NEGATIVE, Balloon::VELX_NEGATIVE, Balloon::VELX_NEGATIVE};

        Balloon::Config config = {
            .x = POS_X.at(LUCK),
            .y = -Balloon::WIDTH.at(4),
            .type = Balloon::Type::POWERBALL,
            .size = Balloon::Size::EXTRALARGE,
            .vel_x = VEL_X.at(LUCK),
            .game_tempo = balloon_speed_,
            .creation_counter = 0,
            .play_area = play_area_,
            .texture = balloon_textures_.at(4),
            .animation = balloon_animations_.at(4),
            .sound = {
                .bouncing_enabled = bouncing_sound_enabled_,
                .poping_enabled = poping_sound_enabled_,
                .enabled = sound_enabled_}};

        balloons_.emplace_back(std::make_unique<Balloon>(config));
        balloons_.back()->setInvulnerable(true);

        power_ball_enabled_ = true;
        power_ball_counter_ = Balloon::POWERBALL_COUNTER;
    }
}

// Establece la velocidad de los globos
void BalloonManager::setBalloonSpeed(float speed) {
    balloon_speed_ = speed;
    for (auto& balloon : balloons_) {
        balloon->setGameTempo(speed);
    }
}

// Explosiona un globo. Lo destruye y crea otros dos si es el caso
auto BalloonManager::popBalloon(const std::shared_ptr<Balloon>& balloon) -> int {
    stage_info_->addPower(1);
    int score = 0;

    if (balloon->getType() == Balloon::Type::POWERBALL) {
        balloon->pop(true);
        score = destroyAllBalloons();
        power_ball_enabled_ = false;
        balloon_deploy_counter_ = BALLOON_POP_DELAY;  // Resetea con retraso
    } else {
        score = balloon->getScore();
        if (balloon->getSize() != Balloon::Size::SMALL) {
            createChildBalloon(balloon, "LEFT");
            createChildBalloon(balloon, "RIGHT");
        }

        // Agrega la explosión y elimina el globo
        explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast<int>(balloon->getSize()));
        balloon->pop(true);
    }

    return score;
}

// Explosiona un globo. Lo destruye = no crea otros globos
auto BalloonManager::destroyBalloon(std::shared_ptr<Balloon>& balloon) -> int {
    int score = 0;

    // Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
    switch (balloon->getSize()) {
        case Balloon::Size::EXTRALARGE:
            score = Balloon::SCORE.at(3) + (2 * Balloon::SCORE.at(2)) + (4 * Balloon::SCORE.at(1)) + (8 * Balloon::SCORE.at(0));
            break;
        case Balloon::Size::LARGE:
            score = Balloon::SCORE.at(2) + (2 * Balloon::SCORE.at(1)) + (4 * Balloon::SCORE.at(0));
            break;
        case Balloon::Size::MEDIUM:
            score = Balloon::SCORE.at(1) + (2 * Balloon::SCORE.at(0));
            break;
        case Balloon::Size::SMALL:
            score = Balloon::SCORE.at(0);
            break;
        default:
            score = 0;
            break;
    }

    // Aumenta el poder de la fase
    stage_info_->addPower(balloon->getPower());

    // Destruye el globo
    explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast<int>(balloon->getSize()));
    balloon->pop();

    return score;
}

// Destruye todos los globos
auto BalloonManager::destroyAllBalloons() -> int {
    int score = 0;
    for (auto& balloon : balloons_) {
        score += destroyBalloon(balloon);
    }

    balloon_deploy_counter_ = DEFAULT_BALLOON_DEPLOY_DELAY;
    Screen::get()->flash(Colors::FLASH, 0.05F);
    Screen::get()->shake();

    return score;
}

// Detiene todos los globos
void BalloonManager::stopAllBalloons() {
    for (auto& balloon : balloons_) {
        if (!balloon->isBeingCreated()) {
            balloon->stop();
        }
    }
}

// Pone en marcha todos los globos
void BalloonManager::startAllBalloons() {
    for (auto& balloon : balloons_) {
        if (!balloon->isBeingCreated()) {
            balloon->start();
        }
    }
}

// Cambia el color de todos los globos
void BalloonManager::reverseColorsToAllBalloons() {
    for (auto& balloon : balloons_) {
        if (balloon->isStopped()) {
            balloon->useReverseColor();
        }
    }
}

// Cambia el color de todos los globos
void BalloonManager::normalColorsToAllBalloons() {
    for (auto& balloon : balloons_) {
        balloon->useNormalColor();
    }
}

// Crea dos globos gordos
void BalloonManager::createTwoBigBalloons() {
    deployFormation(1);
}

// Obtiene el nivel de ameza actual generado por los globos
auto BalloonManager::getMenace() -> int {
    return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto& balloon) -> auto { return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); });
}

// Establece el sonido de los globos
void BalloonManager::setSounds(bool value) {
    sound_enabled_ = value;
    for (auto& balloon : balloons_) {
        balloon->setSound(value);
    }
}

// Activa o desactiva los sonidos de rebote los globos
void BalloonManager::setBouncingSounds(bool value) {
    bouncing_sound_enabled_ = value;
    for (auto& balloon : balloons_) {
        balloon->setBouncingSound(value);
    }
}
// Activa o desactiva los sonidos de los globos al explotar
void BalloonManager::setPoppingSounds(bool value) {
    poping_sound_enabled_ = value;
    for (auto& balloon : balloons_) {
        balloon->setPoppingSound(value);
    }
}