#include <FastLED.h>

#define LED_PIN  3

#define COLOR_ORDER GRB
#define CHIPSET     WS2811

#define BRIGHTNESS 150

CRGB col1  = CRGB::White ;
CRGB col2  = CRGB::Fuchsia; 
CRGB  col3  = CRGB::Maroon; 
CRGB black  = CRGB::Black;
CRGB col4 = CRGB::Green;
CRGB col5 = CRGB:: Yellow;
CRGB col6 = CRGB:: Blue;

CRGBPalette16 palDrop = {
                        col1,  
                        col1,  
                        col1,
                        col3,
                        
                        col5, 
                        col5,
                        col5,
                        col1,
                        
                        col6,
                        col6,
                        col6,
                        col5,
                        
                        col4,
                        col4,
                        col4,
                        col1 };

// Helper functions for an two-dimensional XY matrix of pixels.
// Simple 2-D demo code is included as well.
//
//     XY(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             No error checking is performed on the ranges of x and y.
//
//     XYsafe(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//             Error checking IS performed on the ranges of x and y, and an
//             index of "-1" is returned.  Special instructions below
//             explain how to use this without having to do your own error
//             checking every time you use this function.
//             This is a slightly more advanced technique, and
//             it REQUIRES SPECIAL ADDITIONAL setup, described below.


// Params for width and height
const uint8_t kMatrixWidth = 5;
const uint8_t kMatrixHeight = 30;

// Param for different pixel layouts
const bool    kMatrixSerpentineLayout = false; 
// Set 'kMatrixSerpentineLayout' to false if your pixels are
// laid out all running the same way, like this:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//     .----<----<----<----'
//     |
//     5 >  6 >  7 >  8 >  9
//                         |
//     .----<----<----<----'
//     |
//    10 > 11 > 12 > 13 > 14
//                         |
//     .----<----<----<----'
//     |
//    15 > 16 > 17 > 18 > 19
//
// Set 'kMatrixSerpentineLayout' to true if your pixels are
// laid out back-and-forth, like this:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//                         |
//     9 <  8 <  7 <  6 <  5
//     |
//     |
//    10 > 11 > 12 > 13 > 14
//                        |
//                        |
//    19 < 18 < 17 < 16 < 15
//
// Bonus vocabulary word: anything that goes one way
// in one row, and then backwards in the next row, and so on
// is call "boustrophedon", meaning "as the ox plows."


// This function will return the right 'led index number' for
// a given set of X and Y coordinates on your matrix.
// IT DOES NOT CHECK THE COORDINATE BOUNDARIES.
// That's up to you.  Don't pass it bogus values.
//
// Use the "XY" function like this:
//
//    for( uint8_t x = 0; x < kMatrixWidth; x++) {
//      for( uint8_t y = 0; y < kMatrixHeight; y++) {
//
//        // Here's the x, y to 'led index' in action:
//        leds[ XY( x, y) ] = CHSV( random8(), 255, 255);
//
//      }
//    }
//
//
uint16_t XY( uint8_t x, uint8_t y)
{
  uint16_t i;

  if( kMatrixSerpentineLayout == false) {
    i = (y * kMatrixWidth) + x;
  }

  if( kMatrixSerpentineLayout == true) {
    if( y & 0x01) {
      // Odd rows run backwards
      uint8_t reverseX = (kMatrixWidth - 1) - x;
      i = (y * kMatrixWidth) + reverseX;
    } else {
      // Even rows run forwards
      i = (y * kMatrixWidth) + x;
    }
  }

  return i;
}


// Once you've gotten the basics working (AND NOT UNTIL THEN!)
// here's a helpful technique that can be tricky to set up, but
// then helps you avoid the needs for sprinkling array-bound-checking
// throughout your code.
//
// It requires a careful attention to get it set up correctly, but
// can potentially make your code smaller and faster.
//
// Suppose you have an 8 x 5 matrix of 40 LEDs.  Normally, you'd
// delcare your leds array like this:
//    CRGB leds[40];
// But instead of that, declare an LED buffer with one extra pixel in
// it, "leds_plus_safety_pixel".  Then declare "leds" as a pointer to
// that array, but starting with the 2nd element (id=1) of that array:
//    CRGB leds_with_safety_pixel[41];
//    CRGB* const leds( leds_plus_safety_pixel + 1);
// Then you use the "leds" array as you normally would.
// Now "leds[0..N]" are aliases for "leds_plus_safety_pixel[1..(N+1)]",
// AND leds[-1] is now a legitimate and safe alias for leds_plus_safety_pixel[0].
// leds_plus_safety_pixel[0] aka leds[-1] is now your "safety pixel".
//
// Now instead of using the XY function above, use the one below, "XYsafe".
//
// If the X and Y values are 'in bounds', this function will return an index
// into the visible led array, same as "XY" does.
// HOWEVER -- and this is the trick -- if the X or Y values
// are out of bounds, this function will return an index of -1.
// And since leds[-1] is actually just an alias for leds_plus_safety_pixel[0],
// it's a totally safe and legal place to access.  And since the 'safety pixel'
// falls 'outside' the visible part of the LED array, anything you write
// there is hidden from view automatically.
// Thus, this line of code is totally safe, regardless of the actual size of
// your matrix:
//    leds[ XYsafe( random8(), random8() ) ] = CHSV( random8(), 255, 255);
//
// The only catch here is that while this makes it safe to read from and
// write to 'any pixel', there's really only ONE 'safety pixel'.  No matter
// what out-of-bounds coordinates you write to, you'll really be writing to
// that one safety pixel.  And if you try to READ from the safety pixel,
// you'll read whatever was written there last, reglardless of what coordinates
// were supplied.

