#include "game/scenes/intro_sprites_scene.hpp"

#include <algorithm>
#include <cstdlib>

#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/options.hpp"

namespace {

    // Duració d'un pas. El vell doIntroSprites feia Jg::setUpdateTicks(20);
    // cada iteració del seu for (i) consumia un tick de 20 ms.
    constexpr int TICK_MS = 20;

    // Taules de frames. Ubicacions de cada sprite dins el gfx de la intro
    // (gfx/logo.gif o gfx/logo_new.gif — el layout de sprites és el mateix).
    // Cada sprite ocupa 15×15 px, disposats horitzontalment per fila.
    // Els valors són els offsets x (la y la posa l'invocador al src_y).
    // Derivats dels `fr_ani_N[i] = ...` del vell doIntroSprites.
    constexpr Uint16 FR1[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180};                                             // camina dreta (y=0)
    constexpr Uint16 FR2[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180};                                             // camina esquerra (y=15)
    constexpr Uint16 FR3[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150};                                                       // trau mapa dreta (y=30)
    constexpr Uint16 FR4[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150};                                                       // trau mapa esquerra (y=45)
    constexpr Uint16 FR5[] = {165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 300, 285, 270, 255, 240, 225, 210, 195, 180, 165};  // bot de susto (y=45, mirror)
    constexpr Uint16 FR6[] = {0, 15, 30, 45, 60, 75, 90, 105};                                                                      // momia (y=60)
    constexpr Uint16 FR7[] = {75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270,                                   // paper (y=75, idx 0..13)
        0,
        15,
        30,
        45,
        60,
        75,
        90,
        105,
        120,
        135,
        150,
        165,
        180,
        195,
        210};                                                                                            // sombra (y=105, idx 14..28)
    constexpr Uint16 FR8[] = {15, 30, 45, 60};                                                           // pedra (y=75)
    constexpr Uint16 FR9[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225};   // prota ball (y=120)
    constexpr Uint16 FR10[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225};  // momia ball (y=135)
    constexpr Uint16 FR11[] = {15, 30, 45, 60, 75, 60};                                                  // altaveu (y=90, [5]=[3] pel loop de 4)

    constexpr Uint16 CREU = 75;         // src_y de la creu (overlay)
    constexpr Uint16 INTERROGANT = 90;  // src_y del signe d'interrogant

    // Equivalent de la funció `drawIntroWordmark` de modulesequence.cpp.
    // Branqueja segons use_new_logo perquè la mateixa sub-escena es
    // reutilitza des de IntroScene (logo vell) i IntroNewLogoScene (logo
    // nou) amb arxius diferents però mateix layout de sprites.
    void drawWordmark(const Uint8* gfx) {
        if (Options::game.use_new_logo) {
            // Centrat: (320 − 188) / 2 = 66 (IntroNewLogoScene usa la mateixa x).
            Jd8::blit(66, 78, gfx, 60, 158, 188, 28);
        } else {
            Jd8::blit(43, 78, gfx, 43, 155, 231, 45);
        }
    }

    using RenderFn = void (*)(const Uint8*, int);

    // Una fase — rang [start_i..end_i] inclusive (direcció implícita per
    // signe), funció de render, i flag d'skippable. Totes les fases actuals
    // són skippables; el flag es conserva per si alguna futura ha de ser
    // no interrompuda (p.ex. un logo fatídic que cal veure sencer).
    struct SpritePhase {
        int start_i;
        int end_i;
        RenderFn render;
        bool skippable;
    };

    // =========================================================================
    // Variant 0 — Interrogant / Momia
    // =========================================================================

    void v0WalkRight(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(i, 150, gfx, FR1[(i / 5) % 13], 0, 15, 15, 0);
    }

