#include "background.h"
#include <SDL2/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL2/SDL_pixels.h>    // Para SDL_PIXELFORMAT_RGBA8888
#include <algorithm>            // Para clamp, max
#include "moving_sprite.h"      // Para MovingSprite
#include "param.h"              // Para Param, ParamBackground, param
#include "resource.h"           // Para Resource
#include "screen.h"             // Para Screen
#include "sprite.h"             // Para Sprite
#include "texture.h"            // Para Texture

// Constructor
Background::Background()
    : renderer_(Screen::get()->getRenderer()),

      buildings_texture_(Resource::get()->getTexture("game_buildings.png")),
      top_clouds_texture_(Resource::get()->getTexture("game_clouds1.png")),
      bottom_clouds_texture_(Resource::get()->getTexture("game_clouds2.png")),
      grass_texture_(Resource::get()->getTexture("game_grass.png")),
      gradients_texture_(Resource::get()->getTexture("game_sky_colors.png")),

      rect_({0, 0, gradients_texture_->getWidth() / 2, gradients_texture_->getHeight() / 2}),
      src_rect_({0, 0, 320, 240}),
      dst_rect_({0, 0, 320, 240}),
      base_(rect_.h),
      color_(Color(param.background.attenuate_color.r, param.background.attenuate_color.g, param.background.attenuate_color.b)),
      alpha_color_text_(param.background.attenuate_alpha),
      alpha_color_text_temp_(param.background.attenuate_alpha)

{
    // Inicializa variables
    {
        gradient_rect_[0] = {0, 0, rect_.w, rect_.h};
        gradient_rect_[1] = {rect_.w, 0, rect_.w, rect_.h};
        gradient_rect_[2] = {0, rect_.h, rect_.w, rect_.h};
        gradient_rect_[3] = {rect_.w, rect_.h, rect_.w, rect_.h};

        const int top_clouds_texture_height = top_clouds_texture_->getHeight() / 4;
        const int bottom_clouds_texture_height = bottom_clouds_texture_->getHeight() / 4;
        for (int i = 0; i < 4; ++i)
        {
            top_clouds_rect_[i] = {0, i * top_clouds_texture_height, top_clouds_texture_->getWidth(), top_clouds_texture_height};
            bottom_clouds_rect_[i] = {0, i * bottom_clouds_texture_height, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_height};
        }
    }

    // Crea los sprites
    {
        const int top_clouds_y = base_ - 165;
        const int bottom_clouds_y = base_ - 101;

        top_clouds_sprite_a_ = std::make_unique<MovingSprite>(top_clouds_texture_, (SDL_Rect){0, top_clouds_y, rect_.w, top_clouds_texture_->getHeight()});
        top_clouds_sprite_b_ = std::make_unique<MovingSprite>(top_clouds_texture_, (SDL_Rect){rect_.w, top_clouds_y, rect_.w, top_clouds_texture_->getHeight()});

        bottom_clouds_sprite_a_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_Rect){0, bottom_clouds_y, rect_.w, bottom_clouds_texture_->getHeight()});
        bottom_clouds_sprite_b_ = std::make_unique<MovingSprite>(bottom_clouds_texture_, (SDL_Rect){rect_.w, bottom_clouds_y, rect_.w, bottom_clouds_texture_->getHeight()});

        buildings_sprite_ = std::make_unique<Sprite>(buildings_texture_, 0, 0, buildings_texture_->getWidth(), buildings_texture_->getHeight());
        gradient_sprite_ = std::make_unique<Sprite>(gradients_texture_, 0, 0, rect_.w, rect_.h);
        grass_sprite_ = std::make_unique<Sprite>(grass_texture_, 0, 0, grass_texture_->getWidth(), grass_texture_->getHeight() / 2);
    }

    // Inicializa objetos
    {
        constexpr float top_clouds_speed = 0.1f;
        constexpr float bottom_clouds_speed = 0.05f;

        top_clouds_sprite_a_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
        top_clouds_sprite_a_->setVelX(-top_clouds_speed);

        top_clouds_sprite_b_->setSpriteClip(0, 0, top_clouds_texture_->getWidth(), top_clouds_texture_->getHeight());
        top_clouds_sprite_b_->setVelX(-top_clouds_speed);

        bottom_clouds_sprite_a_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
        bottom_clouds_sprite_a_->setVelX(-bottom_clouds_speed);

        bottom_clouds_sprite_b_->setSpriteClip(0, 0, bottom_clouds_texture_->getWidth(), bottom_clouds_texture_->getHeight());
        bottom_clouds_sprite_b_->setVelX(-bottom_clouds_speed);

        buildings_sprite_->setY(base_ - buildings_sprite_->getHeight());
        grass_sprite_->setY(base_ - grass_sprite_->getHeight());
    }

    // Crea la textura para componer el fondo
    canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
    SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);

    // Crea la textura para atenuar el fondo
    color_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
    SDL_SetTextureBlendMode(color_texture_, SDL_BLENDMODE_BLEND);
    setColor(color_);
    SDL_SetTextureAlphaMod(color_texture_, alpha_color_text_);
}

