#include "game/scenes/loading_screen.hpp"

#include <SDL3/SDL.h>

#include <cmath>    // Para std::sin
#include <cstdlib>  // Para rand

#include "core/audio/audio.hpp"               // Para Audio
#include "core/input/global_inputs.hpp"       // Para check
#include "core/input/input.hpp"               // Para Input
#include "core/rendering/screen.hpp"          // Para Screen
#include "core/rendering/sprite/sprite.hpp"   // Para SSprite
#include "core/rendering/surface.hpp"         // Para Surface
#include "core/resources/resource_cache.hpp"  // Para Resource
#include "core/system/global_events.hpp"      // Para check
#include "game/options.hpp"                   // Para Options, options, SectionState, Options...
#include "game/scene_manager.hpp"             // Para SceneManager
#include "utils/defines.hpp"                  // Para GAME_SPEED
#include "utils/utils.hpp"                    // Para stringToColor, PaletteColor

// Constructor
LoadingScreen::LoadingScreen()
    : mono_loading_screen_surface_(Resource::Cache::get()->getSurface("loading_screen_bn.gif")),
      color_loading_screen_surface_(Resource::Cache::get()->getSurface("loading_screen_color.gif")),
      mono_loading_screen_sprite_(std::make_unique<Sprite>(mono_loading_screen_surface_, 0, 0, mono_loading_screen_surface_->getWidth(), mono_loading_screen_surface_->getHeight())),
      color_loading_screen_sprite_(std::make_unique<Sprite>(color_loading_screen_surface_, 0, 0, color_loading_screen_surface_->getWidth(), color_loading_screen_surface_->getHeight())),
      program_sprite_(std::make_unique<Sprite>(Resource::Cache::get()->getSurface("program_jaildoc.gif"))),
      screen_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
      delta_timer_(std::make_unique<DeltaTimer>()) {
    // Configura la superficie donde se van a pintar los sprites
    screen_surface_->clear(static_cast<Uint8>(PaletteColor::WHITE));

    // Inicializa variables
    SceneManager::current = SceneManager::Scene::LOADING_SCREEN;
    SceneManager::options = SceneManager::Options::NONE;
    program_sprite_->setPosition(0.0F, 8.0F);

    // Inicializa el array de índices de líneas
    initLineIndexArray();

    // Cambia el color del borde
    Screen::get()->setBorderColor(stringToColor("white"));
    transitionToState(State::SILENT1);

    // Ajusta el volumen i neteja la pantalla (abans a run())
    Audio::get()->setMusicVolume(50);
    Screen::get()->start();
    Screen::get()->clearRenderer();
    Screen::get()->render();
}

// Destructor
LoadingScreen::~LoadingScreen() {
    Audio::get()->stopMusic();
    Audio::get()->setMusicVolume(100);
}

// Despatx d'un event (SDL3 Callback API)
void LoadingScreen::handleEvent(const SDL_Event& event) {
    GlobalEvents::handle(event);
}

// Comprueba las entradas
void LoadingScreen::handleInput() {
    Input::get()->update();
    GlobalInputs::handle();
}

// Inicializa el array de índices de líneas (imita el direccionamiento de memoria del Spectrum)
void LoadingScreen::initLineIndexArray() {
    for (int i = 0; i < MONO_TOTAL_LINES; ++i) {
        if (i < 64) {  // Primer bloque de 2K
            line_index_[i] = ((i % 8) * 8) + (i / 8);
        } else if (i < 128) {  // Segundo bloque de 2K
            line_index_[i] = 64 + ((i % 8) * 8) + ((i - 64) / 8);
        } else {  // Tercer bloque de 2K
            line_index_[i] = 128 + ((i % 8) * 8) + ((i - 128) / 8);
        }
    }
}

// Transiciona a un nuevo estado
void LoadingScreen::transitionToState(State new_state) {
    state_ = new_state;
    state_time_ = 0.0F;

    // Acciones específicas al entrar en cada estado
    switch (new_state) {
        case State::SILENT1:
        case State::SILENT2:
            current_border_type_ = Border::RED;
            Audio::get()->stopMusic();
            break;

        case State::HEADER1:
        case State::HEADER2:
            current_border_type_ = Border::RED_AND_CYAN;
            Audio::get()->playMusic("loading_header.ogg", 0);
            break;

        case State::DATA1:
            printProgramName();
            current_border_type_ = Border::YELLOW_AND_BLUE;
            Audio::get()->playMusic("loading_data1.ogg", 0);
            break;
        case State::DATA2:
            current_border_type_ = Border::YELLOW_AND_BLUE;
            Audio::get()->playMusic("loading_data2.ogg", 0);
            break;
        case State::LOADING_MONO:
            current_border_type_ = Border::YELLOW_AND_BLUE;
            Audio::get()->playMusic("loading_screen_data.ogg", 0);
            last_mono_step_ = -1;  // Resetear contador de pasos mono
            break;

        case State::LOADING_COLOR:
            current_border_type_ = Border::YELLOW_AND_BLUE;
            Audio::get()->playMusic("loading_screen_color.ogg", 0);
            last_color_block_ = -1;  // Resetear contador de bloques color
            break;

        case State::COMPLETE:
            current_border_type_ = Border::BLACK;
            // Transicionar a la pantalla de título
            SceneManager::current = SceneManager::Scene::TITLE;
            SceneManager::options = SceneManager::Options::TITLE_WITH_LOADING_SCREEN;
            Audio::get()->stopMusic();
            break;
    }
}

