// ── flying_text.h ─────────────────────────────────────────────────────────────
// Renders a null-terminated string as a flying-text effect on a 10×10×10
// LED cube.
//
//  Coordinate convention:
//    X  0–9  (back → front — characters fly toward viewer along X)
//    Y  0–9  (bottom → top)
//    Z  0–9  (left → right as viewed from X=9)
//
//  Effect overview:
//    1. Clear the cube.
//    2. Stamp glyph of msg[0] into the X=0 plane.
//    3. Call shift(AXIS_X, DIR_POS) each step — content moves toward X=9.
//    4. Every 10 shifts, stamp the next character into X=0.
//    5. When all characters have been launched, keep shifting until the last
//       one exits X=9 (10 more shifts), then return.
//
//  Usage (in Effects.h):
//    void effect_FlyingText()
//    {
//        FlyingText ft;
//        ft.begin("HELLO WORLD! 123");
//        while (!ft.done()) {    // <-- MUST be while, not if
//            ft.step();
//            delay(80);
//        }
//    }
//
//  Dependencies (all satisfied by Draw.h):
//    void DrawPixel(uint8_t x, uint8_t y, uint8_t z, CRGB color)
//    void ClearPixel(int x, int y, int z)
//    void clrplane_x(int x)
//    void shift(int axis, int direction)
//    FastLED.clear()
//    AXIS_X, DIR_POS
// ─────────────────────────────────────────────────────────────────────────────

#ifndef FLYING_TEXT_H
#define FLYING_TEXT_H

#include <stdint.h>
#include <string.h>
#include "font_5x7.h"
// Draw.h is already included before this file via Effects.h → provides
// DrawPixel, clrplane_x, shift, AXIS_X, CUBE_SIZE, FastLED etc.

#define FONT_READ(addr) (*(addr))

#ifndef DIR_POS
#  define DIR_POS +1
#  define DIR_NEG -1
#endif

// ── Glyph placement on the 10×10 YZ face (the X=0 plane) ─────────────────────
// The glyph is 5 columns wide (Z axis) and 7 rows tall (Y axis).
// Centred horizontally: (10 - 5) / 2 = 2 px left margin  → Z 2–6
// 1 px top margin                                         → Y 7 down to Y 1
static const int FT_Z_OFFSET = 2;  // font col 0 → LED Z 2
static const int FT_Y_OFFSET = 1;  // font row 6 (bottom) → LED Y 1

// ─────────────────────────────────────────────────────────────────────────────
class FlyingText {
public:

    // Call once to initialise. Stamps first character at X=0 immediately.
    void begin(const char* msg) {
        strncpy(_msg, msg, MAX_MSG - 1);
        _msg[MAX_MSG - 1] = '\0';
        _len      = (uint8_t)strlen(_msg);
        _shift    = 0;
        _launched = 0;
        _finished = false;

        FastLED.clear();

        if (_len > 0) {
            _stampGlyph(_msg[0], 0);
            FastLED.show();          // show first character before first shift
            _launched = 1;
        } else {
            _finished = true;
        }
    }

    // Advance one shift step. Returns false when animation is complete.
    bool step() {
        if (_finished) return false;

        // ── Ghost fix ─────────────────────────────────────────────────────────
        // shift(AXIS_X, DIR_POS) is a circular barrel-shift: it saves X=9 to a
        // buffer, copies X=8→X=9, X=7→X=8 ... X=0→X=1, then writes the buffer
        // back to X=0 — and calls FastLED.show() before returning, so the ghost
        // is already on screen before we can do anything about it.
        //
        // Fix: pre-clear X=9 before EVERY shift. The shift loop still overwrites
        // X=9 with X=8's content as normal, so characters travel correctly.
        // Only the buffer that feeds X=0 is affected — it becomes black instead
        // of the previous occupant of X=9, making the shift effectively linear.
        //
        // This covers ALL ghost cases:
        //   • any character followed by a space   (previously unfixed)
        //   • any character-to-character boundary (ghost was brief but present)
        //   • the very last character             (previously fixed conditionally)
        clrplane_x(CUBE_DEPTH - 1);
        // ─────────────────────────────────────────────────────────────────────

        shift(AXIS_X, DIR_POS);      // shift() calls FastLED.show() internally
        _shift++;

        if (_shift >= CUBE_DEPTH) {
            _shift = 0;
            if (_launched < _len) {
                _stampGlyph(_msg[_launched], 0);
                FastLED.show();
                _launched++;
            } else {
                _finished = true;    // last character has cleared X=9
            }
        }

        return !_finished;
    }

    bool    done()     const { return _finished; }
    uint8_t launched() const { return _launched; }

private:
    static const int CUBE_DEPTH = 10;
    static const int GLYPH_W    = 5;
    static const int GLYPH_H    = 7;
    static const int MAX_MSG    = 128;

    char    _msg[MAX_MSG];
    uint8_t _len;
    uint8_t _launched;
    uint8_t _shift;
    bool    _finished;

    // Stamp one glyph into the YZ plane at the given X position.
    void _stampGlyph(char c, int x) {
        CRGB color;
        if (c < 0x20 || c > 0x7E) c = '?';
        uint8_t glyph = (uint8_t)(c - 0x20);

        clrplane_x(x);   // clear only the destination plane

        for (int col = 0; col < GLYPH_W; col++) {
            int ledZ = col + FT_Z_OFFSET;
            if (ledZ < 0 || ledZ >= CUBE_DEPTH) continue;

            uint8_t colBits = FONT_READ(&FONT_5x7[glyph][col]);

            for (int row = 0; row < GLYPH_H; row++) {
                // ── BUG FIX: honour the 'on' flag ────────────────────────────
                // Previously DrawPixel was called unconditionally, painting all
                // pixels and producing a solid filled rectangle.
                bool on = (colBits >> row) & 0x01;
                if (!on) continue;                // ← skip dark pixels
                // ─────────────────────────────────────────────────────────────

                // row 0 = top of glyph → highest Y; row 6 = bottom → lowest Y
                int ledY = (GLYPH_H - 1 - row) + FT_Y_OFFSET;
                if (ledY < 0 || ledY >= CUBE_DEPTH) continue;
                if(c == '_') {
                    color = CRGB::Red;
                } else {
                    color = CRGB::Green;
                }
                DrawPixel((uint8_t)x, (uint8_t)ledY, (uint8_t)ledZ, color);
            }
        }
    }
};

#endif // FLYING_TEXT_H
