#pragma once

#include <SDL3/SDL.h>

#include <cstdint>  // for uint8_t
#include <string>   // for string, basic_string
#include <vector>   // for vector

#include "game/entities/bullet.h"  // for Bullet::Kind (signatura de createBullet)
#include "game/entities/item.h"    // for Item::Id (signatura de dropItem/createItem)
#include "utils/utils.h"           // for DemoKeys, Color
class Balloon;
class Fade;
class Menu;
class MovingSprite;
class Player;
class SmartSprite;
class Sprite;
class Text;
class Texture;
namespace Ja {
    struct Music;
    struct Sound;
}  // namespace Ja

// Clase Game
class Game {
   public:
    Game(int num_players, int current_stage, SDL_Renderer *renderer, bool demo, Section *section);  // Constructor
    ~Game();                                                                                        // Destructor

    Game(const Game &) = delete;
    auto operator=(const Game &) -> Game & = delete;

    void run();                                      // Bucle para el juego
    void iterate();                                  // Ejecuta un frame del juego
    [[nodiscard]] auto hasFinished() const -> bool;  // Indica si el juego ha terminado
    void handleEvent(const SDL_Event *event);        // Procesa un evento

   private:
    // Cantidad de elementos a escribir en los ficheros de datos
    static constexpr int TOTAL_SCORE_DATA = 3;
    static constexpr int TOTAL_DEMO_DATA = 2000;

    // Contadores
    static constexpr int STAGE_COUNTER = 200;
    static constexpr int SHAKE_COUNTER = 10;
    static constexpr int HELP_COUNTER = 1000;
    static constexpr int GAME_COMPLETED_START_FADE = 500;
    static constexpr int GAME_COMPLETED_END = 700;

    // Formaciones enemigas
    static constexpr int NUMBER_OF_ENEMY_FORMATIONS = 100;
    static constexpr int MAX_NUMBER_OF_ENEMIES_IN_A_FORMATION = 50;

    // Porcentaje de aparición de los objetos
    static constexpr int ITEM_POINTS_1_DISK_ODDS = 10;
    static constexpr int ITEM_POINTS_2_GAVINA_ODDS = 6;
    static constexpr int ITEM_POINTS_3_PACMAR_ODDS = 3;
    static constexpr int ITEM_CLOCK_ODDS = 5;
    static constexpr int ITEM_COFFEE_ODDS = 5;
    static constexpr int ITEM_COFFEE_MACHINE_ODDS = 4;

    // Valores para las variables asociadas a los objetos
    static constexpr int TIME_STOPPED_COUNTER = 300;

    struct EnemyInit {
        int x;                    // Posición en el eje X donde crear al enemigo
        int y;                    // Posición en el eje Y donde crear al enemigo
        float vel_x;              // Velocidad inicial en el eje X
        Uint8 kind;               // Tipo de enemigo
        Uint16 creation_counter;  // Temporizador para la creación del enemigo
    };

    struct EnemyFormation  // Contiene la información de una formación enemiga
    {
        Uint8 number_of_enemies;                               // Cantidad de enemigos que forman la formación
        EnemyInit init[MAX_NUMBER_OF_ENEMIES_IN_A_FORMATION];  // Vector con todas las inicializaciones de los enemigos de la formación
    };

    struct EnemyPool {
        EnemyFormation *set[10];  // Conjunto de formaciones enemigas
    };

    struct Stage  // Contiene todas las variables relacionadas con una fase
    {
        EnemyPool *enemy_pool;     // El conjunto de formaciones enemigas de la fase
        Uint16 current_power;      // Cantidad actual de poder
        Uint16 power_to_complete;  // Cantidad de poder que se necesita para completar la fase
        Uint8 max_menace;          // Umbral máximo de amenaza de la fase
        Uint8 min_menace;          // Umbral mínimo de amenaza de la fase
        Uint8 number;              // Numero de fase
    };

    struct Effect {
        bool flash;           // Indica si se ha de pintar la pantalla de blanco
        bool shake;           // Indica si se ha de agitar la pantalla
        Uint8 shake_counter;  // Contador para medir el tiempo que dura el efecto
    };

    // Estado para el efecto de agitación intensa (muerte del jugador)
    struct DeathShake {
        bool active;             // Indica si el efecto está activo
        Uint8 step;              // Paso actual del efecto (0-7)
        Uint32 last_step_ticks;  // Ticks del último paso
    };

    // Fases de la secuencia de muerte del jugador
    enum class DeathPhase : std::uint8_t {
        NONE,
        SHAKING,
        WAITING,
        DONE
    };

