#include "FastLED.h"
// Matrix size
#define NUM_ROWS 16
#define NUM_COLS 16
#define WIDTH NUM_COLS
#define HEIGHT NUM_ROWS
#define NUM_LEDS NUM_ROWS * NUM_COLS
#define MATRIX_TYPE 1
// LEDs pin
#define DATA_PIN 3
// LED brightness
#define BRIGHTNESS 255
// Define the array of leds
CRGB leds[NUM_LEDS];


/// ----------------------------- Wandering souls(Wind)(Lighters) ------------------------------
//(c)stepko

//--Settings------
#define Speed (255)
#define Scale (8)
#define Run (1) // 0-AllIn1Direction/1-beatsin/2-linear
#define col (1) //0-Whited/1-Colored/2-fromPallete
#define tails (1) //0-None/1-DimAll/2-Blur2D
#define reseting (10) //time in seconds, 0-off
#define broad (0) //only linear RUN
//---------------

#define LIGHTERS_AM ((WIDTH + HEIGHT))
int lightersPos[2][LIGHTERS_AM];
int8_t lightersSpeed[3][LIGHTERS_AM];
byte lcolor[LIGHTERS_AM];
byte mass[LIGHTERS_AM];
bool loadingFlag = true;

uint32_t getPixColor(uint32_t thisSegm) {
  uint32_t thisPixel = thisSegm;
  if (thisPixel > NUM_LEDS - 1) return 0;
  return (((uint32_t) leds[thisPixel].r << 16) | ((uint32_t) leds[thisPixel].g << 8) | (uint32_t) leds[thisPixel].b);
}
uint32_t getPixColorXY(uint8_t x, uint8_t y) {
  return getPixColor(XY(x, y));
}
void drawPixelXYF(float x, float y,
                  const CRGB & color) {
  if (x < 0 || y < 0 || x > ((float) WIDTH - 1) || y > ((float) HEIGHT - 1)) return;

  // extract the fractional parts and derive their inverses
  uint8_t xx = (x - (int) x) * 255, yy = (y - (int) y) * 255, ix = 255 - xx, iy = 255 - yy;
  // calculate the intensities for each affected pixel
#define WU_WEIGHT(a, b)((uint8_t)(((a) * (b) + (a) + (b)) >> 8))
  uint8_t wu[4] = {
    WU_WEIGHT(ix, iy),
    WU_WEIGHT(xx, iy),
    WU_WEIGHT(ix, yy),
    WU_WEIGHT(xx, yy)
  };
  // multiply the intensities by the colour, and saturating-add them to the pixels
  for (uint8_t i = 0; i < 4; i++) {
    int16_t xn = x + (i & 1), yn = y + ((i >> 1) & 1);
    CRGB clr = getPixColorXY(xn, yn);
    if (xn < (int) WIDTH - 1 && yn < (int) HEIGHT - 1 && yn > 0 && xn > 0) {
      clr.r = qadd8(clr.r, (color.r * wu[i]) >> 8);
      clr.g = qadd8(clr.g, (color.g * wu[i]) >> 8);
      clr.b = qadd8(clr.b, (color.b * wu[i]) >> 8);
    } else {
      clr.r = qadd8(clr.r, (color.r * 85) >> 8);
      clr.g = qadd8(clr.g, (color.g * 85) >> 8);
      clr.b = qadd8(clr.b, (color.b * 85) >> 8);
    }
    leds[XY(xn, yn)] = clr;
  }
#undef WU_WEIGHT
}
void draw() {
  if (loadingFlag) {
    loadingFlag = false;
    randomSeed(millis());
    for (byte i = 0; i < LIGHTERS_AM; i++) {
      if(Run == 2){
      lightersSpeed[0][i] = random(-20,20);
      lightersSpeed[1][i] = random(-20,20); 
      }else{
      lightersSpeed[0][i] = random(3, 25);
      lightersSpeed[1][i] = random(3, 25);}
      lightersSpeed[2][i] = random(3, 25);
      lightersPos[0][i] = random(0, WIDTH * 10);
      lightersPos[1][i] = random(0, HEIGHT * 10);
      mass[i] = random(15, 100);
      lcolor[i] = random(0, 9) * 28;
    }
  }
  if (tails == 0) {
    FastLED.clear();
  } else if (tails == 1) {
    fadeToBlackBy(leds, NUM_LEDS, 50);
  } else if (tails == 2) {
    blur2d(leds, WIDTH, HEIGHT, 30);
    fadeToBlackBy(leds, NUM_LEDS, 5);
  }
  for (byte i = 0; i < map(Scale, 1, 16, 2, LIGHTERS_AM); i++) {
    lcolor[i]++;
    if (Run == 1) {
      lightersPos[0][i] += beatsin16(lightersSpeed[0][i] / map(Speed, 1, 255, 10, 1), 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
      lightersPos[1][i] += beatsin16(lightersSpeed[1][i] / map(Speed, 1, 255, 10, 1), 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
    } else if (Run == 2) {
      lightersPos[0][i] += lightersSpeed[0][i]/ map(Speed, 1, 255, 10, 1);
      lightersPos[1][i] += lightersSpeed[1][i]/ map(Speed, 1, 255, 10, 1);
    }
    else {
      lightersPos[0][i] += beatsin88(lightersSpeed[0][0] * Speed, 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
      lightersPos[1][i] += beatsin88(lightersSpeed[1][0] * Speed, 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
    }
    if(broad){
      if ((lightersPos[0][i] <= 0) || (lightersPos[0][i] >= (WIDTH - 1) * 10)) lightersSpeed[0][i] = -lightersSpeed[0][i];
      if ((lightersPos[1][i] <= 0) || (lightersPos[1][i] >= (HEIGHT - 1) * 10)) lightersSpeed[1][i] = -lightersSpeed[1][i];
    }else{
    if (lightersPos[0][i] < 0) lightersPos[0][i] = (WIDTH - 1) * 10;
    if (lightersPos[0][i] > (WIDTH - 1) * 10) lightersPos[0][i] = 0;
    if (lightersPos[1][i] < 0) lightersPos[1][i] = (HEIGHT - 1) * 10;
    if (lightersPos[1][i] > (HEIGHT - 1) * 10) lightersPos[1][i] = 0;}
    if (col == 1) {
      drawPixelXYF((float) lightersPos[0][i] / 10, (float) lightersPos[1][i] / 10, CHSV(lcolor[i], 255, beatsin16((int) lightersSpeed[3] / (256 - Speed), 200, 255)));
    } else if (col == 2) {
      drawPixelXYF((float) lightersPos[0][i] / 10, (float) lightersPos[1][i] / 10, ColorFromPalette(OceanColors_p, lcolor[i], beatsin16((int) lightersSpeed[3] / (256 - Speed), 200, 255)));
    }
    else {
      drawPixelXYF((float) lightersPos[0][i] / 10, (float) lightersPos[1][i] / 10, CHSV(lcolor[i], 40, beatsin16((int) lightersSpeed[3] / (256 - Speed), 200, 255)));
    }
  }
  if(reseting>0){
  EVERY_N_SECONDS(reseting) {
    randomSeed(millis());
    for (byte i = 0; i < map(Scale, 1, 16, 2, LIGHTERS_AM); i++) {
      if(Run == 2){
      lightersSpeed[0][i] = random(-20,20);
      lightersSpeed[1][i] = random(-20,20); 
      }else{
      lightersSpeed[0][i] = random(3, 25);
      lightersSpeed[1][i] = random(3, 25);}
      lightersSpeed[2][i] = random(3, 25);
      mass[i] + random(-25, 25);
    }
  }}
}

void setup() {
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
}

void loop() {
  draw();
  FastLED.show();
} //loop


uint16_t XY (uint8_t x, uint8_t y) {

  if ((y % 2 == 0) || MATRIX_TYPE)                     // если чётная строка
  {
    return ((uint32_t)y * WIDTH + x) % (WIDTH * HEIGHT);
  }
  else                                                      // если нечётная строка
  {
    return ((uint32_t)y  * WIDTH + WIDTH - x - 1) % (WIDTH * HEIGHT);
  }
}