#include "boid_manager.h"

#include <algorithm>  // for std::min, std::max
#include <cmath>      // for sqrt, atan2

#include "../ball.h"                // for Ball
#include "../engine.h"              // for Engine (si se necesita)
#include "../scene/scene_manager.h" // for SceneManager
#include "../state/state_manager.h" // for StateManager
#include "../ui/ui_manager.h"       // for UIManager

BoidManager::BoidManager()
    : engine_(nullptr)
    , scene_mgr_(nullptr)
    , ui_mgr_(nullptr)
    , state_mgr_(nullptr)
    , screen_width_(0)
    , screen_height_(0)
    , boids_active_(false) {
}

BoidManager::~BoidManager() {
}

void BoidManager::initialize(Engine* engine, SceneManager* scene_mgr, UIManager* ui_mgr,
                              StateManager* state_mgr, int screen_width, int screen_height) {
    engine_ = engine;
    scene_mgr_ = scene_mgr;
    ui_mgr_ = ui_mgr;
    state_mgr_ = state_mgr;
    screen_width_ = screen_width;
    screen_height_ = screen_height;
}

void BoidManager::updateScreenSize(int width, int height) {
    screen_width_ = width;
    screen_height_ = height;
}

void BoidManager::activateBoids() {
    boids_active_ = true;

    // Desactivar gravedad al entrar en modo boids
    scene_mgr_->forceBallsGravityOff();

    // Inicializar velocidades aleatorias para los boids
    auto& balls = scene_mgr_->getBallsMutable();
    for (auto& ball : balls) {
        // Dar velocidad inicial aleatoria si está quieto
        float vx, vy;
        ball->getVelocity(vx, vy);
        if (vx == 0.0f && vy == 0.0f) {
            // Velocidad aleatoria entre -1 y 1
            vx = (rand() % 200 - 100) / 100.0f;
            vy = (rand() % 200 - 100) / 100.0f;
            ball->setVelocity(vx, vy);
        }
    }

    // Mostrar notificación (solo si NO estamos en modo demo o logo)
    if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
        ui_mgr_->showNotification("Modo Boids");
    }
}

void BoidManager::deactivateBoids(bool force_gravity_on) {
    if (!boids_active_) return;

    boids_active_ = false;

    // Activar gravedad al salir (si se especifica)
    if (force_gravity_on) {
        scene_mgr_->forceBallsGravityOn();
    }

    // Mostrar notificación (solo si NO estamos en modo demo o logo)
    if (state_mgr_ && ui_mgr_ && state_mgr_->getCurrentMode() == AppMode::SANDBOX) {
        ui_mgr_->showNotification("Modo Física");
    }
}

void BoidManager::toggleBoidsMode(bool force_gravity_on) {
    if (boids_active_) {
        deactivateBoids(force_gravity_on);
    } else {
        activateBoids();
    }
}

void BoidManager::update(float delta_time) {
    if (!boids_active_) return;

    auto& balls = scene_mgr_->getBallsMutable();

    // Aplicar las tres reglas de Reynolds a cada boid
    for (auto& ball : balls) {
        applySeparation(ball.get(), delta_time);
        applyAlignment(ball.get(), delta_time);
        applyCohesion(ball.get(), delta_time);
        applyBoundaries(ball.get());
        limitSpeed(ball.get());
    }

    // Actualizar posiciones con velocidades resultantes
    for (auto& ball : balls) {
        float vx, vy;
        ball->getVelocity(vx, vy);

        SDL_FRect pos = ball->getPosition();
        pos.x += vx;
        pos.y += vy;

        ball->setPosition(pos.x, pos.y);
    }
}

// ============================================================================
// REGLAS DE REYNOLDS (1987)
// ============================================================================

void BoidManager::applySeparation(Ball* boid, float delta_time) {
    // Regla 1: Separación - Evitar colisiones con vecinos cercanos
    float steer_x = 0.0f;
    float steer_y = 0.0f;
    int count = 0;

    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    const auto& balls = scene_mgr_->getBalls();
    for (const auto& other : balls) {
        if (other.get() == boid) continue;  // Ignorar a sí mismo

        SDL_FRect other_pos = other->getPosition();
        float other_x = other_pos.x + other_pos.w / 2.0f;
        float other_y = other_pos.y + other_pos.h / 2.0f;

        float dx = center_x - other_x;
        float dy = center_y - other_y;
        float distance = std::sqrt(dx * dx + dy * dy);

        if (distance > 0.0f && distance < BOID_SEPARATION_RADIUS) {
            // Vector normalizado apuntando lejos del vecino, ponderado por cercanía
            steer_x += (dx / distance) / distance;
            steer_y += (dy / distance) / distance;
            count++;
        }
    }

    if (count > 0) {
        // Promedio
        steer_x /= count;
        steer_y /= count;

        // Aplicar fuerza de separación
        float vx, vy;
        boid->getVelocity(vx, vy);
        vx += steer_x * BOID_SEPARATION_WEIGHT * delta_time;
        vy += steer_y * BOID_SEPARATION_WEIGHT * delta_time;
        boid->setVelocity(vx, vy);
    }
}