    void v0PullMapRight(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 150, gfx, FR3[std::min(i / 5, 10)], 30, 15, 15, 0);
    }

    void v0WalkLeftTo80(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(i, 150, gfx, FR2[(i / 5) % 13], 15, 15, 15, 0);
    }

    void v0PullMapLeft(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(80, 150, gfx, FR4[std::min(i / 5, 10)], 45, 15, 15, 0);
    }

    void v0MomiaLeft(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(i, 150, gfx, FR6[(i / 5) % 8], 60, 15, 15, 0);
        Jd8::blitCK(80, 150, gfx, FR4[10], 45, 15, 15, 0);
    }

    void v0Turn(const Uint8* gfx, int /*i*/) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(80, 150, gfx, FR1[1], 0, 15, 15, 0);
        Jd8::blitCK(95, 150, gfx, FR6[4], 60, 15, 15, 0);
        Jd8::blitCK(80, 133, gfx, 0, INTERROGANT, 15, 15, 0);
    }

    void v0Jump1(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(80, 150 - ((i % 50) / 5), gfx, FR5[std::min(i / 5, 19)], 45, 15, 15, 0);
        Jd8::blitCK(95, 150, gfx, FR6[4], 60, 15, 15, 0);
    }

    void v0Jump2(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(80, 140 + ((i % 50) / 5), gfx, FR5[std::min(i / 5, 19)], 45, 15, 15, 0);
        Jd8::blitCK(95, 150, gfx, FR6[4], 60, 15, 15, 0);
    }

    void v0WalkFinal(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(i, 150, gfx, FR2[(i / 5) % 13], 15, 15, 15, 0);
        Jd8::blitCK(95, 150, gfx, FR6[4], 60, 15, 15, 0);
    }

    void v0Final(const Uint8* gfx, int /*i*/) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(95, 150, gfx, FR6[4], 60, 15, 15, 0);
        Jd8::blitCK(95, 133, gfx, 0, INTERROGANT, 15, 15, 0);
    }

    constexpr SpritePhase VARIANT_0[] = {
        {.start_i = 0, .end_i = 200, .render = v0WalkRight, .skippable = true},
        {.start_i = 0, .end_i = 200, .render = v0PullMapRight, .skippable = true},
        {.start_i = 200, .end_i = 0, .render = v0PullMapRight, .skippable = true},  // guarda el mapa (reprodueix inversament)
        {.start_i = 200, .end_i = 80, .render = v0WalkLeftTo80, .skippable = true},
        {.start_i = 0, .end_i = 200, .render = v0PullMapLeft, .skippable = true},
        {.start_i = 300, .end_i = 95, .render = v0MomiaLeft, .skippable = true},
        {.start_i = 0, .end_i = 50, .render = v0Turn, .skippable = true},
        {.start_i = 0, .end_i = 49, .render = v0Jump1, .skippable = true},
        {.start_i = 50, .end_i = 99, .render = v0Jump2, .skippable = true},
        {.start_i = 80, .end_i = 0, .render = v0WalkFinal, .skippable = true},
        {.start_i = 0, .end_i = 150, .render = v0Final, .skippable = true},
    };

    // =========================================================================
    // Variant 1 — Creu / Pedra
    // =========================================================================

    void v1WalkRight(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(i, 150, gfx, FR1[(i / 5) % 13], 0, 15, 15, 255);
    }

    void v1PullMap(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(200, 150, gfx, FR3[std::min(i / 5, 10)], 30, 15, 15, 255);
    }

    void v1Interrogant(const Uint8* gfx, int /*i*/) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(200, 134, gfx, 0, INTERROGANT, 15, 15, 255);
        Jd8::blitCK(200, 150, gfx, FR3[10], 30, 15, 15, 255);
    }

    void v1DropMap(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        const int IDX = std::min(i / 5, 28);
        // FR7 té 29 frames dividits en dos grups: paper (idx 0..13, src_y=75)
        // i sombra (idx 14..28, src_y=105). El vell feia una branca al bucle.
        if (IDX <= 13) {
            Jd8::blitCK(200, 150, gfx, FR7[IDX], 75, 15, 15, 255);
        } else {
            Jd8::blitCK(200, 150, gfx, FR7[IDX], 105, 15, 15, 255);
        }
    }

    void v1StoneFall(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(200, 150, gfx, FR7[28], 105, 15, 15, 255);
        Jd8::blitCK(200, i * 2, gfx, FR8[0], 75, 15, 15, 255);
    }

    void v1StoneBreak(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(200, 150, gfx, FR8[i / 10], 75, 15, 15, 255);
    }

    void v1Final(const Uint8* gfx, int /*i*/) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
        Jd8::blitCK(200, 150, gfx, FR8[1], 75, 15, 15, 255);
        Jd8::blitCK(185, 150, gfx, FR8[2], 75, 15, 15, 255);
        Jd8::blitCK(215, 150, gfx, FR8[3], 75, 15, 15, 255);
    }

    constexpr SpritePhase VARIANT_1[] = {
        {.start_i = 0, .end_i = 200, .render = v1WalkRight, .skippable = true},
        {.start_i = 0, .end_i = 300, .render = v1PullMap, .skippable = true},
        {.start_i = 0, .end_i = 100, .render = v1Interrogant, .skippable = true},
        {.start_i = 0, .end_i = 200, .render = v1DropMap, .skippable = true},
        {.start_i = 0, .end_i = 75, .render = v1StoneFall, .skippable = true},
        {.start_i = 0, .end_i = 19, .render = v1StoneBreak, .skippable = true},
        {.start_i = 0, .end_i = 200, .render = v1Final, .skippable = true},
    };

    // =========================================================================
    // Variant 2 — Ball de carnaval
    // =========================================================================

    void v2Approach(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(i, 150, gfx, FR1[(i / 5) % 13], 0, 15, 15, 255);
        Jd8::blitCK(304 - i, 150, gfx, FR6[(i / 10) % 8], 60, 15, 15, 255);
    }

    void v2Still(const Uint8* gfx, int /*i*/) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(145, 150, gfx, FR1[1], 0, 15, 15, 255);
        Jd8::blitCK(160, 150, gfx, FR6[1], 60, 15, 15, 255);
    }

    void v2Horn(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(125, 150, gfx, FR11[(i / 10) % 2], 90, 15, 15, 255);
        Jd8::blitCK(145, 150, gfx, FR1[1], 0, 15, 15, 255);
        Jd8::blitCK(160, 150, gfx, FR6[1], 60, 15, 15, 255);
    }

    void v2Ball(const Uint8* gfx, int i) {
        Jd8::clearScreen(0);
        drawWordmark(gfx);
        Jd8::blitCK(145, 150, gfx, FR9[(i / 10) % 16], 120, 15, 15, 255);
        Jd8::blitCK(160, 150, gfx, FR10[(i / 10) % 16], 135, 15, 15, 255);
        Jd8::blitCK(125, 150, gfx, FR11[((i / 5) % 4) + 2], 90, 15, 15, 255);
    }

    constexpr SpritePhase VARIANT_2[] = {
        {.start_i = 0, .end_i = 145, .render = v2Approach, .skippable = true},
        {.start_i = 0, .end_i = 100, .render = v2Still, .skippable = true},
        {.start_i = 0, .end_i = 50, .render = v2Horn, .skippable = true},
        {.start_i = 0, .end_i = 800, .render = v2Ball, .skippable = true},
    };

    // =========================================================================
    // Dispatch per variant
    // =========================================================================

    auto variantTable(int variant) -> const SpritePhase* {
        switch (variant) {
            case 0:
                return VARIANT_0;
            case 1:
                return VARIANT_1;
            case 2:
                return VARIANT_2;
            default:
                return VARIANT_0;
        }
    }

    auto variantLength(int variant) -> int {
        switch (variant) {
            case 0:
                return sizeof(VARIANT_0) / sizeof(VARIANT_0[0]);
            case 1:
                return sizeof(VARIANT_1) / sizeof(VARIANT_1[0]);
            case 2:
                return sizeof(VARIANT_2) / sizeof(VARIANT_2[0]);
            default:
                return 0;
        }
    }

    auto phaseStepCount(const SpritePhase& p) -> int {
        return std::abs(p.end_i - p.start_i) + 1;
    }

    auto phaseCurrentI(const SpritePhase& p, int step) -> int {
        return p.end_i >= p.start_i ? p.start_i + step : p.start_i - step;
    }

}  // namespace