// Actualiza el estado actual
void LoadingScreen::updateState(float delta_time) {
    state_time_ += delta_time;

    // Transiciones automáticas por tiempo para los estados iniciales
    // LOADING_MONO y LOADING_COLOR transicionan en sus propias funciones
    switch (state_) {
        case State::SILENT1:
            if (state_time_ >= SILENT1_DURATION) {
                transitionToState(State::HEADER1);
            }
            break;

        case State::HEADER1:
            if (state_time_ >= HEADER1_DURATION) {
                transitionToState(State::DATA1);
            }
            break;

        case State::DATA1:
            if (state_time_ >= DATA1_DURATION) {
                transitionToState(State::SILENT2);
            }
            break;

        case State::SILENT2:
            if (state_time_ >= SILENT2_DURATION) {
                transitionToState(State::HEADER2);
            }
            break;

        case State::HEADER2:
            if (state_time_ >= HEADER2_DURATION) {
                transitionToState(State::LOADING_MONO);
            }
            break;

        case State::DATA2:
            if (state_time_ >= DATA2_DURATION) {
                transitionToState(State::COMPLETE);
            }
            break;

        case State::LOADING_MONO:
        case State::LOADING_COLOR:
        case State::COMPLETE:
            // Estos estados se gestionan en updateMonoLoad/updateColorLoad
            break;
    }
}

// Gestiona la carga monocromática (time-based simplificado)
void LoadingScreen::updateMonoLoad(float /*delta_time*/) {
    // Calcular progreso lineal (0.0 - 1.0)
    float progress = state_time_ / LOADING_MONO_DURATION;
    progress = std::min(progress, 1.0F);

    // Calcular paso total actual (0-959)
    const int TOTAL_STEPS = MONO_TOTAL_LINES * MONO_STEPS_PER_LINE;  // 192 * 5 = 960
    const int CURRENT_STEP = static_cast<int>(progress * TOTAL_STEPS);

    // Verificar si ha completado todas las líneas
    if (CURRENT_STEP >= TOTAL_STEPS) {
        transitionToState(State::LOADING_COLOR);
        return;
    }

    // Dibujar todos los pasos intermedios desde el último dibujado
    const float TEXTURE_WIDTH = mono_loading_screen_surface_->getWidth();
    const float CLIP_WIDTH = TEXTURE_WIDTH / MONO_STEPS_PER_LINE;

    auto previous_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(screen_surface_);

    for (int step = last_mono_step_ + 1; step <= CURRENT_STEP; ++step) {
        // Calcular línea y sub-paso para este paso
        const int CURRENT_LINE = step / MONO_STEPS_PER_LINE;     // 0-191
        const int CURRENT_SUBSTEP = step % MONO_STEPS_PER_LINE;  // 0-4

        // Saltar si excede el total de líneas
        if (CURRENT_LINE >= MONO_TOTAL_LINES) {
            break;
        }

        // Calcular rectángulo de clip para este paso
        const float CLIP_X = CURRENT_SUBSTEP * CLIP_WIDTH;

        load_rect_.x = CLIP_X;
        load_rect_.y = static_cast<float>(line_index_[CURRENT_LINE]);
        load_rect_.w = CLIP_WIDTH;
        load_rect_.h = 1.0F;

        // Configurar y dibujar sobre screen_surface_
        mono_loading_screen_sprite_->setClip(load_rect_);
        mono_loading_screen_sprite_->setPosition(load_rect_);
        mono_loading_screen_sprite_->render();
    }

    Screen::get()->setRendererSurface(previous_renderer);

    // Actualizar el último paso dibujado
    last_mono_step_ = CURRENT_STEP;
}

