#include "core/system/director.hpp"

#include <cstring>
#include <iostream>

#include "core/input/global_inputs.hpp"
#include "core/input/mouse.hpp"
#include "core/jail/jgame.hpp"
#include "core/jail/jinput.hpp"
#include "core/rendering/menu.hpp"
#include "core/rendering/overlay.hpp"
#include "core/rendering/screen.hpp"
#include "game/info.hpp"
#include "game/modulegame.hpp"
#include "game/modulesequence.hpp"
#include "game/options.hpp"

// Cheats del joc original — declarats a jinput.cpp
extern void JI_moveCheats(Uint8 new_key);

Director* Director::instance_ = nullptr;

void Director::init() {
    instance_ = new Director();
}

void Director::destroy() {
    delete instance_;
    instance_ = nullptr;
}

auto Director::get() -> Director* {
    return instance_;
}

void Director::run() {
    // Llança el game thread
    game_thread_ = std::thread(&Director::gameThreadFunc, this);

    // Doble buffer: game_frame és el frame net del joc, presentation_buffer
    // és el frame + overlay (es regenera cada iteració des de game_frame)
    Uint32 game_frame[320 * 200]{};
    Uint32 presentation_buffer[320 * 200]{};
    bool has_frame = false;

    constexpr Uint32 TARGET_FRAME_MS = 16;  // ~60 FPS

    // Bucle principal del director (no-bloquejant)
    while (!game_thread_done_ && !quit_requested_) {
        Uint32 frame_start = SDL_GetTicks();

        handleEvents();
        GlobalInputs::handle();
        Mouse::updateCursorVisibility();

        // Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
        if (esc_blocked_ && !Overlay::isEscConsumed()) {
            esc_blocked_ = false;
        }

        // Consumeix un frame nou si n'hi ha un disponible (no bloqueja)
        bool new_frame = false;
        {
            std::lock_guard lock(mutex_);
            if (frame_ready_ && latest_frame_ != nullptr) {
                memcpy(game_frame, latest_frame_, sizeof(game_frame));
                frame_ready_ = false;
                frame_consumed_ = true;
                has_frame = true;
                new_frame = true;
            }
        }
        if (new_frame) {
            frame_consumed_cv_.notify_one();  // desbloqueja el joc
        }

        // Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
        if (has_frame) {
            memcpy(presentation_buffer, game_frame, sizeof(presentation_buffer));
            Screen::get()->present(presentation_buffer);
        }

        // Limita a ~60 FPS
        Uint32 elapsed = SDL_GetTicks() - frame_start;
        if (elapsed < TARGET_FRAME_MS) {
            SDL_Delay(TARGET_FRAME_MS - elapsed);
        }
    }

    // Assegura que el game thread ix (despertar-lo per si està esperant)
    quit_requested_ = true;
    JG_QuitSignal();
    {
        std::lock_guard lock(mutex_);
        frame_consumed_ = true;
    }
    frame_consumed_cv_.notify_all();

    if (game_thread_.joinable()) {
        game_thread_.join();
    }
}

void Director::handleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_EVENT_QUIT) {
            JG_QuitSignal();
            requestQuit();
        }
        // Menú: F12 (o tecla configurada) obre/tanca el menú flotant
        if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
            event.key.scancode == Options::keys_gui.menu_toggle) {
            Menu::toggle();
            JI_SetInputBlocked(Menu::isOpen());
            continue;
        }
        // Si el menú està obert, consumeix tot l'input de teclat
        if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
            if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
                Menu::close();
                JI_SetInputBlocked(false);
                // Empassa l'ESC fins al release perquè el joc no la veja per polling
                esc_swallow_until_release_ = true;
            } else {
                Menu::handleKey(event.key.scancode);
            }
            continue;
        }
        if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
            continue;  // no deixem passar KEY_UP al joc tampoc
        }
        // Allibera el bloqueig d'ESC quan l'usuari la deixa anar
        if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) {
            esc_swallow_until_release_ = false;
            continue;
        }
        // ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
        if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
            esc_blocked_ = true;  // Bloqueja ESC per polling immediatament
            if (!Overlay::isEscConsumed()) {
                // Primera pulsació: mostra notificació
                Overlay::handleEscape();
            } else {
                // Segona pulsació: senyal d'eixida al joc
                esc_blocked_ = false;
                key_pressed_ = true;
                JG_QuitSignal();
            }
            continue;  // no processa més aquest event
        }
        if (event.type == SDL_EVENT_KEY_UP) {
            if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
                // Ja processat a KEY_DOWN, només deixem netejar el bloqueig
                // quan l'overlay faça timeout
                continue;
            } else {
                // Comprova si és una tecla GUI (no passa al joc)
                const auto sc = event.key.scancode;
                const bool is_gui_key = (sc == Options::keys_gui.dec_zoom ||
                    sc == Options::keys_gui.inc_zoom ||
                    sc == Options::keys_gui.fullscreen ||
                    sc == Options::keys_gui.toggle_shader ||
                    sc == Options::keys_gui.toggle_aspect_ratio ||
                    sc == Options::keys_gui.toggle_supersampling ||
                    sc == Options::keys_gui.next_shader ||
                    sc == Options::keys_gui.next_shader_preset ||
                    sc == Options::keys_gui.toggle_stretch_filter ||
                    sc == Options::keys_gui.toggle_render_info);
                if (!is_gui_key) {
                    key_pressed_ = true;
                    JI_moveCheats(sc);
                }
            }
        }
        Mouse::handleEvent(event);
    }
}

void Director::publishFrame(Uint32* pixels) {
    {
        std::lock_guard lock(mutex_);
        latest_frame_ = pixels;
        frame_ready_ = true;
        frame_consumed_ = false;
    }
    frame_produced_cv_.notify_one();

    // Espera que el director consumeixca el frame
    {
        std::unique_lock lock(mutex_);
        frame_consumed_cv_.wait(lock, [this] {
            return frame_consumed_ || quit_requested_;
        });
    }
}

void Director::requestQuit() {
    quit_requested_ = true;
    JG_QuitSignal();
    frame_consumed_cv_.notify_all();
    frame_produced_cv_.notify_all();
}

auto Director::consumeKeyPressed() -> bool {
    return key_pressed_.exchange(false);
}

void Director::gameThreadFunc() {
    info::num_habitacio = Options::game.habitacio_inicial;
    info::num_piramide = Options::game.piramide_inicial;
    info::diners = 0;
    info::diamants = 0;
    info::vida = Options::game.vides;
    info::momies = 0;
    info::nou_personatge = false;
    info::pepe_activat = false;

    FILE* ini = fopen("trick.ini", "rb");
    if (ini != nullptr) {
        info::nou_personatge = true;
        fclose(ini);
    }

    int gameState = 1;
    while (gameState != -1 && !quit_requested_) {
        switch (gameState) {
            case 0: {
                auto* moduleGame = new ModuleGame();
                gameState = moduleGame->Go();
                delete moduleGame;
                break;
            }
            case 1: {
                auto* moduleSequence = new ModuleSequence();
                gameState = moduleSequence->Go();
                delete moduleSequence;
                break;
            }
        }
    }

    game_thread_done_ = true;
    // Despertar el director per si esperava un frame
    frame_produced_cv_.notify_all();
}