// Destructor
Background::~Background()
{
    SDL_DestroyTexture(canvas_);
    SDL_DestroyTexture(color_texture_);
}

// Actualiza la lógica del objeto
void Background::update()
{
    // Actualiza el valor de alpha_
    updateAlphaColorText();

    // Actualiza las nubes
    updateClouds();

    // Calcula el frame de la hierba
    grass_sprite_->setSpriteClip(0, (10 * (counter_ / 20 % 2)), 320, 10);

    // Calcula el valor de alpha_
    alpha_ = std::max((255 - (int)(255 * transition_)), 0);

    // Incrementa el contador
    ++counter_;

    // Compone todos los elementos del fondo en la textura
    fillCanvas();
}

// Dibuja el gradiente de fondo
void Background::renderGradient()
{
    // Dibuja el gradiente de detras
    gradients_texture_->setAlpha(255);
    gradient_sprite_->setSpriteClip(gradient_rect_[(gradient_number_ + 1) % 4]);
    gradient_sprite_->render();

    // Dibuja el gradiente de delante con una opacidad cada vez menor
    gradients_texture_->setAlpha(alpha_);
    gradient_sprite_->setSpriteClip(gradient_rect_[gradient_number_]);
    gradient_sprite_->render();
}

// Dibuja las nubes de arriba
void Background::renderTopClouds()
{
    // Dibuja el primer conjunto de nubes, las de detras
    top_clouds_texture_->setAlpha(255);
    top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]);
    top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[(gradient_number_ + 1) % 4]);
    top_clouds_sprite_a_->render();
    top_clouds_sprite_b_->render();

    // Dibuja el segundo conjunto de nubes, las de delante
    top_clouds_texture_->setAlpha(alpha_);
    top_clouds_sprite_a_->setSpriteClip(top_clouds_rect_[gradient_number_]);
    top_clouds_sprite_b_->setSpriteClip(top_clouds_rect_[gradient_number_]);
    top_clouds_sprite_a_->render();
    top_clouds_sprite_b_->render();
}

// Dibuja las nubes de abajo
void Background::renderBottomClouds()
{
    // Dibuja el primer conjunto de nubes, las de detras
    bottom_clouds_texture_->setAlpha(255);
    bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]);
    bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[(gradient_number_ + 1) % 4]);
    bottom_clouds_sprite_a_->render();
    bottom_clouds_sprite_b_->render();

    // Dibuja el segundo conjunto de nubes, las de delante
    bottom_clouds_texture_->setAlpha(alpha_);
    bottom_clouds_sprite_a_->setSpriteClip(bottom_clouds_rect_[gradient_number_]);
    bottom_clouds_sprite_b_->setSpriteClip(bottom_clouds_rect_[gradient_number_]);
    bottom_clouds_sprite_a_->render();
    bottom_clouds_sprite_b_->render();
}

// Compone todos los elementos del fondo en la textura
void Background::fillCanvas()
{
    // Cambia el destino del renderizador
    auto temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, canvas_);

    // Dibuja el gradiente de fondo
    renderGradient();

    // Dibuja las nubes de arriba
    renderTopClouds();

    // Dibuja las nubes de abajo
    renderBottomClouds();

    // Dibuja los edificios
    buildings_sprite_->render();

    // Dibuja la hierba
    grass_sprite_->render();

    // Deja el renderizador apuntando donde estaba
    SDL_SetRenderTarget(renderer_, temp);
}

