#include "balloon_manager.h"
#include <stdlib.h>             // Para rand
#include <algorithm>            // Para remove_if
#include <numeric>              // Para accumulate
#include "balloon.h"            // Para Balloon, BALLOON_SCORE, BALLOON_VELX...
#include "balloon_formations.h" // Para BalloonFormationParams, BalloonForma...
#include "explosions.h"         // Para Explosions
#include "audio.h"              // Para JA_PlaySound
#include "param.h"              // Para Param, ParamGame, param
#include "resource.h"           // Para Resource
#include "screen.h"             // Para Screen
#include "stage.h"              // Para addPower
#include "utils.h"              // Para Zone, Color, flash_color

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

// Inicializa
void BalloonManager::init()
{
    // Texturas - Globos
    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("balloon4.png"));
    balloon_textures_.emplace_back(Resource::get()->getTexture("powerball.png"));

    // Animaciones -- Globos
    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("balloon4.ani"));
    balloon_animations_.emplace_back(Resource::get()->getAnimation("powerball.ani"));

    // Texturas - Explosiones
    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"));
    explosions_textures_.emplace_back(Resource::get()->getTexture("explosion4.png"));

    // Animaciones -- Explosiones
    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"));
    explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion4.ani"));

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

// Actualiza
void BalloonManager::update()
{
    for (auto balloon : balloons_)
    {
        balloon->update();
    }
    updateBalloonDeployCounter();
    explosions_->update();
}

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

// Crea una formación de enemigos
void BalloonManager::deployBalloonFormation(int stage)
{
    // Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
    if (balloon_deploy_counter_ == 0)
    {
        // En este punto se decide entre crear una powerball o una formación enemiga
        if ((rand() % 100 < 15) && (canPowerBallBeCreated()))
        {
            // Crea una powerball
            createPowerBall();

            // Da un poco de margen para que se creen mas enemigos
            balloon_deploy_counter_ = 10;
        }
        else
        {
            // Decrementa el contador de despliegues enemigos de la PowerBall
            power_ball_counter_ = (power_ball_counter_ > 0) ? (power_ball_counter_ - 1) : 0;

            // Elige una formación enemiga la azar
            auto formation = rand() % 10;

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

            last_balloon_deploy_ = formation;

            const auto set = balloon_formations_->getSet(stage, formation);
            const auto num_enemies = set.number_of_balloons;
            for (int i = 0; i < num_enemies; ++i)
            {
                auto p = set.init[i];
                createBalloon(
                    p.x,
                    p.y,
                    p.type,
                    p.size,
                    p.vel_x,
                    balloon_speed_,
                    (creation_time_enabled_) ? p.creation_counter : 0);
            }

            balloon_deploy_counter_ = 300;
        }
    }
}

// Crea una formación de enemigos específica
void BalloonManager::deploySet(int set_number)
{
    const auto set = balloon_formations_->getSet(set_number);
    const auto num_enemies = set.number_of_balloons;
    for (int i = 0; i < num_enemies; ++i)
    {
        auto p = set.init[i];
        createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter);
    }
}

// Crea una formación de enemigos específica
void BalloonManager::deploySet(int set_number, int y)
{
    const auto set = balloon_formations_->getSet(set_number);
    const auto num_enemies = set.number_of_balloons;
    for (int i = 0; i < num_enemies; ++i)
    {
        auto p = set.init[i];
        createBalloon(p.x, y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter);
    }
}

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

// Actualiza la variable enemyDeployCounter
void BalloonManager::updateBalloonDeployCounter()
{
    if (balloon_deploy_counter_ > 0)
    {
        --balloon_deploy_counter_;
    }
}

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

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

// Crea un globo nuevo en el vector de globos
std::shared_ptr<Balloon> BalloonManager::createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int creation_timer)
{
    if (can_deploy_balloons_)
    {
        const int index = static_cast<int>(size);
        balloons_.emplace_back(std::make_shared<Balloon>(x, y, type, size, velx, speed, creation_timer, play_area_, balloon_textures_.at(index), balloon_animations_.at(index)));
        return balloons_.back();
    }

    return nullptr;
}

