#include "fade.h"

#include <SDL3/SDL.h>  // Para SDL_SetRenderTarget, SDL_FRect, SDL_GetRenderTarget, SDL_RenderFillRect, SDL_SetRenderDrawBlendMode, SDL_SetRenderDrawColor, Uint8, SDL_GetRenderDrawBlendMode, SDL_BLENDMODE_NONE, SDL_BlendMode, SDL_CreateTexture, SDL_DestroyTexture, SDL_RenderClear, SDL_RenderTexture, SDL_SetTextureAlphaMod, SDL_SetTextureBlendMode, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess

#include <algorithm>  // Para min, max
#include <cstdlib>    // Para rand, size_t

#include "color.h"   // Para Color
#include "param.h"   // Para Param, param, ParamGame, ParamFade
#include "screen.h"  // Para Screen

// Constructor
Fade::Fade()
    : renderer_(Screen::get()->getRenderer()) {
    // Crea la textura donde dibujar el fade
    backbuffer_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
    SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);

    // Inicializa las variables
    init();
}

// Destructor
Fade::~Fade() {
    SDL_DestroyTexture(backbuffer_);
}

// Inicializa las variables
void Fade::init() {
    type_ = FadeType::CENTER;
    mode_ = FadeMode::OUT;
    counter_ = 0;
    r_ = 0;
    g_ = 0;
    b_ = 0;
    a_ = 0;
    post_duration_ = 0;
    post_counter_ = 0;
    pre_duration_ = 0;
    pre_counter_ = 0;
    num_squares_width_ = param.fade.num_squares_width;
    num_squares_height_ = param.fade.num_squares_height;
    fade_random_squares_delay_ = param.fade.random_squares_delay;
    fade_random_squares_mult_ = param.fade.random_squares_mult;
}

// Resetea algunas variables para volver a hacer el fade sin perder ciertos parametros
void Fade::reset() {
    state_ = FadeState::NOT_ENABLED;
    counter_ = 0;
}

// Pinta una transición en pantalla
void Fade::render() {
    if (state_ != FadeState::NOT_ENABLED) {
        SDL_RenderTexture(renderer_, backbuffer_, nullptr, nullptr);
    }
}

// Actualiza las variables internas
void Fade::update() {
    switch (state_) {
        case FadeState::PRE:
            updatePreState();
            break;
        case FadeState::FADING:
            updateFadingState();
            break;
        case FadeState::POST:
            updatePostState();
            break;
        default:
            break;
    }
}

void Fade::updatePreState() {
    if (pre_counter_ == pre_duration_) {
        state_ = FadeState::FADING;
    } else {
        pre_counter_++;
    }
}

void Fade::updateFadingState() {
    switch (type_) {
        case FadeType::FULLSCREEN:
            updateFullscreenFade();
            break;
        case FadeType::CENTER:
            updateCenterFade();
            break;
        case FadeType::RANDOM_SQUARE:
            updateRandomSquareFade();
            break;
        case FadeType::VENETIAN:
            updateVenetianFade();
            break;
        default:
            break;
    }
    counter_++;
}

void Fade::updatePostState() {
    if (post_counter_ == post_duration_) {
        state_ = FadeState::FINISHED;
    } else {
        post_counter_++;
    }
    cleanBackbuffer(r_, g_, b_, a_);
}

void Fade::updateFullscreenFade() {
    // Modifica la transparencia
    a_ = mode_ == FadeMode::OUT ? std::min(counter_ * 4, 255) : 255 - std::min(counter_ * 4, 255);
    SDL_SetTextureAlphaMod(backbuffer_, a_);

    // Comprueba si ha terminado
    if (counter_ >= 255 / 4) {
        state_ = FadeState::POST;
    }
}

void Fade::updateCenterFade() {
    drawCenterFadeRectangles();

    // Comprueba si ha terminado
    if ((counter_ * 4) > param.game.height) {
        state_ = FadeState::POST;
        a_ = 255;
    }
}

void Fade::drawCenterFadeRectangles() {
    auto *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);
    SDL_SetRenderDrawColor(renderer_, r_, g_, b_, a_);

    for (int i = 0; i < counter_; i++) {
        rect1_.h = rect2_.h = i * 4;
        rect2_.y = param.game.height - (i * 4);

        SDL_RenderFillRect(renderer_, &rect1_);
        SDL_RenderFillRect(renderer_, &rect2_);

        value_ = calculateValue(0, counter_, i);
    }

    SDL_SetRenderTarget(renderer_, temp);
}

void Fade::updateRandomSquareFade() {
    if (counter_ % fade_random_squares_delay_ == 0) {
        drawRandomSquares();
    }

    value_ = calculateValue(0, (num_squares_width_ * num_squares_height_), (counter_ * fade_random_squares_mult_ / fade_random_squares_delay_));

    // Comprueba si ha terminado
    if (counter_ * fade_random_squares_mult_ / fade_random_squares_delay_ >=
        num_squares_width_ * num_squares_height_) {
        state_ = FadeState::POST;
    }
}

