#include "core/jail/jinput.hpp"

#include <algorithm>
#include <cstring>

#include "core/system/director.hpp"

namespace {

    // keystates és actualitzat per SDL internament. Des del joc només fem lectures.
    const bool* keystates = nullptr;

    // Buffer dels últims 5 caràcters tecle. Emmagatzemem caràcters ASCII
    // lowercase (traduïts des de SDL_Scancode) per a poder comparar directament
    // amb les cadenes dels cheats ("reviu", "alone", "obert").
    Uint8 cheat[5] = {0, 0, 0, 0, 0};

    bool key_pressed = false;

    // Temps restant en mil·lisegons durant el qual Ji::keyPressed/Ji::anyKey
    // retornen false. Utilitzat per a evitar que pulsacions fortuïtes
    // saltin cinemàtiques al començament.
    float wait_ms = 0.0F;

    // Per a calcular el delta entre crides a Ji::update sense que els callers
    // hagen de passar-lo explícitament. Es reinicia a la primera crida.
    Uint64 last_update_tick = 0;

    bool input_blocked = false;

    Uint8 virtual_keystates[static_cast<size_t>(Ji::VirtualSource::COUNT)][SDL_SCANCODE_COUNT] = {{0}};

    auto scancodeToAscii(Uint8 scancode) -> Uint8 {
        if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
            return static_cast<Uint8>('a' + (scancode - SDL_SCANCODE_A));
        }
        return 0;
    }

}  // namespace

void Ji::disableKeyboard(Uint32 time) {
    wait_ms = static_cast<float>(time);
}

void Ji::setInputBlocked(bool blocked) {
    input_blocked = blocked;
}

void Ji::setVirtualKey(int scancode, VirtualSource source, bool pressed) {
    if (scancode < 0 || scancode >= SDL_SCANCODE_COUNT) {
        return;
    }
    const auto SRC_IDX = static_cast<size_t>(source);
    if (SRC_IDX >= static_cast<size_t>(VirtualSource::COUNT)) {
        return;
    }
    virtual_keystates[SRC_IDX][scancode] = pressed ? 1 : 0;
}

void Ji::moveCheats(Uint8 scancode) {
    cheat[0] = cheat[1];
    cheat[1] = cheat[2];
    cheat[2] = cheat[3];
    cheat[3] = cheat[4];
    cheat[4] = scancodeToAscii(scancode);
}

void Ji::update() {
    // El director ha processat tots els events. Ací només refresquem
    // el snapshot del teclat i consumim el flag de tecla polsada.
    if (keystates == nullptr) {
        keystates = SDL_GetKeyboardState(nullptr);
    }

    const Uint64 NOW = SDL_GetTicks();
    if (last_update_tick == 0) {
        last_update_tick = NOW;
    }
    const auto DELTA_MS = static_cast<float>(NOW - last_update_tick);
    last_update_tick = NOW;

    if (wait_ms > 0.0F) {
        wait_ms -= DELTA_MS;
        wait_ms = std::max(wait_ms, 0.0F);
    }

    // Consumim el flag de "alguna tecla no-GUI polsada" del director
    key_pressed = Director::get()->consumeKeyPressed();
}

auto Ji::keyPressed(int key) -> bool {
    if (wait_ms > 0.0F || keystates == nullptr) {
        return false;
    }
    // Input bloquejat (p.ex. menú flotant obert)
    if (input_blocked) {
        return false;
    }
    // ESC bloquejada pel Director (primera pulsació mostra notificació)
    if (key == SDL_SCANCODE_ESCAPE && Director::get()->isEscBlocked()) {
        return false;
    }
    if (key < 0 || key >= SDL_SCANCODE_COUNT) {
        return false;
    }
    if (static_cast<int>(keystates[key]) != 0) {
        return true;
    }
    return std::ranges::any_of(virtual_keystates, [key](const auto& vk) { return vk[key] != 0; });
}

auto Ji::cheatActivated(const char* cheat_code) -> bool {
    const size_t LEN = std::strlen(cheat_code);
    if (LEN > sizeof(cheat)) {
        return false;
    }
    // Compara contra els últims `len` caràcters del buffer. El buffer té
    // mida fixa 5 i acumula sempre el darrer tecle a la posició 4.
    const size_t OFFSET = sizeof(cheat) - LEN;
    for (size_t i = 0; i < LEN; i++) {
        if (cheat[OFFSET + i] != static_cast<Uint8>(cheat_code[i])) {
            return false;
        }
    }
    return true;
}

auto Ji::anyKey() -> bool {
    return wait_ms > 0.0F ? false : key_pressed;
}
