#include "core/rendering/pixel_reveal.hpp"

#include <algorithm>  // Para min, ranges::all_of
#include <numeric>    // Para iota
#include <queue>      // Para queue (BFS en modo ORDERED)
#include <random>     // Para mt19937, shuffle

#include "core/rendering/surface.hpp"  // Para Surface
#include "utils/utils.hpp"             // Para PaletteColor

// Constructor
PixelReveal::PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps, bool reverse, RevealMode mode)
    : cover_surface_(std::make_shared<Surface>(width, height)),
      reveal_order_(height),
      row_step_(height, 0),
      width_(width),
      height_(height),
      pixels_per_second_(pixels_per_second),
      step_duration_(step_duration),
      num_steps_(num_steps),
      reverse_(reverse),
      mode_(mode) {
    // En modo normal: empieza negro sólido (se irá revelando a transparente)
    // En modo inverso: empieza transparente (se irá cubriendo de negro)
    const auto INITIAL_COLOR = reverse_ ? 255 : 0;
    cover_surface_->clear(INITIAL_COLOR);

    if (mode_ == RevealMode::ORDERED) {
        // Calcula offsets por bisección BFS: 0, N/2, N/4, 3N/4, ...
        std::vector<int> offsets;
        offsets.push_back(0);
        std::queue<std::pair<int, int>> bq;
        bq.emplace(0, num_steps_);
        while (static_cast<int>(offsets.size()) < num_steps_) {
            auto [lo, hi] = bq.front();
            bq.pop();
            if (hi - lo <= 1) {
                continue;
            }
            const int MID = (lo + hi) / 2;
            offsets.push_back(MID);
            bq.emplace(lo, MID);
            bq.emplace(MID, hi);
        }
        // Genera el orden: para cada offset, todas las columnas col = offset, offset+N, offset+2N, ...
        std::vector<int> ordered_cols;
        ordered_cols.reserve(width_);
        for (const int OFF : offsets) {
            for (int col = OFF; col < width_; col += num_steps_) {
                ordered_cols.push_back(col);
            }
        }
        // Todas las filas usan el mismo orden (sin aleatoriedad)
        for (int r = 0; r < height_; r++) {
            reveal_order_[r] = ordered_cols;
        }
    } else {
        // Modo RANDOM: orden aleatorio por fila usando la fila como semilla (reproducible)
        for (int r = 0; r < height_; r++) {
            reveal_order_[r].resize(width_);
            std::iota(reveal_order_[r].begin(), reveal_order_[r].end(), 0);
            std::mt19937 rng(static_cast<unsigned int>(r));
            std::shuffle(reveal_order_[r].begin(), reveal_order_[r].end(), rng);
        }
    }
}

// Actualiza el estado del revelado
void PixelReveal::update(float time_active) {  // NOLINT(readability-make-member-function-const)
    // En modo normal revela (pone transparente); en modo inverso cubre (pone negro)
    const auto PIXEL_COLOR = reverse_ ? 0 : 255;

    for (int r = 0; r < height_; r++) {
        const float T_START = static_cast<float>(r) / pixels_per_second_;
        const float TIME_IN_ROW = time_active - T_START;

        if (TIME_IN_ROW < 0.0F) {
            continue;  // Esta fila aún no ha empezado
        }

        const int STEPS = std::min(num_steps_, static_cast<int>(TIME_IN_ROW / step_duration_));

        if (STEPS > row_step_[r]) {
            // Procesa los píxeles de los pasos pendientes
            for (int step = row_step_[r]; step < STEPS; step++) {
                const int START_IDX = step * width_ / num_steps_;
                const int END_IDX = (step == num_steps_ - 1) ? width_ : (step + 1) * width_ / num_steps_;

                for (int idx = START_IDX; idx < END_IDX; idx++) {
                    const int COL = reveal_order_[r][idx];
                    cover_surface_->putPixel(COL, r, PIXEL_COLOR);
                }
            }
            row_step_[r] = STEPS;
        }
    }
}

// Dibuja la máscara en la posición indicada
void PixelReveal::render(int dst_x, int dst_y) const {
    cover_surface_->render(dst_x, dst_y);
}

// Indica si el revelado ha completado todas las filas
auto PixelReveal::isComplete() const -> bool {
    return std::ranges::all_of(row_step_, [this](int s) -> bool { return s >= num_steps_; });
}