// Gestiona la carga en color
void LoadingScreen::updateColorLoad(float /*delta_time*/) {
    // Calcular progreso lineal (0.0 - 1.0)
    float progress = state_time_ / LOADING_COLOR_DURATION;
    progress = std::min(progress, 1.0F);

    // Calcular bloque actual (0-767) - ahora pinta de 1 en 1 en lugar de 2 en 2
    const int CURRENT_BLOCK = static_cast<int>(progress * COLOR_TOTAL_BLOCKS);

    // Verificar si ha completado todos los bloques
    if (CURRENT_BLOCK >= COLOR_TOTAL_BLOCKS) {
        transitionToState(State::DATA2);
        return;
    }

    // Dibujar todos los bloques intermedios desde el último dibujado
    auto previous_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(screen_surface_);

    // Iterar desde el último bloque + 1 hasta el bloque actual (de 1 en 1)
    for (int block = last_color_block_ + 1; block <= CURRENT_BLOCK; ++block) {
        // Saltar si excede el total de bloques
        if (block >= COLOR_TOTAL_BLOCKS) {
            break;
        }

        // Calcular posición del bloque
        load_rect_.x = static_cast<float>((block * COLOR_BLOCK_SPACING) % 256);
        load_rect_.y = static_cast<float>((block / COLOR_BLOCKS_PER_ROW) * COLOR_BLOCK_SPACING);
        load_rect_.w = static_cast<float>(COLOR_BLOCK_WIDTH);
        load_rect_.h = static_cast<float>(COLOR_BLOCK_HEIGHT);

        // Configurar y dibujar sobre screen_surface_
        color_loading_screen_sprite_->setClip(load_rect_);
        color_loading_screen_sprite_->setPosition(load_rect_);
        color_loading_screen_sprite_->render();
    }

    Screen::get()->setRendererSurface(previous_renderer);

    // Actualizar el último bloque dibujado
    last_color_block_ = CURRENT_BLOCK;
}

// Dibuja el efecto de carga amarillo y azul en el borde
void LoadingScreen::renderDataBorder() {
    // Obtiene la Surface del borde
    auto border = Screen::get()->getBorderSurface();

    // Pinta el borde de color azul
    border->clear(static_cast<Uint8>(PaletteColor::BLUE));

    // Añade lineas amarillas
    const auto COLOR = static_cast<Uint8>(PaletteColor::YELLOW);
    const int WIDTH = Options::game.width + (Options::video.border.width * 2);
    const int HEIGHT = Options::game.height + (Options::video.border.height * 2);
    bool draw_enabled = rand() % 2 == 0;

    int row = 0;
    while (row < HEIGHT) {
        const int ROW_HEIGHT = (rand() % 4) + 3;
        if (draw_enabled) {
            for (int i = row; i < row + ROW_HEIGHT; ++i) {
                border->drawLine(0, i, WIDTH, i, COLOR);
            }
        }
        row += ROW_HEIGHT;
        draw_enabled = !draw_enabled;
    }
}

// Dibuja el efecto de carga rojo y azul en el borde
void LoadingScreen::renderHeaderBorder() const {
    // Obtiene la Surface del borde
    auto border = Screen::get()->getBorderSurface();

    // Pinta el borde de color azul o rojo
    border->clear(carrier_.toggle ? static_cast<Uint8>(PaletteColor::CYAN) : static_cast<Uint8>(PaletteColor::RED));

    // Añade lineas rojas o azules
    const auto COLOR = carrier_.toggle ? static_cast<Uint8>(PaletteColor::RED) : static_cast<Uint8>(PaletteColor::CYAN);
    const int WIDTH = Options::game.width + (Options::video.border.width * 2);
    const int HEIGHT = Options::game.height + (Options::video.border.height * 2);

    // Primera linea (con el color y tamaño de la portadora)
    int row = 0;
    const int FIRST_ROW_HEIGHT = static_cast<int>(carrier_.offset);
    for (int i = row; i < row + FIRST_ROW_HEIGHT; ++i) {
        border->drawLine(0, i, WIDTH, i, COLOR);
    }
    row += FIRST_ROW_HEIGHT;

    // Resto de lineas (siguen a la portadora)
    bool draw_enabled = false;
    while (row < HEIGHT) {
        if (draw_enabled) {
            for (int i = row; i < row + HEADER_DATAROW_HEIGHT; ++i) {
                border->drawLine(0, i, WIDTH, i, COLOR);
            }
        }
        row += HEADER_DATAROW_HEIGHT;
        draw_enabled = !draw_enabled;
    }
}

// Dibuja el borde de color
void LoadingScreen::renderColoredBorder(PaletteColor color) {
    // Obtiene la Surface del borde
    auto border = Screen::get()->getBorderSurface();

    // Pinta el borde de color azul
    border->clear(static_cast<Uint8>(color));
}

