// ── wrap_scroll.h ─────────────────────────────────────────────────────────────
// Tiles a 10×10 two-colour bitmap across the 4 vertical faces of the cube and
// animates it scrolling around the outside — like a billboard wrapped around
// all four sides simultaneously.
//
//  Geometry:
//    The 4 vertical faces unroll into a 40-column × 10-row perimeter strip.
//    The 10-column bitmap tiles 4× across that strip (one copy per face).
//    Each animation step advances a scroll offset by 1, smoothly rotating
//    the pattern clockwise or counter-clockwise around the cube.
//
//  Perimeter mapping (clockwise viewed from above, Y = up):
//    u =  0–9  : Front face  Z=0,  x = 0→9
//    u = 10–19 : Right face  X=9,  z = 0→9
//    u = 20–29 : Back face   Z=9,  x = 9→0
//    u = 30–39 : Left face   X=0,  z = 9→0
//    (The 4 corners are painted by both their adjacent faces — no visible seam
//     because both faces sample the same bitmap column at a corner.)
//
//  Bitmap format — uint16_t bmp[10]:
//    bmp[0]   = top row    → Y=9  on cube
//    bmp[9]   = bottom row → Y=0  on cube
//    bit 0 (LSB) = column 0 (leftmost in the tile)
//    bit 9       = column 9 (rightmost in the tile)
//    Bit is 1 → colorA,  bit is 0 → colorB
//
//  Full wrap: 40 scroll steps move the pattern one complete lap of the cube.
//  Pattern period: every 10 steps the pattern is back in the same position
//  on a given face (because the tile is 10 wide).  For a seamless visual
//  cycle use steps that are a multiple of 10 (10, 20, 40 …).
//
//  Built-in patterns:
//    WRAP_STRIPES_DIAG  — diagonal stripes, 5 LEDs each colour
//    WRAP_STRIPES_VERT  — vertical stripes, 5 LEDs each colour
//    WRAP_DIAMOND       — a diamond / lozenge, colourA inside, colourB outside
//    WRAP_CHEVRON       — V-shaped chevrons pointing clockwise
//
//  Usage:
//    wrap_scroll        (WRAP_STRIPES_DIAG, CRGB::Cyan,   CRGB::DarkBlue, 40, 60);
//    wrap_scroll        (WRAP_DIAMOND,      CRGB::Orange, CRGB::Black,    40, 60, -1);
//    wrap_scroll_rainbow(WRAP_CHEVRON, 40, 60);
//    wrap_scroll_repeat (WRAP_STRIPES_DIAG, CRGB::Red, CRGB::DarkRed, 40, 60, 3);
//
//  Dependencies (provided by Draw.h):
//    DrawPixel(), FastLED, CUBE_SIZE
// ─────────────────────────────────────────────────────────────────────────────

#ifndef WRAP_SCROLL_H
#define WRAP_SCROLL_H

#include <stdint.h>
// Draw.h already included via Effects.h

// ── Perimeter constants ───────────────────────────────────────────────────────
static const uint8_t WS_PERIM = 40;   // total perimeter columns (4 × 10)
static const uint8_t WS_TILE  = 10;   // bitmap width = one face width

// ── Built-in 10×10 bitmaps ────────────────────────────────────────────────────
// uint16_t[10]: index 0 = top row (Y=9), index 9 = bottom row (Y=0)
// bit 0 = column 0 (left edge of tile), bit 9 = column 9 (right edge)

