/******************************************************************************
 * Addressable LED Strip Fireworks Show
 * By Joe Larson, the 3D Printing Professor
 * http://3dpprofessor.com
 ******************************************************************************/

#include "FastLED.h"

/***************************************
 *        Hardware definitions         *
 ***************************************/
#define NUM_LEDS 20
#define LED_DATA_PIN 3
#define POT_PIN A3
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
/***************************************/
#define Brightness 255
CRGB leds[NUM_LEDS];

bool active = true;
bool done = false;
void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println();
  pinMode(POT_PIN, INPUT);
  randomSeed(analogRead(A0));
  FastLED.addLeds<WS2812B, LED_DATA_PIN, GRB>(leds, NUM_LEDS); // Added "GRB" for my manufacturer who decided to mix up the colors
  FastLED.setBrightness(Brightness);
}

void loop() 
{
  EVERY_N_MILLIS(50){fireworks(active);}
    FastLED.show();
  
}

/******************************************************************************/




struct fworkObj{
  const int sparks = NUM_LEDS/3; //
  const float decele = 0.08; // 0.11
  const float veloMin = 0.1;
  const uint8_t minFly = 5; 
  const uint8_t maxFly = 13 ;
  uint8_t type[NUM_LEDS/3];
  float heat[NUM_LEDS/3];
  CRGB lcolor[NUM_LEDS/3];
  float location[NUM_LEDS/3];
  float velocity[NUM_LEDS/3];
  bool inv[NUM_LEDS/3];
  bool lastInv = false;
  
};
fworkObj fw;
CRGB greenColors[] = {
  CRGB::Chartreuse,
  CRGB(0, 255, 47),
  CRGB(41, 241, 141),
  CRGB(28, 188, 0),
  CRGB(22, 255, 96),
  CRGB(67, 255, 86),
  CRGB(107, 240, 12)};
CRGB randomColor(){ return greenColors[random(ARRAY_SIZE(greenColors))];}

bool fireworks(bool active)
{
  fadeToBlackBy(leds, NUM_LEDS, 96);
  if (sparkCount() == 0){ float sparkVel = getVelByDist(fw.minFly, fw.maxFly);
    newSpark(1, CRGB(29, 48, 48), 0, sparkVel, fw.lastInv);
    fw.lastInv = !fw.lastInv;
  }
  updateFireworks();
  showFireworks();
  return (!active && alldead());
}



void updateFireworks() {
  for (int i = 0; i < fw.sparks; i++) {

    switch (fw.type[i]) {

      case 0:  break;

      case 1: // rising
        fw.location[i] += constrain(fw.velocity[i], -1.0, 1.0);
        fw.velocity[i] -= fw.decele; // gravity
        if (fw.velocity[i] < fw.veloMin) { // Explode  
          fw.type[i] = 0;
          int numNew = 4 + random(4);
          float newDiamter = (((float)random16(20) / 10.0) + 2) / 1.8;
          CRGB newColor = randomColor();
          //CRGB newColor = CHSV(random(255), 192, 255);
          uint8_t firstDir = random(0,2);
          for (int j = 0; j < numNew; j++){
            float newVel = ((newDiamter /(float)numNew) * (float)(j) );
            float newVel2 = (float)random16(10)/20.0;
            if (((j+firstDir) % 2) == 0) {newVel *= -1; newVel2 *= -1;}
            newSpark(2, newColor, fw.location[i], newVel + newVel2, fw.inv[i]);
          }
        }
        break;
      case 2: // exploding
        fw.location[i] += fw.velocity[i];
        fw.heat[i] -= 0.07;
        if (fw.heat[i] <= 0)  {  fw.type[i] = 0;}
        else if (fw.heat[i] < 0.5){
          fw.lcolor[i] -= CRGB(128,128,128);
          fw.velocity[i] /= 3;
          if (random8(100) < 50){fw.velocity[i] += 0.05;}
          else {fw.velocity[i] -= 0.05;}
          if (random8(100) < 50){fw.lcolor[i] += CRGB(25,25,25);}
          else{fw.lcolor[i] -= CRGB(25,25,25);} 
        }
        break;
    }
  }
}
void newSpark(uint8_t type, CRGB newCol, int newLoc, float newVel, bool invert)
{
  int newfirework = nextdead();
  if (newfirework < 0) {return;}
  fw.inv[newfirework] = invert;
  fw.type[newfirework] = type;
  fw.heat[newfirework] = 1.0;
  fw.lcolor[newfirework] = newCol;
  fw.location[newfirework] = newLoc;
  fw.velocity[newfirework] = newVel;
}

void showFireworks(){
  for (int i = 0; i < fw.sparks; i++) {
    if (fw.type[i] > 0) {
      if ((fw.location[i] < 0)|| (fw.location[i] >= NUM_LEDS)){ fw.type[i] = 0; }// just kill it if it goes out of bounds. 
      else{ 
        int pos = fw.inv[i] ? NUM_LEDS-1 - (int)fw.location[i] : (int)fw.location[i];
        leds[pos] += fw.lcolor[i]; 
        }
    }
  }
}

int sparkCount(){
  int retval = 0;
  for (int i = 0; i < fw.sparks; i++){if (fw.type[i] > 0){retval++;}}
  return retval;
  }

int nextdead(){
  uint8_t addDead = 0;
  for (int i = 0; i < fw.sparks; i++){
    if (fw.type[i] == 0){
      addDead++;
      if(addDead > 2){return i;}
    }
  }
  return -1;
}

bool alldead(){ for (int i = 0; i < fw.sparks; i++){ if (fw.type[i] != 0){ return false;} } return true;}

float getVelByDist(uint8_t from, uint8_t to){
  float distGoal = (float)random((float)from*10, (float)to * 10 + 1) / 10;
  float dist = fw.veloMin ;
  float veloAdd = fw.veloMin;
  while(true){
    dist+= veloAdd > 1 ? 1 : veloAdd;
    veloAdd += fw.decele ;
    if(dist>distGoal){break;}
  }
  veloAdd -= fw.decele ;
  return veloAdd;
}
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
neopixels:DOUT
neopixels:VDD
neopixels:VSS
neopixels:DIN