// Dibuja el objeto
void Background::render()
{
    // Fondo
    SDL_RenderCopy(renderer_, canvas_, &src_rect_, &dst_rect_);

    // Atenuación
    SDL_RenderCopy(renderer_, color_texture_, &src_rect_, &dst_rect_);
}

// Vuelve a cargar las texturas
void Background::reloadTextures()
{
    buildings_texture_->reLoad();
    top_clouds_texture_->reLoad();
    bottom_clouds_texture_->reLoad();
    grass_texture_->reLoad();
    gradients_texture_->reLoad();
}

// Ajusta el valor de la variable
void Background::setCloudsSpeed(float value)
{
    clouds_speed_ = value;
}

// Ajusta el valor de la variable
void Background::setGradientNumber(int value)
{
    gradient_number_ = value % 4;
}

// Ajusta el valor de la variable
void Background::setTransition(float value)
{
    transition_ = std::clamp(value, 0.0f, 1.0f);
}

// Establece la posición del objeto
void Background::setPos(SDL_Rect pos)
{
    dst_rect_ = pos;

    // Si cambian las medidas del destino, hay que cambiar las del origen para evitar deformar la imagen
    src_rect_.x = 0;
    src_rect_.y = rect_.h - pos.h;
    src_rect_.w = pos.w;
    src_rect_.h = pos.h;
}

// Establece el color_ de atenuación
void Background::setColor(Color color)
{
    color_ = color;

    // Colorea la textura
    auto temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, color_texture_);

    SDL_SetRenderDrawColor(renderer_, color_.r, color_.g, color_.b, 255);
    SDL_RenderClear(renderer_);

    SDL_SetRenderTarget(renderer_, temp);
}

// Establece la transparencia de la atenuación
void Background::setAlpha(int alpha)
{
    // Evita que se asignen valores fuera de rango
    alpha_ = std::clamp(alpha, 0, 255);

    // Guarda el valor actual y establece el nuevo valor
    alpha_color_text_temp_ = alpha_color_text_;
    alpha_color_text_ = alpha_;
}

// Actualiza el valor de alpha_
void Background::updateAlphaColorText()
{
    if (alpha_color_text_ == alpha_color_text_temp_)
    {
        return;
    }
    else
    {
        alpha_color_text_ > alpha_color_text_temp_ ? ++alpha_color_text_temp_ : --alpha_color_text_temp_;
        SDL_SetTextureAlphaMod(color_texture_, alpha_color_text_temp_);
    }
}

// Actualiza las nubes
void Background::updateClouds()
{
    // Aplica la velocidad calculada a las nubes
    top_clouds_sprite_a_->setVelX(clouds_speed_);
    top_clouds_sprite_b_->setVelX(clouds_speed_);
    bottom_clouds_sprite_a_->setVelX(clouds_speed_ / 2);
    bottom_clouds_sprite_b_->setVelX(clouds_speed_ / 2);

    // Mueve las nubes
    top_clouds_sprite_a_->update();
    top_clouds_sprite_b_->update();
    bottom_clouds_sprite_a_->update();
    bottom_clouds_sprite_b_->update();

    // Calcula el offset de las nubes
    if (top_clouds_sprite_a_->getPosX() < -top_clouds_sprite_a_->getWidth())
    {
        top_clouds_sprite_a_->setPosX(top_clouds_sprite_a_->getWidth());
    }

    if (top_clouds_sprite_b_->getPosX() < -top_clouds_sprite_b_->getWidth())
    {
        top_clouds_sprite_b_->setPosX(top_clouds_sprite_b_->getWidth());
    }

    if (bottom_clouds_sprite_a_->getPosX() < -bottom_clouds_sprite_a_->getWidth())
    {
        bottom_clouds_sprite_a_->setPosX(bottom_clouds_sprite_a_->getWidth());
    }

    if (bottom_clouds_sprite_b_->getPosX() < -bottom_clouds_sprite_b_->getWidth())
    {
        bottom_clouds_sprite_b_->setPosX(bottom_clouds_sprite_b_->getWidth());
    }
}