// Source: https://github.com/s-marley/FastLED-basics

#include <FastLED.h>
#include "palette.h"
#define NUM_LEDS  20

#define LED_PIN   2
#define SECONDS_PER_PALETTE 10
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
#define LEDS_OFF -1
#define LEDS_COOL 0
#define LEDS_HEAT 1
#define LEDS_BOOM 2

FASTLED_USING_NAMESPACE
CRGB leds[NUM_LEDS];
CRGB ledsNext[NUM_LEDS];

const int8_t ledIdx2Pos[NUM_LEDS][2] = { 
  { 0, 7 },	{ 0, 6 },	{ 0, 5 },	{ 0, 4 },	{ 0, 3 },	{ 0, 2 },	{ 0, 1 },	//left side
  { 1, 0 },	{ 2, 0 },	{ 3, 0 },	{ 4, 0 },	{ 5, 0 },	{ 6, 0 },	          //middle
  { 7, 1 },	{ 7, 2 },	{ 7, 3 },	{ 7, 4 },	{ 7, 5 },	{ 7, 6 },	{ 7, 7 }  //right side
  };


void setup() {
  FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(50);
  Serial.begin(115200);
}


typedef void (*ledAniList[])();
ledAniList animations = {  coolPlasma,heatPulse,greenExplosion };

int8_t aniIdxNew = LEDS_COOL;



void loop() {
  
  ledsMain();


}

void ledsMain(){

  EVERY_N_MILLIS(20){
    
    chkAniIdx();
    //animations[]();
    }
  //
 EVERY_N_MILLIS(50){aniIdxNew = map(analogRead(A0),0,1023,-1,2);}
 // EVERY_N_SECONDS(8){ aniIdxNew++ % 3;}
}

void chkAniIdx(){
  static int8_t aniIdxLast = aniIdxNew;
  static uint16_t fadeval = 0;
  if(aniIdxNew == -1 && aniIdxLast == -1){return;} 
  else if(aniIdxNew != -1 && aniIdxLast == -1){fadeval = 127; aniIdxLast = aniIdxNew; } 
  animations[aniIdxLast]();
  if(aniIdxLast == aniIdxNew && fadeval == 0){FastLED.show(); return; }
  else if(aniIdxLast != aniIdxNew && fadeval < 127){fadeval+=2;}
  else if(aniIdxLast != aniIdxNew && fadeval >= 127){ aniIdxLast = aniIdxNew; FastLED.clear(); FastLED.show(); return;}
  else if(aniIdxLast == aniIdxNew && fadeval >= 127 && fadeval < 255){fadeval+=2; if(aniIdxNew == LEDS_BOOM){fadeval=0;}}
  else if(aniIdxLast == aniIdxNew && fadeval >= 255){fadeval=0;}
  fadeLightBy	(	leds, NUM_LEDS,  cos8(fadeval+127));//
  if(aniIdxLast != -1){FastLED.show();}
}




void coolPlasma(){
  static bool stateLast = false;
  if( currentPal != cool_pal){currentPal = cool_pal;}
    static uint8_t gHue = 0;
    const float plasmaSpeed = 0.25; //0.1 to 1
    const uint8_t plasmaZoom = 30; // 20 to 80
    uint16_t ms = plasmaSpeed * millis();   
  for( int i = 0; i< NUM_LEDS; i++){
    byte noises =  inoise8 (i * plasmaZoom, 6 * plasmaZoom, ms);
    leds[i] = ColorFromPalette(currentPal, noises+gHue, 255, LINEARBLEND);
    EVERY_N_MILLIS(60){gHue++;}
  }

}

void heatPulse(){
    if( currentPal != heat_pal){currentPal = heat_pal;}
    static uint8_t fadeVal = 0;
    const uint16_t brightnessScale =150;// 150;
    const uint16_t indexScale = 100;
    for (int i = 0; i < NUM_LEDS; i++) {
      uint8_t brightness = inoise8(i * brightnessScale, millis() / 5);
      uint8_t index = inoise8(i * indexScale, millis() /10);
      leds[i] = ColorFromPalette( currentPal , index, brightness);
    }
  fadeLightBy	(	leds, NUM_LEDS, triwave8(fadeVal) * 0.75);
  fadeVal+=2;

}



void greenExplosion(){EVERY_N_MILLIS(50){fireworks();}}



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

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))];}





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

}

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 freeIdx = nextdead();
  if (freeIdx < 0) {return;}
  fw.inv[freeIdx] = invert;
  fw.type[freeIdx] = type;
  fw.heat[freeIdx] = 1.0;
  fw.lcolor[freeIdx] = newCol;
  fw.location[freeIdx] = newLoc;
  fw.velocity[freeIdx] = 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 n = 0; for (int i = 0; i < fw.sparks; i++){if (fw.type[i] > 0){n++;}} return n; }

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
pot1:GND
pot1:SIG
pot1:VCC
neopixels1:DOUT
neopixels1:VDD
neopixels1:VSS
neopixels1:DIN
neopixels2:DOUT
neopixels2:VDD
neopixels2:VSS
neopixels2:DIN