#include "FastLED.h"

// Matrix size
#define NUM_ROWS 32
#define NUM_COLS 32
#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];

#define SPARKS_AM WIDTH
float FADE_KOEF = 10;
float SpeedK = .98;
float SpeedDecX = .01;
float SpeedDecY = 0;
#define Board 1
struct {
  bool GravityX, GravityY;
  int16_t genPosX, genPosY;
  int16_t gravityPosX, gravityPosY;

  void setGenPos(int16_t x, int16_t y) {
    genPosX = x;
    genPosY = y;
  }

  void setGravityPos(int16_t x, int16_t y) {
    GravityX = 1; GravityY = 1;
    gravityPosX = x;
    gravityPosY = y;
  }

  void setGravityPos(int16_t x, bool y) {
    if (y) {
      gravityPosX = x;
      GravityX = 1; GravityY = 0;
    }
    else {
      gravityPosY = y;
      GravityX = 0; GravityY = 1;
    }
  }

} PS_Var;

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 wu_pixel(uint32_t x, uint32_t y, CRGB * col) { //awesome wu_pixel procedure by reddit u/sutaburosu
  // extract the fractional parts and derive their inverses
  uint8_t xx = x & 0xff, yy = y & 0xff, 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++) {
    uint16_t xy = XY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1));
    leds[xy].r = qadd8(leds[xy].r, col -> r * wu[i] >> 8);
    leds[xy].g = qadd8(leds[xy].g, col -> g * wu[i] >> 8);
    leds[xy].b = qadd8(leds[xy].b, col -> b * wu[i] >> 8);
  }
}

bool loadingFlag = true;
bool boom;
struct {
  int16_t PosX, PosY;
  float SpeedX, SpeedY;
  float Fade;
  byte Color;

  void reg() {
    PosX = PS_Var.genPosX;
    PosY = PS_Var.genPosY;
    SpeedX = random(-10, 10);
    SpeedY = random(-10, 10);
    Fade = 255;
    Color = random();
  }

  void phisics() {
    if (SpeedK) {
      if (PS_Var.GravityX) {
        if (PS_Var.gravityPosX < PosX)
          SpeedX -= SpeedK;
        else
          SpeedX += SpeedK;
      }
      if (PS_Var.GravityY) {
        if (PS_Var.gravityPosY < PosY)
          SpeedY -= SpeedK;
        else
          SpeedY += SpeedK;
      }
    }
    Fade -= (255. / (float)((HEIGHT + WIDTH) * FADE_KOEF));
    if (SpeedDecX || SpeedX) {
      if (SpeedX > 0)
        SpeedX -= SpeedDecX;
      else
        SpeedX += SpeedDecX;
      if (abs(SpeedX) <= SpeedDecX)
        SpeedX = 0;
    }
    if (SpeedDecY || SpeedY) {
      if (SpeedY > 0)
        SpeedY -= SpeedDecY;
      else
        SpeedY += SpeedDecY;
      if (abs(SpeedY) <= SpeedDecY)
        SpeedX = 0;
    }
    if (Board) {
      if (PosX < 0 || PosX >= WIDTH * 10) SpeedX = -SpeedX;
      if (PosY < 0) SpeedY = -SpeedY;
    }
    PosX += /*constrain(*/SpeedX/*, -10, 10)*/;
    PosY += /*constrain(*/SpeedY/*, -10, 10)*/;
    if (SpeedY > 0 && SpeedY < 1 && PosY < 20)
      reg();
  }

  void render(CRGB Col) {
    phisics();
    if (PosX < ((HEIGHT - 1) * 10) && PosY >= 0)
      if (PosX < ((WIDTH - 1) * 10) && PosX >= 0) {
        CRGB color = Col;
        wu_pixel(PosX * 25.6, PosY * 25.6, & color);
      }
  }

  void setRegenRule(bool b) {
    if (b) reg();
  }
} dot[SPARKS_AM];


void draw() {
  PS_Var.setGenPos(beatsin16(10, 0, WIDTH * 10), beatsin16(10, 0, HEIGHT * 10, 0, 16384));
  if (loadingFlag) {
    PS_Var.setGravityPos(0, false);
    for (byte i = 0; i < SPARKS_AM; i++) {
      dot[i].reg();
      dot[i].Fade = random();
      for (byte a = 0; a < i; a++) {
        dot[i].phisics();
      }
    }
    loadingFlag = false;
  }
  //fadeToBlackBy(leds, NUM_LEDS, 20);
  FastLED.clear();
  for (byte i = 0; i < SPARKS_AM; i++) {
    dot[i].setRegenRule((dot[i].Fade <= 35) ? 1 : 0);
    dot[i].render(CHSV(dot[i].Color, 255, constrain(dot[i].Fade, 32, 255)));
  }
  delay(16);
}

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

void loop() {
  draw();
  FastLED.show();
  //static int frame = 0;
  //if (frame++ % 32 == 0)
  //  Serial.println(FastLED.getFPS());

} //loop



uint16_t XY (uint8_t x, uint8_t y) {
  if (x >= WIDTH || y >= HEIGHT) return;
  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);
  }
}