// ── Diagonal stripes — 5 LEDs each colour ────────────────────────────────────
// Pixel (col, row) is colorA when (col + row) % 10 < 5.
// As the offset scrolls, the stripes appear to slide diagonally around the cube.
//
//  row 0 (Y=9): cols 0–4 lit → bits 0-4     = 0b0000011111 = 0x001F
//  row 1 (Y=8): cols 9,0–3  → bits 0-3,9   = 0b1000001111 = 0x020F
//  row 2 (Y=7): cols 8,9,0–2→ bits 0-2,8-9 = 0b1100000111 = 0x0307
//  row 3 (Y=6): cols 7–9,0–1→ bits 0-1,7-9 = 0b1110000011 = 0x0383
//  row 4 (Y=5): cols 6–9,0  → bits 0,6-9   = 0b1111000001 = 0x03C1
//  row 5 (Y=4): cols 5–9    → bits 5-9     = 0b1111100000 = 0x03E0
//  row 6 (Y=3): cols 4–8    → bits 4-8     = 0b0111110000 = 0x01F0 (wait:
//               (c+6)%10<5 → c∈{4,3,2,1,0}? No: c+6<5 mod10 → c∈{4,5,6,7,8}? 
//               Let me recompute carefully below.)
//
//  Recomputed from (col+row)%10 < 5 for row 0..9:
//  r=0: c=0,1,2,3,4            → bits 0-4              = 0x001F =  31
//  r=1: c s.t. (c+1)%10<5
//       (c+1)∈{0,1,2,3,4}→c∈{9,0,1,2,3}
//       bits 0,1,2,3,9          = 1+2+4+8+512          = 0x020F = 527
//  r=2: (c+2)∈{0..4}→c∈{8,9,0,1,2}
//       bits 0,1,2,8,9          = 1+2+4+256+512        = 0x0307 = 775
//  r=3: c∈{7,8,9,0,1}
//       bits 0,1,7,8,9          = 1+2+128+256+512      = 0x0383 = 899
//  r=4: c∈{6,7,8,9,0}
//       bits 0,6,7,8,9          = 1+64+128+256+512     = 0x03C1 = 961
//  r=5: c∈{5,6,7,8,9}
//       bits 5,6,7,8,9          = 32+64+128+256+512    = 0x03E0 = 992
//  r=6: c∈{4,5,6,7,8}
//       bits 4,5,6,7,8          = 16+32+64+128+256     = 0x01F0 = 496
//  r=7: c∈{3,4,5,6,7}
//       bits 3,4,5,6,7          = 8+16+32+64+128       = 0x00F8 = 248
//  r=8: c∈{2,3,4,5,6}
//       bits 2,3,4,5,6          = 4+8+16+32+64         = 0x007C = 124
//  r=9: c∈{1,2,3,4,5}
//       bits 1,2,3,4,5          = 2+4+8+16+32          = 0x003E =  62
//
static const uint16_t WRAP_STRIPES_DIAG[10] = {
     31,   // row 0  Y=9
    527,   // row 1  Y=8
    775,   // row 2  Y=7
    899,   // row 3  Y=6
    961,   // row 4  Y=5
    992,   // row 5  Y=4
    496,   // row 6  Y=3
    248,   // row 7  Y=2
    124,   // row 8  Y=1
     62,   // row 9  Y=0
};

// ── Vertical stripes — 5 LEDs each colour ────────────────────────────────────
// Pixel (col, row) is colorA when col < 5. Stripes run straight up and down.
// Scrolling makes them appear to slide horizontally around the cube.
static const uint16_t WRAP_STRIPES_VERT[10] = {
    // cols 0-4 lit (bits 0-4) for every row = 0b0000011111 = 31
    31, 31, 31, 31, 31,   // Y=9 down to Y=5
    31, 31, 31, 31, 31,   // Y=4 down to Y=0
};