void BoidManager::applyAlignment(Ball* boid, float delta_time) {
    // Regla 2: Alineación - Seguir dirección promedio del grupo
    float avg_vx = 0.0f;
    float avg_vy = 0.0f;
    int count = 0;

    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    const auto& balls = scene_mgr_->getBalls();
    for (const auto& other : balls) {
        if (other.get() == boid) continue;

        SDL_FRect other_pos = other->getPosition();
        float other_x = other_pos.x + other_pos.w / 2.0f;
        float other_y = other_pos.y + other_pos.h / 2.0f;

        float dx = center_x - other_x;
        float dy = center_y - other_y;
        float distance = std::sqrt(dx * dx + dy * dy);

        if (distance < BOID_ALIGNMENT_RADIUS) {
            float other_vx, other_vy;
            other->getVelocity(other_vx, other_vy);
            avg_vx += other_vx;
            avg_vy += other_vy;
            count++;
        }
    }

    if (count > 0) {
        // Velocidad promedio del grupo
        avg_vx /= count;
        avg_vy /= count;

        // Steering hacia la velocidad promedio
        float vx, vy;
        boid->getVelocity(vx, vy);
        float steer_x = (avg_vx - vx) * BOID_ALIGNMENT_WEIGHT * delta_time;
        float steer_y = (avg_vy - vy) * BOID_ALIGNMENT_WEIGHT * delta_time;

        // Limitar fuerza máxima de steering
        float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
        if (steer_mag > BOID_MAX_FORCE) {
            steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
            steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
        }

        vx += steer_x;
        vy += steer_y;
        boid->setVelocity(vx, vy);
    }
}

void BoidManager::applyCohesion(Ball* boid, float delta_time) {
    // Regla 3: Cohesión - Moverse hacia el centro de masa del grupo
    float center_of_mass_x = 0.0f;
    float center_of_mass_y = 0.0f;
    int count = 0;

    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    const auto& balls = scene_mgr_->getBalls();
    for (const auto& other : balls) {
        if (other.get() == boid) continue;

        SDL_FRect other_pos = other->getPosition();
        float other_x = other_pos.x + other_pos.w / 2.0f;
        float other_y = other_pos.y + other_pos.h / 2.0f;

        float dx = center_x - other_x;
        float dy = center_y - other_y;
        float distance = std::sqrt(dx * dx + dy * dy);

        if (distance < BOID_COHESION_RADIUS) {
            center_of_mass_x += other_x;
            center_of_mass_y += other_y;
            count++;
        }
    }

    if (count > 0) {
        // Centro de masa del grupo
        center_of_mass_x /= count;
        center_of_mass_y /= count;

        // Dirección hacia el centro
        float steer_x = (center_of_mass_x - center_x) * BOID_COHESION_WEIGHT * delta_time;
        float steer_y = (center_of_mass_y - center_y) * BOID_COHESION_WEIGHT * delta_time;

        // Limitar fuerza máxima de steering
        float steer_mag = std::sqrt(steer_x * steer_x + steer_y * steer_y);
        if (steer_mag > BOID_MAX_FORCE) {
            steer_x = (steer_x / steer_mag) * BOID_MAX_FORCE;
            steer_y = (steer_y / steer_mag) * BOID_MAX_FORCE;
        }

        float vx, vy;
        boid->getVelocity(vx, vy);
        vx += steer_x;
        vy += steer_y;
        boid->setVelocity(vx, vy);
    }
}

void BoidManager::applyBoundaries(Ball* boid) {
    // Mantener boids dentro de los límites de la pantalla
    // Comportamiento "wrapping" (teletransporte al otro lado)
    SDL_FRect pos = boid->getPosition();
    float center_x = pos.x + pos.w / 2.0f;
    float center_y = pos.y + pos.h / 2.0f;

    bool wrapped = false;

    if (center_x < 0) {
        pos.x = screen_width_ - pos.w / 2.0f;
        wrapped = true;
    } else if (center_x > screen_width_) {
        pos.x = -pos.w / 2.0f;
        wrapped = true;
    }

    if (center_y < 0) {
        pos.y = screen_height_ - pos.h / 2.0f;
        wrapped = true;
    } else if (center_y > screen_height_) {
        pos.y = -pos.h / 2.0f;
        wrapped = true;
    }

    if (wrapped) {
        boid->setPosition(pos.x, pos.y);
    }
}

void BoidManager::limitSpeed(Ball* boid) {
    // Limitar velocidad máxima del boid
    float vx, vy;
    boid->getVelocity(vx, vy);

    float speed = std::sqrt(vx * vx + vy * vy);
    if (speed > BOID_MAX_SPEED) {
        vx = (vx / speed) * BOID_MAX_SPEED;
        vy = (vy / speed) * BOID_MAX_SPEED;
        boid->setVelocity(vx, vy);
    }
}