namespace Scenes {

    IntroSpritesScene::IntroSpritesScene(SurfaceHandle&& gfx)
        : gfx_(std::move(gfx)) {}

    void IntroSpritesScene::onEnter() {
        // El vell doIntroSprites feia `rand() % 3` al principi. El seed ve
        // establert per `srand(time(0))` al boot del joc (info.cpp / main),
        // així que la variant canvia entre execucions.
        variant_ = std::rand() % 3;
        phase_ = 0;
        phase_step_ = 0;
        step_acc_ms_ = 0;
        done_ = false;

        // Renderitzem ja el primer frame (step 0 de la primera fase) perquè
        // el Jd8::flip del mini-loop del fiber el pinte al primer cicle.
        const SpritePhase* phases = variantTable(variant_);
        phases[0].render(gfx_.get(), phaseCurrentI(phases[0], 0));
    }

    void IntroSpritesScene::tick(int delta_ms) {
        if (done_) {
            return;
        }

        const SpritePhase* phases = variantTable(variant_);
        const int NUM_PHASES = variantLength(variant_);

        // Skip per tecla. Durant la fase marcada com a no skippable (només
        // v0Final al vell codi) s'ignora — preserva la semàntica del vell
        // bucle final de la variant 0 que no cridava wait_frame_or_skip.
        if (phases[phase_].skippable && Ji::anyKey()) {
            done_ = true;
            return;
        }

        step_acc_ms_ += delta_ms;
        while (step_acc_ms_ >= TICK_MS && !done_) {
            step_acc_ms_ -= TICK_MS;
            ++phase_step_;
            if (phase_step_ >= phaseStepCount(phases[phase_])) {
                ++phase_;
                phase_step_ = 0;
                if (phase_ >= NUM_PHASES) {
                    done_ = true;
                    return;
                }
            }
        }
        phases[phase_].render(gfx_.get(), phaseCurrentI(phases[phase_], phase_step_));
    }

}  // namespace Scenes
