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

// How many leds in your strip?
#define NUM_LEDS 30

// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN.  For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 6

// Define the array of leds
CRGB leds[NUM_LEDS];
#define BRIGHTNESS         255

// Define a new type so we can create an array of sequences, along with metadata we want to add
struct Sequence {
  uint8_t* sequence;
  uint8_t length;
};

// Define the sequences
const PROGMEM uint8_t sequence0[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
const PROGMEM uint8_t sequence1[] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
const PROGMEM uint8_t sequence2[] = {20, 22, 24, 26, 28};
const PROGMEM uint8_t sequence3[] = {21, 23, 25, 27, 29};
const PROGMEM Sequence sequences[] = {
  {sequence0, sizeof(sequence0)},
  {sequence1, sizeof(sequence1)},
  {sequence2, sizeof(sequence2)},
  {sequence3, sizeof(sequence3)},
};


void setup() {
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);  // GRB ordering is assumed
}


// A forward declaration of a function we're going to use before it's defined.
// I don't know why. I'm copying the style of the original code. Arduino code shouldn't need this.
void moveSequence(uint8_t, uint8_t, Sequence*);


void loop() {
  // swap direction each second
  static uint8_t dir = 0;
  EVERY_N_MILLIS(1000) {
    dir ^= 1;
  }

  // The "global" base hue. It increases by 1 each frame.
  static uint8_t gHue = 0;
  gHue += 1;

  // each Sequence's "local" hue is derived from the "global" hue for this frame
  uint8_t lHue = gHue;

  // for each Sequence in the sequences[] array
  for (uint8_t seqcnt = 0; seqcnt < sizeof(sequences) / sizeof(sequences[0]); seqcnt++) {
    // shift the LEDs in this Sequence one pixel up or down
    CRGB* uncovered = moveSequence(dir, &sequences[seqcnt]);
    // plot over the freshly uncovered pixel
    *uncovered = CHSV(lHue + random8(20), 255, 255);
    // change the "local" hue for the next sequence
    lHue += 64;
  }

  // dither for 50ms: slows down the animation speed, and may increase apparent colour depth.
  FastLED.delay(50);
}


// shift a Sequence by one pixel, and return a pointer to the freshly uncovered pixel
CRGB* moveSequence(uint8_t dir, Sequence* sequence) {
  uint8_t src, dst, length = pgm_read_byte_near(&sequence->length);
  uint8_t* seq = pgm_read_ptr_near(&sequence->sequence);
  switch (dir) {
    case 0:
      src = pgm_read_byte_near(seq);
      for (uint8_t i = 1; i < length; i++) {
        dst = src;
        src = pgm_read_byte_near(seq + i);
        leds[dst] = leds[src];
      }
      return &leds[pgm_read_byte_near(seq + length - 1)];
      break;
    case 1:
      src = pgm_read_byte_near(seq + length - 1);
      for (uint8_t i = length - 1; i > 0; i--) {
        dst = src;
        src = pgm_read_byte_near(seq + i - 1);
        leds[dst] = leds[src];
      }
      return &leds[pgm_read_byte_near(seq)];
      break;
    default:
      return &leds[pgm_read_byte_near(seq)];
      break;
  }
}