#include "FastLED.h"

// length of the trail
#define STRING_SIZE 12

#define NUM_STRIPS 8
#define NUM_LEDS_PER_STRIP 300
#define NUM_LEDS ((NUM_LEDS_PER_STRIP) * (NUM_STRIPS))

CRGB leds[NUM_LEDS];

#define RANDOMS 8
uint16_t randoms[RANDOMS];

// Holds the "highest" updated LED in each strip + 1
uint16_t dirtystrips[NUM_STRIPS];


// write a pixel and update "height" of the strip
void plotLED(int16_t lednum, CRGB col) {
  if (lednum < 0 || lednum >= NUM_LEDS)
    return;
  leds[lednum] = col;
  uint8_t strip = 0;
  while (lednum >= NUM_LEDS_PER_STRIP)
    strip++, lednum -= NUM_LEDS_PER_STRIP;
  if (lednum >= dirtystrips[strip])
    dirtystrips[strip] = lednum + 1;
}


// send the differences since the last showDirtyStrips()
void showDirtyStrips() {
  for (uint8_t strip = 0; strip < NUM_STRIPS; strip++) {
    if (dirtystrips[strip] > 0) {
      FastLED[strip].setLeds(&leds[strip * NUM_LEDS_PER_STRIP], dirtystrips[strip]);
      FastLED[strip].showLeds(255);
      // FastLED[strip].setLeds(&leds[strip * NUM_LEDS_PER_STRIP], NUM_LEDS_PER_STRIP);
      dirtystrips[strip] = 0;
    }
  }
}


void setup() {
  Serial.begin(115200);
  // after a reset, send the entire LED data to clear the LEDs
  for (uint8_t strip = 0; strip < NUM_STRIPS; strip++)
    dirtystrips[strip] = NUM_LEDS_PER_STRIP;
  // seed our separate random number generators
  for (uint8_t rand = 0; rand < RANDOMS; rand++)
    randoms[rand] = random16() + random16() + random16();

  // the "leds" pointers here are ignored when using showDirtyStrips()
  FastLED.addLeds<NEOPIXEL, 8> (leds, 0 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 27>(leds, 1 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 16>(leds, 2 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 17>(leds, 3 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 25>(leds, 4 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 26>(leds, 5 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 12>(leds, 6 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 13>(leds, 7 * NUM_LEDS_PER_STRIP, NUM_LEDS_PER_STRIP);
}


void loop() {
  static int16_t red_pos;
  
  if (red_pos >= NUM_LEDS + STRING_SIZE)
    red_pos = 0;
  plotLED(red_pos, CRGB::Red);
  plotLED(red_pos - STRING_SIZE, CRGB::Black);
  red_pos++;

  if (0) {
    for (uint8_t rand = 0; rand < RANDOMS; rand++) {
      uint32_t prand = (uint32_t)NUM_LEDS * (uint32_t)randoms[rand];
      uint16_t ledindex = prand >> 16;
      if ((leds[ledindex].r | leds[ledindex].g | leds[ledindex].b) == 0) {
        // current led had faded to black: pick a new random black led
        do {
          randoms[rand] = randoms[rand] * (uint16_t)2053 + (uint16_t)13849;
          prand = (uint32_t)NUM_LEDS * (uint32_t)randoms[rand];
          ledindex = prand >> 16;
        } while ((leds[ledindex].r | leds[ledindex].g | leds[ledindex].b) > 0);
        // and set the new led to a random colour
        plotLED(ledindex, CHSV(random8(), 192 + random8()/4, 224 + random8()/8));
      } else {
        // fade this pixel towards black
        CRGB tmp = leds[ledindex];
        fadeToBlackBy(&tmp, 1, 32);
        plotLED(ledindex, tmp);
      }
    }
  }

  if (1)
    showDirtyStrips();
  else
    FastLED.show();

  // print frames per second
  uint16_t sample_frames = 50;
  static uint16_t frame;
  static uint32_t last_ms;
  if (++frame == sample_frames) {
    frame = 0;
    Serial.print(sample_frames * 1000.0 / (millis() - last_ms));
    Serial.println(" FPS");
    last_ms = millis();
  }
}