/*                  we ❤️ wokwi.com

changes from the original (https://wokwi.com/arduino/projects/297779324291908105):
  * runs on a Nano. 641 LEDs won't fit in 2KB, but the longest
    string is only 174 LEDs. The same leds[174] array is re-used
    for each letter.

  * re-using leds[] means that nothing can depend on the previous 
    frame of animation, so fadeToBlackBy() had to go. Everything is
    generated from scratch for each frame.

  * uses a palette rather than HSV, so it's more easily customisable

  * made it shiny. use switch 3 to move the light with the potentiometer.


TODO:
  * variable length trails, so each letter's sequence can loop
    seamlessly.

*/

#include <FastLED.h>
#include <avr/pgmspace.h>
#include "FastLED_additions.h"

// the longest string's length
#define NUM_LEDS 174
CRGB leds[NUM_LEDS];

// how many LEDs connected to each pin
const uint16_t pin_length[] PROGMEM = {174, 120, 104, 164, 52};
#define NUM_PINS (sizeof(pin_length) / sizeof(pin_length[0]))

CRGBPalette16 palette = RainbowColors_p;
uint16_t hue_frame_delta;


void setup() {
  Serial.begin(115200);
  pinMode(A0, INPUT);
  for (uint8_t pin = 8; pin <= 12; pin++)
    pinMode(pin, INPUT_PULLUP);
  FastLED.setCorrection(UncorrectedColor);
  FastLED.setTemperature(UncorrectedTemperature);
  FastLED.addLeds<WS2812B, 2, GRB>(leds, pin_length[0]);
  FastLED.addLeds<WS2812B, 3, GRB>(leds, pin_length[1]);
  FastLED.addLeds<WS2812B, 4, GRB>(leds, pin_length[2]);
  FastLED.addLeds<WS2812B, 5, GRB>(leds, pin_length[3]);
  FastLED.addLeds<WS2812B, 6, GRB>(leds, pin_length[4]);
}


void loop() {
  static uint16_t anim_offset, base_hue;
  anim_offset--;
  hue_frame_delta = 64;
  if (digitalRead(12))
    hue_frame_delta += 256 * 128;
  if (digitalRead(11))
    hue_frame_delta += 512;
  base_hue += hue_frame_delta;

  static uint8_t analog_value;
  if (digitalRead(10))
    analog_value = analogRead(A0) >> 2;
  else
    analog_value += 2;

  for (uint8_t pin = 0; pin < NUM_PINS; pin++) {
    perimeterMarch(pin, anim_offset, base_hue + 24 * 256 * pin);
    applyShine(pin, anim_offset, analog_value);
    FastLED[pin].showLeds();
  }
  delay(1);
}


// an array of which describes a route around the perimeter of each letter
struct Span {
  uint16_t start;
  int8_t length;
};
const uint8_t spans_per_pin[] PROGMEM = {16, 2, 13, 2, 1};
const Span perimeter_spans[] PROGMEM = {
  // W
  {0, 12},            // up the LHS of left stroke of W
  {12, 16},           // around ring1
  {28, 12},           // back down RHS of left stroke
  {40, 2},            // 2 dots of ring2
  {56, 10},           // up the LHS shallow centre stroke
  {72, 6},            // up from join of LHS of the steep centre stroke
  {78, 16},           // around ring3
  {94, 12},           // down RHS of steep centre
  {106, 2},           // 2 dots around ring4
  {132, 13},          // up the LHS of right stroke
  {145, 16},          // around ring5
  {161, 13},          // down the RHS of right stroke
  {108, 14},          // remainder of ring4
  {66, 6},            // up to join of LHS of the steep centre stroke
  {122, 10},          // down RHS of shallow centre stroke
  {42, 14},           // around the remainder of ring2
  // o
  {0, 64},            // clockwise around outer ring
  {64, -56},          // anti-clockwise around inner ring
  // k
  {0, 3},             // 3 dots around ring
  {16, -10},          // up LHS of top vert
  {26, -10},          // down RHS of top vert
  {3, 3},             // 3 more dots of ring
  {36, -12},          // up LHS of top right
  {48, -12},          // down RHS of top right
  {6, 4},             // 4 more dots of ring
  {60, 12},           // down RHS of bottom right
  {72, 12},           // up LHS of bottom right
  {10, 4},            // 4 more dots of ring
  {84, -10},          // down RHS of bottom vert
  {94, -10},          // up LHS of bottom vert
  {14, 2},            // last 2 dots of ring
  // w (max length is 127, so I split it into two)
  {0, 102}, {102, 62},
  // i
  {0, 52},
};


