// The 21 colour lights around the triangle (WS2812 on GPIO16).
// Effects 0-7 are the original firmware's (the touch pads cycle through those);
// 8-10 are new and can be picked from the phone. 11-12 are only used by the shows.
#include <Adafruit_NeoPixel.h>

#define LED_PIN   16
#define LED_COUNT 21

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

uint8_t brightness = 30;
int rgb_flag = 1;               // the effect the user picked (0 = lights off)
const int RGB_TOUCH_MODES = 8;  // effects the touch pads cycle through (same as the original)
const int RGB_MODES = 11;       // effects the phone can pick
uint8_t solidR = 0, solidG = 140, solidB = 255;
int overlay = -1;               // effect forced by a show, -1 = none

unsigned long pixelPrevious = 0;
int pixelInterval = 50;
int pixelQueue = 0;
int pixelCycle = 0;
uint16_t pixelCurrent = 0;
uint16_t pixelNumber = LED_COUNT;
int lastEffect = -2;

// 0-255 around the colour wheel: red -> green -> blue -> red.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void colorWipe_delay(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

// Two rainbow comets chasing round.
void strip1() {
  pixelInterval = 100;
  strip.fill(0);
  for (int i = 0; i < 5; i++) {
    strip.setPixelColor((i + pixelNumber - pixelCycle) % pixelNumber, Wheel((i * 10 + pixelQueue) & 255));
    strip.setPixelColor((i + pixelNumber - pixelCycle + 11) % pixelNumber, Wheel((i * 10 + pixelQueue + 128) & 255));
  }
  strip.show();
  if (++pixelCycle >= pixelNumber) pixelCycle = 0;
  if (++pixelQueue >= 256) pixelQueue = 0;
}

// Two white comets one way...
void strip2() {
  strip.fill(0);
  for (int i = 0; i < 5; i++) {
    int j = (4 - i) % 7 * (150 / 5) + 1;
    strip.setPixelColor((i + pixelNumber - pixelCycle) % pixelNumber, strip.Color(j, j, j));
    strip.setPixelColor((i + pixelNumber - pixelCycle + 11) % pixelNumber, strip.Color(j, j, j));
  }
  strip.show();
  if (++pixelCycle >= pixelNumber) pixelCycle = 0;
}

// ...and the other way.
void strip3() {
  strip.fill(0);
  for (int i = 0; i < 5; i++) {
    int j = i % 7 * (150 / 5) + 1;
    strip.setPixelColor((i + pixelCycle) % pixelNumber, strip.Color(j, j, j));
    strip.setPixelColor((i + pixelCycle + 11) % pixelNumber, strip.Color(j, j, j));
  }
  strip.show();
  if (++pixelCycle >= pixelNumber) pixelCycle = 0;
}

void rainbow1() {
  pixelInterval = 30;
  for (uint16_t i = 0; i < pixelNumber; i++) strip.setPixelColor(i, Wheel((i + pixelCycle) & 255));
  strip.show();
  if (++pixelCycle >= 256) pixelCycle = 0;
}

void rainbow2() {
  pixelInterval = 30;
  pixelCycle += 256;
  strip.rainbow(pixelCycle);
  strip.show();
  if (pixelCycle >= 5 * 65536) pixelCycle = 0;
}

void pulse_rainbow1() {
  pixelInterval = 30;
  if (++pixelQueue >= brightness * 2) pixelQueue = 0;
  if (pixelQueue < brightness) strip.setBrightness(pixelQueue);
  else strip.setBrightness(brightness + brightness - pixelQueue);
  pixelCycle += 256;
  strip.rainbow(pixelCycle);
  strip.show();
  if (pixelCycle >= 5 * 65536) pixelCycle = 0;
}

void rgb_off() {
  pixelInterval = 100;
  strip.fill(0);
  strip.show();
}

// 8: one colour, picked on the phone.
void solid() {
  pixelInterval = 100;
  strip.fill(strip.Color(solidR, solidG, solidB));
  strip.show();
}

// 9: red and blue halves swapping.
void police() {
  pixelInterval = 110;
  pixelCycle++;
  bool flip = (pixelCycle / 3) % 2;
  bool on = pixelCycle % 3 != 2;
  for (uint16_t i = 0; i < pixelNumber; i++) {
    bool left = i < pixelNumber / 2;
    uint32_t c = (left ^ flip) ? strip.Color(255, 0, 0) : strip.Color(0, 0, 255);
    strip.setPixelColor(i, on ? c : 0);
  }
  strip.show();
}

// 10: random colour sparkles.
void party() {
  pixelInterval = 60;
  for (uint16_t i = 0; i < pixelNumber; i++) {
    strip.setPixelColor(i, random(3) ? Wheel(random(256)) : 0);
  }
  strip.show();
}

// 11 (shows only): slow red breathing.
void redPulse() {
  pixelInterval = 25;
  pixelQueue = (pixelQueue + 4) % 512;
  int v = pixelQueue < 256 ? pixelQueue : 511 - pixelQueue;
  strip.fill(strip.Color(v, 0, 0));
  strip.show();
}

// 12 (shows only): all lights flash white.
void flashWhite() {
  pixelInterval = 70;
  pixelCycle++;
  strip.fill(pixelCycle % 2 ? strip.Color(255, 255, 255) : 0);
  strip.show();
}

// Called every loop; draws a frame when the current effect is due.
void lightsUpdate(float wheelVelocity) {
  unsigned long now = millis();
  if (now - pixelPrevious < (unsigned long)pixelInterval) return;
  pixelPrevious = now;

  int e = overlay >= 0 ? overlay : rgb_flag;
  if (e != lastEffect) {            // pulse_rainbow1 changes the brightness, so put it back
    strip.setBrightness(brightness);
    lastEffect = e;
  }
  switch (e) {
    case 0: rgb_off(); break;
    case 1:                           // comets that follow the wheel's spin
      if (wheelVelocity > 0) {
        pixelInterval = max(10, 150 - (int)wheelVelocity);
        strip2();
      } else {
        pixelInterval = max(10, 150 + (int)wheelVelocity);
        strip3();
      }
      break;
    case 2: pixelInterval = 100; strip2(); break;
    case 3: pixelInterval = 100; strip3(); break;
    case 4: strip1(); break;
    case 5: rainbow1(); break;
    case 6: rainbow2(); break;
    case 7: pulse_rainbow1(); break;
    case 8: solid(); break;
    case 9: police(); break;
    case 10: party(); break;
    case 11: redPulse(); break;
    case 12: flashWhite(); break;
    default: rgb_off(); break;
  }
}
