#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];


//// ----------------------------- Wind ------------------------------
//(c)stepko
#define LIGHTERS_AM ((WIDTH+HEIGHT)/2)
int lightersPosX[LIGHTERS_AM];
int lightersPosY[LIGHTERS_AM];
int PosRegX[LIGHTERS_AM];
int PosRegY[LIGHTERS_AM];
byte lightersSpeedX[LIGHTERS_AM];
byte lightersSpeedY[LIGHTERS_AM];
byte lightersSpeedZ[LIGHTERS_AM];
byte lcolor[LIGHTERS_AM];
byte mass[LIGHTERS_AM];
bool loadingFlag = true;

uint8_t speed = 255;
uint8_t scale = 16;
bool chaos = 1;

void drawPixelXYF(float x, float y, CRGB color)
{
  // if (x < 0 || y < 0 || x > ((float)WIDTH - 1) || y > ((float)HEIGHT - 1)) return;
  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 = leds[XY(xn, yn)];
    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);
    leds[XY(xn, yn)] = clr;
  }
}

void windBallsRoutine() {
  if (loadingFlag) {
    loadingFlag = false;
    randomSeed(millis());
    for (byte i = 0; i < LIGHTERS_AM; i++) {
      PosRegX[i] = random(0, WIDTH * 10);
      PosRegY[i] = random(0, HEIGHT * 10);
      lightersSpeedX[i] = random(25, 50);
      lightersSpeedY[i] = random(25, 50);
      mass[i] = random(30, 255);
      lcolor[i] = random(0, 9) * 28;
    }
  }
  fadeToBlackBy(leds, NUM_LEDS, 50);
  for (byte i = 0; i < map(scale, 1, 16, 2, LIGHTERS_AM); i++) {
    lcolor[i]++;
    if (chaos) {
      lightersPosX[i] = beatsin16(lightersSpeedX[i] * speed, PosRegX[i], PosRegX[i] + mass[i] * ((HEIGHT + WIDTH) / 16));
      lightersPosY[i] = beatsin16(lightersSpeedY[i] * speed, PosRegY[i], PosRegY[i] + mass[i] * ((HEIGHT + WIDTH) / 16));
    }
    else {
      lightersPosX[i] = beatsin16(lightersSpeedX[0] * speed, PosRegX[i], PosRegX[i] + mass[i] * ((HEIGHT + WIDTH) / 16));
      lightersPosY[i] = beatsin16(lightersSpeedY[0] * speed, PosRegY[i], PosRegY[i] + mass[i] * ((HEIGHT + WIDTH) / 16));
    }
    if (lightersPosX[i] < 0) lightersPosX[i] = (WIDTH - 1) * 10 - lightersPosX[i] - (WIDTH - 1) * 10;
    if (lightersPosX[i] > (WIDTH - 1) * 10) lightersPosX[i] = lightersPosX[i] - (WIDTH - 1) * 10;
    if (lightersPosY[i] < 0)lightersPosY[i] = (HEIGHT - 1) * 10 - lightersPosY[i] - (HEIGHT - 1) * 10;;
    if (lightersPosY[i] > (HEIGHT - 1) * 10) lightersPosY[i] = lightersPosY[i] - (HEIGHT - 1) * 10;
    drawPixelXYF((float)lightersPosX[i] / 10, (float)lightersPosY[i] / 10, CHSV(lcolor[i], 255, beatsin8(lightersSpeedZ[i] / map(speed, 1, 255, 10, 1), 128, 255)));
  }
  EVERY_N_SECONDS(5) {

    for (byte i = 0; i < map(scale, 1, 16, 2, LIGHTERS_AM); i++) {
      lightersSpeedX[i] = random(25, 50);
      lightersSpeedY[i] = random(25, 50);
      mass[i] + random(-25, 25);
      PosRegX[i] - 20;
      if (PosRegX[i] < 0) PosRegX[i] = (WIDTH - 1) * 10;
      PosRegY[i] + 20;
      if (PosRegY[i] > (HEIGHT - 1) * 10) PosRegY[i] = 0;
    }
  }
}




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

void loop() {
  windBallsRoutine();
  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);
  }
}