    // Estado de la secuencia de muerte del jugador
    struct DeathSequence {
        DeathPhase phase;          // Fase actual
        Uint32 phase_start_ticks;  // Ticks del inicio de la fase actual
        Player *player;            // Jugador que está muriendo
    };

    struct Helper {
        bool need_coffee;              // Indica si se necesitan cafes
        bool need_coffee_machine;      // Indica si se necesita PowerUp
        bool need_power_ball;          // Indica si se necesita una PowerBall
        int counter;                   // Contador para no dar ayudas consecutivas
        int item_disk_odds;            // Probabilidad de aparición del objeto
        int item_gavina_odds;          // Probabilidad de aparición del objeto
        int item_paco_odds;            // Probabilidad de aparición del objeto
        int item_clock_odds;           // Probabilidad de aparición del objeto
        int item_coffee_odds;          // Probabilidad de aparición del objeto
        int item_coffee_machine_odds;  // Probabilidad de aparición del objeto
    };

    struct Demo {
        bool enabled;                         // Indica si está activo el modo demo
        bool recording;                       // Indica si está activado el modo para grabar la demo
        Uint16 counter;                       // Contador para el modo demo
        DemoKeys keys;                        // Variable con las pulsaciones de teclas del modo demo
        DemoKeys data_file[TOTAL_DEMO_DATA];  // Datos del fichero con los movimientos para la demo
    };

    void update();     // Actualiza el juego
    void render();     // Dibuja el juego
    void init();       // Inicializa las variables necesarias para la sección 'Game'
    void loadMedia();  // Carga los recursos necesarios para la sección 'Game'

    auto loadScoreFile() -> bool;  // Carga el fichero de puntos
    auto loadDemoFile() -> bool;   // Carga el fichero de datos para la demo
    auto saveScoreFile() -> bool;  // Guarda el fichero de puntos
    auto saveDemoFile() -> bool;   // Guarda el fichero de datos para la demo

    void initEnemyFormations();                 // Inicializa las formaciones enemigas
    void initEnemyFormationsZero();             // Helper de initEnemyFormations
    void initEnemyFormationsLinear();           // Helper de initEnemyFormations
    void initEnemyFormationsSymmetric();        // Helper de initEnemyFormations
    void initEnemyFormationsHexagonsAndTest();  // Helper de initEnemyFormations

    void initEnemyPools();                        // Inicializa los conjuntos de formaciones
    void initGameStages();                        // Inicializa las fases del juego
    void deployEnemyFormation();                  // Crea una formación de enemigos
    void increaseStageCurrentPower(Uint8 power);  // Aumenta el poder de la fase

    void setHiScore(Uint32 score);                           // Establece el valor de la variable
    void updateHiScore();                                    // Actualiza el valor de HiScore en caso necesario
    static auto updateScoreText(Uint32 num) -> std::string;  // Transforma un valor numérico en una cadena de 6 cifras
    void renderScoreBoard();                                 // Pinta el marcador en pantalla usando un objeto texto

    void updatePlayers();  // Actualiza las variables del jugador
    void renderPlayers();  // Dibuja a los jugadores

    void updateStage();                 // Actualiza las variables de la fase
    void updateDeath();                 // Actualiza el estado de muerte
    void renderDeathFade(int counter);  // Renderiza el fade final cuando se acaba la partida

    void updateBalloons();                                                                                     // Actualiza los globos
    void renderBalloons();                                                                                     // Pinta en pantalla todos los globos activos
    auto createBalloon(float x, float y, Uint8 kind, float velx, float speed, Uint16 creationtimer) -> Uint8;  // Crea un globo nuevo en el vector de globos
    void createPowerBall();                                                                                    // Crea una PowerBall
    void setBalloonSpeed(float speed);                                                                         // Establece la velocidad de los globos
    void incBalloonSpeed();                                                                                    // Incrementa la velocidad de los globos
    void updateBalloonSpeed();                                                                                 // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
    void popBalloon(Balloon *balloon);                                                                         // Explosiona un globo. Lo destruye y crea otros dos si es el caso
    void destroyBalloon(Balloon *balloon);                                                                     // Explosiona un globo. Lo destruye
    void destroyAllBalloons();                                                                                 // Destruye todos los globos
    void stopAllBalloons(Uint16 time);                                                                         // Detiene todos los globos
    void startAllBalloons();                                                                                   // Pone en marcha todos los globos
    void freeBalloons();                                                                                       // Vacia el vector de globos