// Crea un globo a partir de otro globo
void BalloonManager::createChildBalloon(const std::shared_ptr<Balloon> &balloon, const std::string &direction)
{
    if (can_deploy_balloons_)
    {
        const float vx = direction == "LEFT" ? BALLOON_VELX_NEGATIVE : BALLOON_VELX_POSITIVE;
        const auto lower_size = static_cast<BalloonSize>(static_cast<int>(balloon->getSize()) - 1);
        auto b = createBalloon(0, balloon->getPosY(), balloon->getType(), lower_size, vx, balloon_speed_, 0);
        const int x = direction == "LEFT" ? balloon->getPosX() + (balloon->getWidth() / 3) : balloon->getPosX() + 2 * (balloon->getWidth() / 3);
        b->alignTo(x);
        b->setVelY(b->getType() == BalloonType::BALLOON ? -2.50f : BALLOON_VELX_NEGATIVE * 2.0f);
        if (balloon->isStopped())
        {
            b->stop();
        }
        if (balloon->isUsingReversedColor())
        {
            b->useReverseColor();
        }
    }
}

// Crea una PowerBall
void BalloonManager::createPowerBall()
{
    if (can_deploy_balloons_)
    {
        constexpr int VALUES = 6;
        constexpr float POS_Y = -BALLOON_SIZE[4];
        constexpr int CREATION_TIME = 0;

        const float LEFT = param.game.play_area.rect.x;
        const float CENTER = param.game.play_area.center_x - (BALLOON_SIZE[4] / 2);
        const float RIGHT = param.game.play_area.rect.w - BALLOON_SIZE[4];

        const int LUCK = rand() % VALUES;
        const float POS_X[VALUES] = {LEFT, LEFT, CENTER, CENTER, RIGHT, RIGHT};
        const float VEL_X[VALUES] = {BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE};

        balloons_.emplace_back(std::make_unique<Balloon>(POS_X[LUCK], POS_Y, BalloonType::POWERBALL, BalloonSize::SIZE4, VEL_X[LUCK], balloon_speed_, CREATION_TIME, play_area_, balloon_textures_[4], balloon_animations_[4]));
        balloons_.back()->setInvulnerable(true);

        power_ball_enabled_ = true;
        power_ball_counter_ = POWERBALL_COUNTER;
    }
}

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

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

    if (balloon->getType() == BalloonType::POWERBALL)
    {
        score = destroyAllBalloons();
        power_ball_enabled_ = false;
        balloon_deploy_counter_ = 20;
    }
    else
    {
        score = balloon->getScore();
        if (balloon->getSize() != BalloonSize::SIZE1)
        {
            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->disable();
    }

    return score;
}

// Explosiona un globo. Lo destruye = no crea otros globos
int BalloonManager::destroyBalloon(std::shared_ptr<Balloon> &balloon)
{
    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 BalloonSize::SIZE4:
        score = BALLOON_SCORE[3] + (2 * BALLOON_SCORE[2]) + (4 * BALLOON_SCORE[1]) + (8 * BALLOON_SCORE[0]);
        break;
    case BalloonSize::SIZE3:
        score = BALLOON_SCORE[2] + (2 * BALLOON_SCORE[1]) + (4 * BALLOON_SCORE[0]);
        break;
    case BalloonSize::SIZE2:
        score = BALLOON_SCORE[1] + (2 * BALLOON_SCORE[0]);
        break;
    case BalloonSize::SIZE1:
        score = BALLOON_SCORE[0];
        break;
    default:
        score = 0;
        break;
    }

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

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

    return score;
}

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

    balloon_deploy_counter_ = 300;
    Audio::get().playSound("power_ball_explosion.wav");
    Screen::get()->flash(FLASH_COLOR, 3);
    Screen::get()->shake();

    return score;
}

// Detiene todos los globos
void BalloonManager::stopAllBalloons()
{
    for (auto &balloon : balloons_)
    {
        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()
{
    deploySet(1);
}

// Crea una disposición de globos aleatoria
void BalloonManager::createRandomBalloons()
{
    const int num_balloons = 2 + rand() % 4;
    for (int i = 0; i < num_balloons; ++i)
    {
        const float x = param.game.game_area.rect.x + (rand() % static_cast<int>(param.game.game_area.rect.w)) - BALLOON_SIZE[3];
        const int y = param.game.game_area.rect.y + (rand() % 50);
        const BalloonSize size = static_cast<BalloonSize>(rand() % 4);
        const float vel_x = (rand() % 2 == 0) ? BALLOON_VELX_POSITIVE : BALLOON_VELX_NEGATIVE;
        const int creation_counter = 0;
        createBalloon(x, y, BalloonType::BALLOON, size, vel_x, balloon_speed_, creation_counter);
    }
}

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

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