#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
CRGB leds_plus_safety_pixel[ NUM_LEDS + 1];
CRGB* const leds( leds_plus_safety_pixel + 1);
#include "SFX.h"
#include "Waterfall.h"
uint16_t XYsafe( uint8_t x, uint8_t y)
{
  if( x >= kMatrixWidth) return -1;
  if( y >= kMatrixHeight) return -1;
  return XY(x,y);
}

// Demo that USES "XY" follows code below
class drop {
    private:
    float speed; 
    float fRow;
    float fCol;    
    
    int life;
    int lifeExp; 
    int palShift;
        
    bool alight; 
    CRGBPalette16 pal;

    public:
    drop(){
        alight = false;   
    };

    bool  eject (float c, float r, float s, CRGBPalette16  p ){
        if (alight) return false;
        palShift = 1;
        alight  = true;
        fCol = c;
        fRow = r;
        speed = s;
        pal = p; 
        lifeExp = random8(200) + 200;
        life = lifeExp; 
        return true;
    };

    bool display(){
      int index; 
      if (!alight) return alight ;
      life--;
      index =64.0f - ((float)life/(float)lifeExp * 64.0f) + (64.0f * palShift); 
        
      fRow +=speed;
    
      // apply gravity to speed
      // Make gravity have less of an effect as
      // the drop reaches the end of its lifespan
      // i.e.as it gets lighter and floats  
      speed -= .0023 * (float)life/(float)lifeExp; 
      speed *= 0.97f; 

      // evapourate the drop  if it reaches the edge
      // or reaches the end of its lifespan
      if (fRow<5 || fRow >=kMatrixHeight){
        alight  = false;
        return false ;
        }
      int shade= constrain((lifeExp-life)*4,0,100); 
      CRGB col = CHSV(120, 100-shade,255);
      if (life<0){
        col = CRGB::White;
        alight= false;
      }
      leds[XY((long)fCol, (long)fRow)] = col  ; 
      return alight;
    }
 }; 
const int num_drops = 1;
drop droplets[num_drops];

void ejectDrop (int col, int row){
  static unsigned long  count[5]; 
  unsigned long now = millis();
  long delay = random(600);
  if (now - count[col]>delay) {
    count[col] = now;
    for (int i=0; i<num_drops; i++){
      if (droplets[i].eject(col,row,0.26f + (i/40.0f) , palDrop )) break;
    }
  }
}

void Splash(int i, int wave ){
  const int noiseLength = 100;
  const int splashHeight=7;
  static uint8_t noise [kMatrixWidth][noiseLength];

  static uint8_t  shift = 0;
  static float fShift = 0;
  
  // Check if we need more fill_noise
  if (shift ==0){
    fShift = noiseLength - splashHeight;
    memset(noise, 0, sizeof(noise));
    fill_raw_2dnoise8 (*noise, kMatrixWidth, noiseLength, 1, random8(), 50, random8(), 50, 120);
  }
  fShift -= 0.012f;
  shift = (long)fShift;
 
  for (int j=0; j<splashHeight; j++){
    int plane =  (3 * (17-wave)) + (j*25);
    CRGB col = sky(i , j+wave); 
    if (noise[i][j+shift] > plane) {
      if (j>3) col =CHSV(170 - (wave*3),90,255) ;
      else col = CHSV(170-(3*wave),255,255); 
    }
    leds [XY(i,j + wave)] = col; 
    if ((j+wave)>17 && col != sky(i, j+wave))  ejectDrop(i, j+wave);
  }
}

CRGB sky(int i, int j){
  return CRGB((j*7) + +2,(j*7),(j*7)) ; 
}

void drawFantasia(){
  const int splashWidth=3 ;
  static uint8_t start=0; 
  FastLED.clear(false);
  uint8_t wave=beatsin8(40,2,8,0,0) + beatsin8(10,0,7,0,0);
  uint8_t trough = 16-wave;

  //Draw some sky
  for (int i = 0; i< kMatrixWidth; i++){
    for(int j=0;j<kMatrixHeight; j++){
      leds[XY(i,j)] = sky(i,j); 
    }
  }
  if (trough == wave)
    start++;
    start = mod8(start, kMatrixWidth); 
  for (int iT  = splashWidth; iT < kMatrixWidth; iT ++){
    int i = start + iT ; 
    i = mod8(i,kMatrixWidth); 
    for (int j = 0; j<trough; j++){
      leds[XY(i,j )] = CHSV(170-(3*j),255, 255) ;
    }
    Splash(i,trough);
  }
  for (int iT=0; iT <splashWidth;iT++) {
    int i= start + iT; 
    i = mod8(i,kMatrixWidth); 
    for (int f=0; f<wave; f++) {
      leds[XY(i,f)] = CHSV(170-(3*f) ,255,255);
    } 
    Splash(i, wave);
  }
  for(int d=0; d<num_drops; d++ ){
    droplets[d].display();
  }
}

void loop()
{
  //drawFantasia();
  displayWaterfall();
  FastLED.show();
}

void setup() {
  Serial.begin(9600);
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
  FastLED.setBrightness( BRIGHTNESS );
}
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