    auto checkPlayerBalloonCollision(Player *player) -> bool;        // Comprueba la colisión entre el jugador y los globos activos
    void checkPlayerItemCollision(Player *player);                   // Comprueba la colisión entre el jugador y los items
    void checkBulletBalloonCollision();                              // Comprueba la colisión entre las balas y los globos
    void resolveBulletBalloonHit(Bullet *bullet, Balloon *balloon);  // Resuelve un impacto bala-globo (helper de checkBulletBalloonCollision)

    void moveBullets();                                                              // Mueve las balas activas
    void renderBullets();                                                            // Pinta las balas activas
    void createBullet(int x, int y, Bullet::Kind kind, bool powered_up, int owner);  // Crea un objeto bala
    void freeBullets();                                                              // Vacia el vector de balas

    void updateItems();                                // Actualiza los items
    void renderItems();                                // Pinta los items activos
    auto dropItem() -> Item::Id;                       // Devuelve un item en función del azar
    void createItem(Item::Id kind, float x, float y);  // Crea un objeto item
    void freeItems();                                  // Vacia el vector de items

    void createItemScoreSprite(int x, int y, const SmartSprite *sprite);  // Crea un objeto SmartSprite
    void freeSmartSprites();                                              // Vacia el vector de smartsprites

    void renderFlashEffect();        // Dibuja el efecto de flash
    void updateShakeEffect();        // Actualiza el efecto de agitar la pantalla
    void throwCoffee(int x, int y);  // Crea un SmartSprite para arrojar el item café al recibir un impacto
    void updateSmartSprites();       // Actualiza los SmartSprites
    void renderSmartSprites();       // Pinta los SmartSprites activos

    void killPlayer(Player *player);                // Acciones a realizar cuando el jugador muere
    void evaluateAndSetMenace();                    // Calcula y establece el valor de amenaza en funcion de los globos activos
    [[nodiscard]] auto getMenace() const -> Uint8;  // Obtiene el valor de la variable

    void setTimeStopped(bool value);                   // Establece el valor de la variable
    [[nodiscard]] auto isTimeStopped() const -> bool;  // Obtiene el valor de la variable
    void setTimeStoppedCounter(Uint16 value);          // Establece el valor de la variable
    void incTimeStoppedCounter(Uint16 value);          // Incrementa el valor de la variable

    void updateEnemyDeployCounter();  // Actualiza la variable EnemyDeployCounter
    void updateTimeStoppedCounter();  // Actualiza y comprueba el valor de la variable
    void updateMenace();              // Gestiona el nivel de amenaza
    void updateBackground();          // Actualiza el fondo
    void renderBackground();          // Dibuja el fondo

    void checkGameInput();                               // Gestiona la entrada durante el juego
    void processDemoInput();                             // Helper de checkGameInput
    void processLiveInput();                             // Helper de checkGameInput
    void processPlayerLiveInput(Player *player, int i);  // Helper de checkGameInput

    void renderMessages();                              // Pinta diferentes mensajes en la pantalla
    void enableTimeStopItem();                          // Habilita el efecto del item de detener el tiempo
    void disableTimeStopItem();                         // Deshabilita el efecto del item de detener el tiempo
    void shakeScreen();                                 // Inicia el efecto de agitación intensa de la pantalla
    void updateDeathShake();                            // Actualiza el efecto de agitación intensa
    [[nodiscard]] auto isDeathShaking() const -> bool;  // Indica si el efecto de agitación intensa está activo
    void updateDeathSequence();                         // Actualiza la secuencia de muerte del jugador

    void updatePausedGame();        // Actualiza las variables del menu de pausa del juego
    void updateLeavingPauseMenu();  // Helper de updatePausedGame
    void updatePauseMenuUI();       // Helper de updatePausedGame
    void renderPausedGame();        // Dibuja el menu de pausa del juego
    void enterPausedGame();         // Inicializa el estado de pausa del juego

    void updateGameOverScreen();  // Actualiza los elementos de la pantalla de game over
    void renderGameOverScreen();  // Dibuja los elementos de la pantalla de game over
    void enterGameOverScreen();   // Inicializa el estado de game over

    auto canPowerBallBeCreated() -> bool;  // Indica si se puede crear una powerball
    auto calculateScreenPower() -> int;    // Calcula el poder actual de los globos en pantalla
    void initPaths();                      // Inicializa las variables que contienen puntos de ruta para mover objetos
    void updateGameCompleted();            // Actualiza el tramo final de juego, una vez completado
    void updateHelper();                   // Actualiza las variables de ayuda
    auto allPlayersAreDead() -> bool;      // Comprueba si todos los jugadores han muerto
    void deleteAllVectorObjects();         // Elimina todos los objetos contenidos en vectores
    void setHiScore();                     // Establece la máxima puntuación desde fichero o desde las puntuaciones online