// ── Diamond / lozenge ────────────────────────────────────────────────────────
// colorA inside the diamond, colorB outside.
// Computed from: |2·col − 9| + |2·row − 9| ≤ 9
//
//  r=0 (Y=9): dr=9 → no cols satisfy dc≤0          = 0x0000 =   0
//  r=1 (Y=8): dr=7 → dc≤2 → |2c-9|≤2 → c=4,5      = 0x0030 =  48
//  r=2 (Y=7): dr=5 → dc≤4 → c=3,4,5,6              = 0x0078 = 120
//  r=3 (Y=6): dr=3 → dc≤6 → c=2,3,4,5,6,7          = 0x00FC = 252
//  r=4 (Y=5): dr=1 → dc≤8 → c=1,2,3,4,5,6,7,8      = 0x01FE = 510
//  r=5 (Y=4): dr=1 → same                            = 0x01FE = 510
//  r=6 (Y=3): dr=3 → same as r=3                     = 0x00FC = 252
//  r=7 (Y=2): dr=5 → same as r=2                     = 0x0078 = 120
//  r=8 (Y=1): dr=7 → same as r=1                     = 0x0030 =  48
//  r=9 (Y=0): dr=9 → empty                           = 0x0000 =   0
//
static const uint16_t WRAP_DIAMOND[10] = {
      0,   // row 0  Y=9  (empty)
     48,   // row 1  Y=8  cols 4,5
    120,   // row 2  Y=7  cols 3-6
    252,   // row 3  Y=6  cols 2-7
    510,   // row 4  Y=5  cols 1-8
    510,   // row 5  Y=4  cols 1-8
    252,   // row 6  Y=3  cols 2-7
    120,   // row 7  Y=2  cols 3-6
     48,   // row 8  Y=1  cols 4,5
      0,   // row 9  Y=0  (empty)
};

// ── Chevron — V-shapes pointing in the scroll direction ───────────────────────
// Pixel (col, row) is colorA when (col + 2*(row<5 ? row : 9-row)) % 10 < 5.
// This creates a V-shape per tile that points clockwise as the scroll advances.
//
//  r=0 (Y=9): row_fold=0, (c+0)%10<5  → c=0-4  = 0x001F =  31
//  r=1 (Y=8): row_fold=1, (c+2)%10<5  → c∈{8,9,0,1,2} = 0x0307 = 775
//  r=2 (Y=7): row_fold=2, (c+4)%10<5  → c∈{6,7,8,9,0} = 0x03C1 = 961
//  r=3 (Y=6): row_fold=3, (c+6)%10<5  → c∈{4,5,6,7,8} = 0x01F0 = 496
//  r=4 (Y=5): row_fold=4, (c+8)%10<5  → c∈{2,3,4,5,6} = 0x007C = 124
//  r=5 (Y=4): row_fold=4  (mirror)     → same as r=4    = 0x007C = 124
//  r=6 (Y=3): row_fold=3               → same as r=3    = 0x01F0 = 496
//  r=7 (Y=2): row_fold=2               → same as r=2    = 0x03C1 = 961
//  r=8 (Y=1): row_fold=1               → same as r=1    = 0x0307 = 775
//  r=9 (Y=0): row_fold=0               → same as r=0    = 0x001F =  31
//
static const uint16_t WRAP_CHEVRON[10] = {
     31,   // row 0  Y=9
    775,   // row 1  Y=8
    961,   // row 2  Y=7
    496,   // row 3  Y=6
    124,   // row 4  Y=5
    124,   // row 5  Y=4
    496,   // row 6  Y=3
    961,   // row 7  Y=2
    775,   // row 8  Y=1
     31,   // row 9  Y=0
};

// ── Perimeter → (x, z) mapping ───────────────────────────────────────────────
// Returns the (x, z) LED coordinate for perimeter position u (0–39) at height y.
// Corners appear at both adjacent face segments — both draws are the same colour
// so there is no visible double-draw artefact.
static inline void _ws_perim_to_xz(uint8_t u, uint8_t &x, uint8_t &z) {
    if      (u < 10) { x = u;        z = 0; }           // Front  Z=0, x: 0→9
    else if (u < 20) { x = 9;        z = u - 10; }      // Right  X=9, z: 0→9
    else if (u < 30) { x = 29 - u;   z = 9; }           // Back   Z=9, x: 9→0
    else             { x = 0;        z = 39 - u; }       // Left   X=0, z: 9→0
}