void Fade::drawRandomSquares() {
    auto *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);

    SDL_BlendMode blend_mode;
    SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
    SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
    SDL_SetRenderDrawColor(renderer_, r_, g_, b_, a_);

    const int INDEX = std::min(counter_ / fade_random_squares_delay_,
        (num_squares_width_ * num_squares_height_) - 1);

    for (int i = 0; i < fade_random_squares_mult_; ++i) {
        const int INDEX2 = std::min(INDEX * fade_random_squares_mult_ + i,
            static_cast<int>(square_.size()) - 1);
        SDL_RenderFillRect(renderer_, &square_[INDEX2]);
    }

    SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
    SDL_SetRenderTarget(renderer_, temp);
}

void Fade::updateVenetianFade() {
    if (square_.back().h < param.fade.venetian_size) {
        drawVenetianBlinds();
        updateVenetianRectangles();
        calculateVenetianProgress();
    } else {
        state_ = FadeState::POST;
    }
}

void Fade::drawVenetianBlinds() {
    auto *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);

    SDL_BlendMode blend_mode;
    SDL_GetRenderDrawBlendMode(renderer_, &blend_mode);
    SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_NONE);
    SDL_SetRenderDrawColor(renderer_, r_, g_, b_, a_);

    for (const auto &rect : square_) {
        SDL_RenderFillRect(renderer_, &rect);
    }

    SDL_SetRenderDrawBlendMode(renderer_, blend_mode);
    SDL_SetRenderTarget(renderer_, temp);
}

void Fade::updateVenetianRectangles() {
    const auto H = counter_ / 2;
    for (size_t i = 0; i < square_.size(); ++i) {
        square_.at(i).h = i == 0 ? H : std::max(static_cast<int>(square_.at(i - 1).h) - 2, 0);
    }
}

void Fade::calculateVenetianProgress() {
    int completed = 0;
    for (const auto &square : square_) {
        if (square.h >= param.fade.venetian_size) {
            ++completed;
        }
    }
    value_ = calculateValue(0, square_.size() - 1, completed);
}

// Activa el fade
void Fade::activate() {
    // Si ya está habilitado, no hay que volverlo a activar
    if (state_ != FadeState::NOT_ENABLED) {
        return;
    }

    state_ = FadeState::PRE;
    counter_ = 0;
    post_counter_ = 0;
    pre_counter_ = 0;

    switch (type_) {
        case FadeType::FULLSCREEN: {
            // Pinta el backbuffer_ de color sólido
            cleanBackbuffer(r_, g_, b_, 255);
            break;
        }

        case FadeType::CENTER: {
            rect1_ = {0, 0, param.game.width, 0};
            rect2_ = {0, 0, param.game.width, 0};
            a_ = 64;
            break;
        }

        case FadeType::RANDOM_SQUARE: {
            rect1_ = {0, 0, static_cast<float>(param.game.width / num_squares_width_), static_cast<float>(param.game.height / num_squares_height_)};
            square_.clear();

            // Añade los cuadrados al vector
            for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
                rect1_.x = (i % num_squares_width_) * rect1_.w;
                rect1_.y = (i / num_squares_width_) * rect1_.h;
                square_.push_back(rect1_);
            }

            // Desordena el vector de cuadrados
            auto num = num_squares_width_ * num_squares_height_;
            while (num > 1) {
                auto num_arreu = rand() % num;
                SDL_FRect temp = square_[num_arreu];
                square_[num_arreu] = square_[num - 1];
                square_[num - 1] = temp;
                num--;
            }

            // Limpia la textura
            a_ = mode_ == FadeMode::OUT ? 0 : 255;
            cleanBackbuffer(r_, g_, b_, a_);

            // Deja el color listo para usar
            a_ = mode_ == FadeMode::OUT ? 255 : 0;

            break;
        }

        case FadeType::VENETIAN: {
            // Limpia la textura
            a_ = mode_ == FadeMode::OUT ? 0 : 255;
            cleanBackbuffer(r_, g_, b_, a_);

            // Deja el color listo para usar
            a_ = mode_ == FadeMode::OUT ? 255 : 0;

            // Añade los cuadrados al vector
            square_.clear();
            rect1_ = {0, 0, param.game.width, 0};
            const int MAX = param.game.height / param.fade.venetian_size;

            for (int i = 0; i < MAX; ++i) {
                rect1_.y = i * param.fade.venetian_size;
                square_.push_back(rect1_);
            }

            break;
        }
    }
}

// Establece el color del fade
void Fade::setColor(Uint8 r, Uint8 g, Uint8 b) {
    r_ = r;
    g_ = g;
    b_ = b;
}

// Establece el color del fade
void Fade::setColor(Color color) {
    r_ = color.r;
    g_ = color.g;
    b_ = color.b;
}

// Limpia el backbuffer
void Fade::cleanBackbuffer(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
    // Dibujamos sobre el backbuffer_
    auto *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);

    // Pintamos la textura con el color del fade
    SDL_SetRenderDrawColor(renderer_, r, g, b, a);
    SDL_RenderClear(renderer_);

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

// Calcula el valor del estado del fade
auto Fade::calculateValue(int min, int max, int current) -> int {
    if (current < min) {
        return 0;
    }

    if (current > max) {
        return 100;
    }

    return static_cast<int>(100.0 * (current - min) / (max - min));
}