    // Objetos y punteros
    SDL_Renderer *renderer_;  // El renderizador de la ventana
    Section *section_;        // Seccion actual dentro del juego

    std::vector<Player *> players_;             // Vector con los jugadores
    std::vector<Balloon *> balloons_;           // Vector con los globos
    std::vector<Bullet *> bullets_;             // Vector con las balas
    std::vector<Item *> items_;                 // Vector con los items
    std::vector<SmartSprite *> smart_sprites_;  // Vector con los smartsprites

    Texture *bullet_texture_;                              // Textura para las balas
    std::vector<Texture *> item_textures_;                 // Vector con las texturas de los items
    std::vector<Texture *> balloon_textures_;              // Vector con las texturas de los globos
    std::vector<Texture *> player1_textures_;              // Vector con las texturas del jugador
    std::vector<Texture *> player2_textures_;              // Vector con las texturas del jugador
    std::vector<std::vector<Texture *>> player_textures_;  // Vector con todas las texturas de los jugadores;

    Texture *game_buildings_texture_;    // Textura con los edificios de fondo
    Texture *game_clouds_texture_;       // Textura con las nubes de fondo
    Texture *game_grass_texture_;        // Textura con la hierba del suelo
    Texture *game_power_meter_texture_;  // Textura con el marcador de poder de la fase
    Texture *game_sky_colors_texture_;   // Textura con los diferentes colores de fondo del juego
    Texture *game_text_texture_;         // Textura para los sprites con textos
    Texture *game_over_texture_;         // Textura para la pantalla de game over
    Texture *game_over_end_texture_;     // Textura para la pantalla de game over de acabar el juego

    std::vector<std::vector<std::string> *> item_animations_;     // Vector con las animaciones de los items
    std::vector<std::vector<std::string> *> player_animations_;   // Vector con las animaciones del jugador
    std::vector<std::vector<std::string> *> balloon_animations_;  // Vector con las animaciones de los globos

    Text *text_;             // Fuente para los textos del juego
    Text *text_big_;         // Fuente de texto grande
    Text *text_scoreboard_;  // Fuente para el marcador del juego
    Text *text_nokia2_;      // Otra fuente de texto para mensajes
    Text *text_nokia_big2_;  // Y la versión en grande

    Menu *game_over_menu_;  // Menú de la pantalla de game over
    Menu *pause_menu_;      // Menú de la pantalla de pausa

    Fade *fade_;                // Objeto para renderizar fades
    SDL_Event *event_handler_;  // Manejador de eventos

    MovingSprite *clouds1_a_;    // Sprite para las nubes superiores
    MovingSprite *clouds1_b_;    // Sprite para las nubes superiores
    MovingSprite *clouds2_a_;    // Sprite para las nubes inferiores
    MovingSprite *clouds2_b_;    // Sprite para las nubes inferiores
    SmartSprite *n1000_sprite_;  // Sprite con el texto 1.000
    SmartSprite *n2500_sprite_;  // Sprite con el texto 2.500
    SmartSprite *n5000_sprite_;  // Sprite con el texto 5.000

    Sprite *buildings_sprite_;      // Sprite con los edificios de fondo
    Sprite *sky_colors_sprite_;     // Sprite con los graficos del degradado de color de fondo
    Sprite *grass_sprite_;          // Sprite para la hierba
    Sprite *power_meter_sprite_;    // Sprite para el medidor de poder de la fase
    Sprite *game_over_sprite_;      // Sprite para dibujar los graficos del game over
    Sprite *game_over_end_sprite_;  // Sprite para dibujar los graficos del game over de acabar el juego

    Ja::Sound *balloon_sound_;           // Sonido para la explosión del globo
    Ja::Sound *bullet_sound_;            // Sonido para los disparos
    Ja::Sound *player_collision_sound_;  // Sonido para la colisión del jugador con un enemigo
    Ja::Sound *hi_score_sound_;          // Sonido para cuando se alcanza la máxima puntuación
    Ja::Sound *item_drop_sound_;         // Sonido para cuando se genera un item
    Ja::Sound *item_pick_up_sound_;      // Sonido para cuando se recoge un item
    Ja::Sound *coffee_out_sound_;        // Sonido para cuando el jugador pierde el café al recibir un impacto
    Ja::Sound *stage_change_sound_;      // Sonido para cuando se cambia de fase
    Ja::Sound *bubble1_sound_;           // Sonido para cuando el jugador muere
    Ja::Sound *bubble2_sound_;           // Sonido para cuando el jugador muere
    Ja::Sound *bubble3_sound_;           // Sonido para cuando el jugador muere
    Ja::Sound *bubble4_sound_;           // Sonido para cuando el jugador muere
    Ja::Sound *clock_sound_;             // Sonido para cuando se detiene el tiempo con el item reloj
    Ja::Sound *power_ball_sound_;        // Sonido para cuando se explota una Power Ball
    Ja::Sound *coffee_machine_sound_;    // Sonido para cuando la máquina de café toca el suelo