void perimeterMarch(uint8_t pin, uint16_t anim_offset, uint16_t hue) {
  uint8_t span = 0;
  uint8_t ipin = 0;
  while (ipin < pin)
    span += pgm_read_byte_near(&spans_per_pin[ipin++]);

  uint8_t spans_remaining = pgm_read_byte_near(&spans_per_pin[pin]);
  while (spans_remaining--) {
    int8_t length = pgm_read_byte_near(&perimeter_spans[span].length);
    uint16_t ledno = pgm_read_word_near(&perimeter_spans[span++].start);
    int8_t step = 1;
    if (length < 0) {
      length = -length;
      ledno += length - 1;
      step = -1;
    }

    while (length--) {
      uint8_t subpos = anim_offset++ % 32;
      if (subpos == 0)
        hue += 0;
      // to mimic the previous fadeToBlackBy() behaviour the hue
      // needs to match what was drawn on previous frames
      uint16_t aged_hue = hue - hue_frame_delta * (31 - subpos);
      uint8_t aged_brightness = 7 + subpos * 8;
      // leds[ledno] = CHSV(aged_hue >> 8, 255, aged_brightness);
      leds[ledno] = ColorFromPaletteExtended(palette, aged_hue, aged_brightness, LINEARBLEND);
      ledno += step;
    }
  }

}


// a list of LED strips/rings and their coordinates and angle
struct Element {
  uint16_t start;
  uint8_t length;
  uint8_t type;   // 0 -> strip, 1 -> ring
  uint8_t angle;
  int16_t x;
  int16_t y;
};

constexpr uint8_t deg2byte(uint32_t degrees) {
  return degrees * 256 / 360;
}

const uint8_t elements_per_pin[] PROGMEM = {13, 2, 9, 8, 3};
const Element elements[] PROGMEM = {
  {0,   12,  0,  deg2byte(160), -1236,   69}, // w1s1
  {12,  16,  1,  deg2byte(180), -1348,  -73}, // w1r1
  {28,  12,  0,  deg2byte(340), -1214,   63}, // w1s2
  {40,  16,  1,    deg2byte(0), -1227,  321}, // w1r2
  {56,  10,  0,  deg2byte(225), -1053,  136}, // w1s3
  {66,  12,  0,  deg2byte(159),  -968,   42}, // w1s4
  {78,  16,  1,  deg2byte(175), -1072,  -97}, // w1r3
  {94,  12,  0,  deg2byte(339),  -935,   38}, // w1s5
  {106, 16,  1,  deg2byte(355),  -940,  296}, // w1r4
  {122, 10,  0,   deg2byte(45), -1039,  154}, // w1s6
  {132, 13,  0,  deg2byte(200),  -810,    9}, // w1s7
  {145, 16,  1,  deg2byte(210),  -782, -126}, // w1r5
  {161, 13,  0,   deg2byte(20),  -790,   18}, // w1s8
  {0,   64,  1,    deg2byte(0),  -633,  -40}, // or1
  {64,  56,  1,  deg2byte(180),  -610,  -15}, // or2
  {0,   16,  1,  deg2byte(270),   -88,  111}, // kr1
  {16,  10,  0,  deg2byte(180),   -80,  -95}, // ks1
  {26,  10,  0,    deg2byte(0),   -58,  -97}, // ks2
  {36,  12,  0,  deg2byte(233),    93, -111}, // ks3
  {48,  12,  0,   deg2byte(43),   110,  -96}, // ks4
  {60,  12,  0,  deg2byte(315),   122,  178}, // ks5
  {72,  12,  0,  deg2byte(135),   109,  198}, // ks6
  {84,  10,  0,    deg2byte(0),   -59,  250}, // ks7
  {94,  10,  0,  deg2byte(180),   -82,  245}, // ks8
  {0,   22,  0,  deg2byte(165),   367,  -61}, // w2s1
  {22,  20,  0,  deg2byte(345),   380,  -76}, // w2s2
  {42,  20,  0,  deg2byte(200),   522,  -82}, // w2s3
  {62,  20,  0,  deg2byte(348),   660,  -76}, // w2s4
  {82,  20,  0,  deg2byte(200),   797,  -65}, // w2s5
  {102, 22,  0,   deg2byte(20),   807,  -48}, // w2s6
  {124, 20,  0,  deg2byte(168),   653,   -8}, // w2s7
  {144, 20,  0,   deg2byte(20),   523,  -20}, // w2s8
  {0,   18,  0,  deg2byte(180),  1034,   50}, // is1
  {18,  16,  1,  deg2byte(190),   987, -116}, // ir1
  {34,  18,  0,    deg2byte(0),  1058,   50}, // is2
};