// ── Core draw function ────────────────────────────────────────────────────────
// Paints all 400 perimeter LED positions (40 columns × 10 rows) using the
// scrolled bitmap.  Both colorA and colorB pixels are always written, so no
// FastLED.clear() is needed between frames.
//
// offset (0–39): each increment shifts the pattern one column clockwise.
static void _ws_draw_frame(const uint16_t bmp[10], uint8_t offset,
                           CRGB colorA, CRGB colorB)
{
    for (uint8_t u = 0; u < WS_PERIM; u++) {
        // Which column of the 10-wide bitmap tile corresponds to this perimeter
        // position after applying the scroll offset?
        uint8_t col = (u + offset) % WS_TILE;

        uint8_t x, z;
        _ws_perim_to_xz(u, x, z);

        for (uint8_t y = 0; y < CUBE_SIZE; y++) {
            // bmp row 0 = top of pattern = Y=9; row 9 = Y=0
            uint8_t  row  = (uint8_t)(9 - y);
            bool     litA = (bmp[row] >> col) & 0x01;
            DrawPixel(x, y, z, litA ? colorA : colorB);
        }
    }
}

// ── Public API ────────────────────────────────────────────────────────────────

// Scroll the bitmap around the 4 faces once, stepping `steps` times.
//   steps=40  → one full lap of the cube (40 perimeter columns)
//   steps=10  → one tile-width of scroll (pattern looks the same after this)
//   stepDelay → milliseconds per frame
//   dir       → +1 clockwise (front→right→back→left), −1 counter-clockwise
void wrap_scroll(const uint16_t bmp[10],
                 CRGB     colorA,
                 CRGB     colorB,
                 uint8_t  steps     = 40,
                 uint16_t stepDelay = 60,
                 int8_t   dir       = 1)
{
    for (uint8_t i = 0; i < steps; i++) {
        // Offset increases in the direction of travel; wrap modulo 40 to keep
        // in range regardless of direction.
        uint8_t offset = (uint8_t)(((int16_t)i * dir % WS_PERIM + WS_PERIM) % WS_PERIM);
        _ws_draw_frame(bmp, offset, colorA, colorB);
        FastLED.show();
        FastLED.delay(stepDelay);
    }
}

// Scroll with a hue that shifts each step, colorB fixed (default black).
// startHue : initial HSV hue for colorA (0–255)
// dir      : +1 clockwise, −1 counter-clockwise
void wrap_scroll_rainbow(const uint16_t bmp[10],
                         uint8_t  steps     = 40,
                         uint16_t stepDelay = 60,
                         int8_t   dir       = 1,
                         uint8_t  startHue  = 0,
                         CRGB     colorB    = CRGB::Black)
{
    for (uint8_t i = 0; i < steps; i++) {
        uint8_t offset = (uint8_t)(((int16_t)i * dir % WS_PERIM + WS_PERIM) % WS_PERIM);
        // Hue advances one full cycle over `steps` steps
        uint8_t hue    = startHue + (uint8_t)((uint16_t)i * 256 / steps);
        CRGB    colorA = CHSV(hue, 255, 220);
        _ws_draw_frame(bmp, offset, colorA, colorB);
        FastLED.show();
        FastLED.delay(stepDelay);
    }
}

// Repeat wrap_scroll `loops` times.
void wrap_scroll_repeat(const uint16_t bmp[10],
                        CRGB     colorA,
                        CRGB     colorB,
                        uint8_t  steps     = 40,
                        uint16_t stepDelay = 60,
                        uint8_t  loops     = 1,
                        int8_t   dir       = 1)
{
    for (uint8_t r = 0; r < loops; r++)
        wrap_scroll(bmp, colorA, colorB, steps, stepDelay, dir);
}

#endif // WRAP_SCROLL_H