// Actualiza las variables
void LoadingScreen::update() {
    const float DELTA_TIME = delta_timer_->tick();

    handleInput();  // Comprueba las entradas

    updateState(DELTA_TIME);  // Actualiza el estado y gestiona transiciones

    // Actualizar la carga según el estado actual
    switch (state_) {
        case State::DATA1:
        case State::DATA2:
            // Por ahora no hacen nada específico
            break;
        case State::SILENT1:
        case State::SILENT2:
            updateSilent(DELTA_TIME);
            break;
        case State::HEADER1:
        case State::HEADER2:
            updateCarrier(DELTA_TIME);
            break;

        case State::LOADING_MONO:
            updateMonoLoad(DELTA_TIME);
            break;

        case State::LOADING_COLOR:
            updateColorLoad(DELTA_TIME);
            break;

        case State::COMPLETE:
            // No hay más actualizaciones
            break;
    }

    Audio::update();                    // Actualiza el objeto Audio
    Screen::get()->update(DELTA_TIME);  // Actualiza el objeto Screen
}

// Dibuja en pantalla
void LoadingScreen::render() {
    // Pinta el borde
    renderBorder();

    // Prepara para empezar a dibujar en la textura de juego
    Screen::get()->start();
    Screen::get()->clearSurface(stringToColor("white"));

    // Copia la surface a la surface de Screen
    screen_surface_->render(0, 0);

    // Vuelca el contenido del renderizador en pantalla
    Screen::get()->render();
}

// Un frame de l'escena (SDL3 Callback API)
void LoadingScreen::iterate() {
    update();
    render();
}

// Pinta el borde
void LoadingScreen::renderBorder() {
    if (Options::video.border.enabled) {
        // Dibuja el efecto de carga en el borde según el tipo actual
        switch (current_border_type_) {
            case Border::YELLOW_AND_BLUE:
                renderDataBorder();
                break;
            case Border::RED_AND_CYAN:
                renderHeaderBorder();
                break;
            case Border::WHITE:
                renderColoredBorder(PaletteColor::WHITE);
                break;
            case Border::BLACK:
                renderColoredBorder(PaletteColor::BLACK);
                break;
            case Border::RED:
                renderColoredBorder(PaletteColor::RED);
                break;
            case Border::CYAN:
                renderColoredBorder(PaletteColor::CYAN);
                break;
            case Border::NONE:
                // No renderizar borde
                break;
        }
    }
}

// Escribe el nombre del programa
void LoadingScreen::printProgramName() {
    auto previous_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(screen_surface_);
    program_sprite_->render();
    Screen::get()->setRendererSurface(previous_renderer);
}

// Actualiza la portadora
void LoadingScreen::updateCarrier(float delta_time) {
    constexpr float CARRIER_BASE_SPEED = -250.0F;
    constexpr float CARRIER_HEIGHT = HEADER_DATAROW_HEIGHT;

    // Oscilación compuesta: mezcla de dos frecuencias para evitar patrón predecible
    const float MODULATION = std::sin(carrier_.total_time * 1.2F) * std::sin((carrier_.total_time * 0.35F) + 1.0F);
    const float SPEED = CARRIER_BASE_SPEED * (0.5F + (0.5F * MODULATION));  // rango [-200, 0]

    carrier_.offset += SPEED * delta_time;

    if (carrier_.offset < 0.0F) {
        carrier_.offset += CARRIER_HEIGHT;  // reinicia al rango [0,HEADER_DATAROW_HEIGHT]
        carrier_.toggle = !carrier_.toggle;
    }

    carrier_.total_time += delta_time;
}

// Actualiza el ruido durante el tiempo de silencio
void LoadingScreen::updateSilent(float delta_time) {
    constexpr float NOISE_THRESHOLD = 0.35F;

    // Oscilación compuesta para simular picos de ruido
    const float MODULATION = std::sin(noise_.total_time * 4.2F) * std::sin((noise_.total_time * 1.7F) + 0.5F);
    noise_.value = std::fabs(MODULATION);  // rango [0.0, 1.0]

    // Detecta cruce de umbral solo si venía de abajo
    if (noise_.value > NOISE_THRESHOLD && !noise_.crossed) {
        noise_.crossed = true;
        current_border_type_ = (current_border_type_ == Border::RED) ? Border::CYAN : Border::RED;
    }

    // Restablece el flag cuando baja del umbral
    if (noise_.value < NOISE_THRESHOLD) {
        noise_.crossed = false;
    }

    noise_.total_time += delta_time;
}