void applyShine(uint8_t pin, uint16_t frame, uint8_t analog_value) {
  static uint32_t change_ms;
  static uint8_t light_angle;
  static uint8_t fade_value;
  if (analog_value != light_angle) {
    light_angle = analog_value;
    // if (light_angle < analog_value)
    //   light_angle++;
    // else
    //   light_angle--;
    change_ms = millis();
  }
  if (millis() - change_ms < 1500) {
    fade_value = qadd8(fade_value, 3);
    if (fade_value > 180)
      fade_value = 180;
  } else
    fade_value = qsub8(fade_value, 3);

  if (fade_value == 0)
    return;

  uint8_t element = 0;
  uint8_t ipin = 0;
  while (ipin < pin)
    element += pgm_read_byte_near(&elements_per_pin[ipin++]);


  uint8_t elements_remaining = pgm_read_byte_near(&elements_per_pin[pin]);
  while (elements_remaining--) {
    uint16_t ledno = pgm_read_word_near(&elements[element].start);
    uint8_t length = pgm_read_byte_near(&elements[element].length);
    uint8_t type = pgm_read_byte_near(&elements[element].type);
    uint8_t angle = pgm_read_byte_near(&elements[element].angle);
    element++;

    uint16_t this_angle = (light_angle - angle) << 8;
    int16_t cos_angle;
    switch (type) {
      case 0: // strip
        cos_angle = cos16(this_angle);
        if (cos_angle > 0) {
          uint8_t this_fade = ((int32_t)cos_angle * fade_value) >> 15;
          while (length--) {
            leds[ledno] = CRGB(qadd8(leds[ledno].r, this_fade),
                              qadd8(leds[ledno].g,  this_fade),
                              qadd8(leds[ledno].b,  this_fade));
            ledno++;
          }
        }
        break;
      case 1: // ring
        uint16_t angle_delta = 0xffff / length;
        this_angle += 1 << 14;
        while (length--) {
          cos_angle = cos16(this_angle);
          if (cos_angle > 0) {
            uint8_t this_fade = ((int32_t)cos_angle * fade_value) >> 15;
            leds[ledno] = CRGB(qadd8(leds[ledno].r, this_fade),
                              qadd8(leds[ledno].g,  this_fade),
                              qadd8(leds[ledno].b,  this_fade));
          }
          this_angle -= angle_delta;
          ledno++;
        }
        break;
      default:
        break;
    }
  }
}