#include "core/rendering/tiled_bg.hpp"

#include <SDL3/SDL.h>  // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess

#include <algorithm>  // Para max
#include <cmath>      // Para cos, pow, sin
#include <cstdlib>    // Para rand
#include <memory>     // Para unique_ptr, make_unique
#include <numbers>    // Para pi
#include <string>     // Para basic_string

#include "core/rendering/screen.hpp"         // Para Screen
#include "core/rendering/sprite/sprite.hpp"  // Para Sprite
#include "core/resources/resource.hpp"       // Para Resource

// Constructor
TiledBG::TiledBG(SDL_FRect pos, TiledBGMode mode)
    : renderer_(Screen::get()->getRenderer()),
      pos_(pos),
      mode_(mode == TiledBGMode::RANDOM ? static_cast<TiledBGMode>(rand() % 2) : mode) {
    // Crea la textura para el mosaico de fondo
    canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, pos_.w * 2, pos_.h * 2);

    // Rellena la textura con el contenido
    fillTexture();

    // Inicializa variables
    switch (mode_) {
        case TiledBGMode::STATIC:
            window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
            speed_ = 0.0F;
            break;
        case TiledBGMode::DIAGONAL:
            window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
            break;
        case TiledBGMode::CIRCLE:
            window_ = {.x = 128, .y = 128, .w = pos_.w, .h = pos_.h};
            break;
        default:
            window_ = {.x = 0, .y = 0, .w = pos_.w, .h = pos_.h};
            break;
    }
}

// Destructor
TiledBG::~TiledBG() {
    SDL_DestroyTexture(canvas_);
}

// Rellena la textura con el contenido
void TiledBG::fillTexture() {
    // Crea los objetos para pintar en la textura de fondo
    auto tile = std::make_unique<Sprite>(Resource::get()->getTexture("title_bg_tile.png"), (SDL_FRect){.x = 0, .y = 0, .w = TILE_WIDTH, .h = TILE_HEIGHT});

    // Prepara para dibujar sobre la textura
    auto* temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, canvas_);

    // Rellena la textura con el tile
    const auto I_MAX = pos_.w * 2 / TILE_WIDTH;
    const auto J_MAX = pos_.h * 2 / TILE_HEIGHT;
    tile->setSpriteClip(0, 0, TILE_WIDTH, TILE_HEIGHT);
    for (int i = 0; i < I_MAX; ++i) {
        for (int j = 0; j < J_MAX; ++j) {
            tile->setX(i * TILE_WIDTH);
            tile->setY(j * TILE_HEIGHT);
            tile->render();
        }
    }

    // Vuelve a colocar el renderizador como estaba
    SDL_SetRenderTarget(renderer_, temp);
}

// Pinta la clase en pantalla
void TiledBG::render() {
    SDL_RenderTexture(renderer_, canvas_, &window_, &pos_);
}

// Actualiza la lógica de la clase (time-based)
void TiledBG::update(float delta_time) {
    updateSpeedChange(delta_time);
    updateDesp(delta_time);
    updateStop(delta_time);

    switch (mode_) {
        case TiledBGMode::DIAGONAL: {
            // El tileado de fondo se desplaza en diagonal
            window_.x = static_cast<int>(desp_) % TILE_WIDTH;
            window_.y = static_cast<int>(desp_) % TILE_HEIGHT;

            break;
        }
        case TiledBGMode::CIRCLE: {
            // El tileado de fondo se desplaza en circulo
            const float ANGLE_RAD = (desp_ * std::numbers::pi / 180.0F);

            window_.x = 128 + static_cast<int>(std::cos(ANGLE_RAD) * 128);
            window_.y = 128 + static_cast<int>(std::sin(-ANGLE_RAD) * 96);
            break;
        }
        default:
            break;
    }
}

// Detiene el desplazamiento de forma ordenada (time-based)
void TiledBG::updateStop(float delta_time) {
    if (stopping_) {
        const int UMBRAL = STOP_THRESHOLD_FACTOR * speed_;

        // Desacelerar si estamos cerca de completar el ciclo (ventana a punto de regresar a 0)
        if (window_.x >= TILE_WIDTH - UMBRAL) {
            // Aplicar desaceleración time-based
            float frame_rate = 60.0F;
            float deceleration_per_ms = std::pow(DECELERATION_FACTOR, frame_rate * delta_time / 1000.0F);
            speed_ /= deceleration_per_ms;

            // Asegura que no baje demasiado
            speed_ = std::max(speed_, MIN_SPEED);
        }

        // Si estamos en 0, detener
        if (window_.x == 0) {
            speed_ = 0.0F;
            stopping_ = false;  // Desactivamos el estado de "stopping"
        }
    }
}

// Cambia la velocidad gradualmente en X segundos
void TiledBG::changeSpeedTo(float target_speed, float duration_s) {
    if (duration_s <= 0.0F) {
        // Si la duración es 0 o negativa, cambia inmediatamente
        speed_ = target_speed;
        changing_speed_ = false;
        return;
    }

    // Configurar el cambio gradual
    changing_speed_ = true;
    initial_speed_ = speed_;
    target_speed_ = target_speed;
    change_duration_s_ = duration_s;
    change_timer_s_ = 0.0F;
}

// Actualiza el cambio gradual de velocidad (time-based)
void TiledBG::updateSpeedChange(float delta_time) {
    if (!changing_speed_) {
        return;
    }

    change_timer_s_ += delta_time;

    if (change_timer_s_ >= change_duration_s_) {
        // Cambio completado
        speed_ = target_speed_;
        changing_speed_ = false;
    } else {
        // Interpolación lineal entre velocidad inicial y objetivo
        float progress = change_timer_s_ / change_duration_s_;
        speed_ = initial_speed_ + ((target_speed_ - initial_speed_) * progress);
    }
}