    Ja::Music *game_music_;  // Musica de fondo

    // Variables
    int num_players_;                                             // Numero de jugadores
    Uint32 ticks_;                                                // Contador de ticks para ajustar la velocidad del programa
    Uint8 ticks_speed_;                                           // Velocidad a la que se repiten los bucles del programa
    Uint32 hi_score_;                                             // Puntuación máxima
    bool hi_score_achieved_;                                      // Indica si se ha superado la puntuación máxima
    std::string hi_score_name_;                                   // Nombre del jugador que ostenta la máxima puntuación
    Stage stage_[10];                                             // Variable con los datos de cada pantalla
    Uint8 current_stage_;                                         // Indica la fase actual
    Uint8 stage_bitmap_counter_;                                  // Contador para el tiempo visible del texto de Stage
    float stage_bitmap_path_[STAGE_COUNTER];                      // Vector con los puntos Y por donde se desplaza el texto
    float get_ready_bitmap_path_[STAGE_COUNTER];                  // Vector con los puntos X por donde se desplaza el texto
    Uint16 death_counter_;                                        // Contador para la animación de muerte del jugador
    Uint8 menace_current_;                                        // Nivel de amenaza actual
    Uint8 menace_threshold_;                                      // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el numero de globos
    bool time_stopped_;                                           // Indica si el tiempo está detenido
    Uint16 time_stopped_counter_;                                 // Temporizador para llevar la cuenta del tiempo detenido
    Uint32 counter_;                                              // Contador para el juego
    Uint32 score_data_file_[TOTAL_SCORE_DATA];                    // Datos del fichero de puntos
    SDL_Rect sky_colors_rect_[4];                                 // Vector con las coordenadas de los 4 colores de cielo
    Uint16 balloons_popped_;                                      // Lleva la cuenta de los globos explotados
    Uint8 last_enemy_deploy_;                                     // Guarda cual ha sido la última formación desplegada para no repetir;
    int enemy_deploy_counter_;                                    // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero
    float enemy_speed_;                                           // Velocidad a la que se mueven los enemigos
    float default_enemy_speed_;                                   // Velocidad base de los enemigos, sin incrementar
    Effect effect_;                                               // Variable para gestionar los efectos visuales
    DeathShake death_shake_;                                      // Variable para gestionar el efecto de agitación intensa
    DeathSequence death_sequence_;                                // Variable para gestionar la secuencia de muerte
    Helper helper_;                                               // Variable para gestionar las ayudas
    bool power_ball_enabled_;                                     // Indica si hay una powerball ya activa
    Uint8 power_ball_counter_;                                    // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
    bool coffee_machine_enabled_;                                 // Indica si hay una máquina de café en el terreno de juego
    bool game_completed_;                                         // Indica si se ha completado la partida, llegando al final de la ultima pantalla
    int game_completed_counter_;                                  // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos
    Uint8 difficulty_;                                            // Dificultad del juego
    float difficulty_score_multiplier_;                           // Multiplicador de puntos en función de la dificultad
    Color difficulty_color_;                                      // Color asociado a la dificultad
    Uint8 player_one_control_;                                    // Variable para almacenar el valor de las opciones
    EnemyFormation enemy_formation_[NUMBER_OF_ENEMY_FORMATIONS];  // Vector con todas las formaciones enemigas
    EnemyPool enemy_pool_[10];                                    // Variable con los diferentes conjuntos de formaciones enemigas
    Uint8 last_stage_reached_;                                    // Contiene el numero de la última pantalla que se ha alcanzado
    Demo demo_;                                                   // Variable con todas las variables relacionadas con el modo demo
    int total_power_to_complete_game_;                            // La suma del poder necesario para completar todas las fases
    int clouds_speed_{0};                                         // Velocidad a la que se desplazan las nubes
    int pause_counter_;                                           // Contador para salir del menu de pausa y volver al juego
    bool leaving_pause_menu_;                                     // Indica si esta saliendo del menu de pausa para volver al juego
    bool pause_initialized_;                                      // Indica si la pausa ha sido inicializada
    bool game_over_initialized_;                                  // Indica si el game over ha sido inicializado
    int game_over_post_fade_;                                     // Opción a realizar cuando termina el fundido del game over
#ifdef PAUSE
    bool pause;
#endif
};
