#include "FastLED.h"

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

byte effect = 1;

const int SPARKS_AM = HEIGHT * 4;
float FADE_KOEF = .6;
float SpeedK = 2;
float SpeedDecX = .1;
float SpeedDecY = 0;

int sparksPos[2][SPARKS_AM];
float sparksSpeed[2][SPARKS_AM];
float sparksFade[SPARKS_AM];
byte sparksColor[SPARKS_AM];
float sparksSat[SPARKS_AM];
static byte period = 10;

int genPos[2];
int gravityPos[2];
bool run = true;

bool loadingFlag = true;
void reg(byte id) {
  sparksPos[0][id] = (genPos[0] == -1205)? random(0, WIDTH * 10) : genPos[0];
  sparksPos[1][id] = (genPos[1] == -1205)? random(0,HEIGHT * 10) : genPos[1];
  byte al = random8();
  sparksSpeed[0][id] = random(-10, 10);
  sparksSpeed[1][id] = random(-5, 20);
  sparksColor[id] = random();
  sparksFade[id] = 255;
}

void phisics(byte id) {
  if (SpeedK) {
    if (gravityPos[0] != -1205) {
      if (gravityPos[0] < sparksPos[0][id])
        sparksSpeed[0][id] -= SpeedK;
      else
        sparksSpeed[0][id] += SpeedK;
    }
    if (gravityPos[1] != -1205) {
      if (gravityPos[1] < sparksPos[1][id])
        sparksSpeed[1][id] -= SpeedK;
      else
        sparksSpeed[1][id] += SpeedK;
    }
  }
  sparksFade[id] -= (255. / float(HEIGHT * FADE_KOEF));
  sparksSat[id] += (255. / (float)((WIDTH + WIDTH) * (FADE_KOEF - 0.2)));
  if (SpeedDecX && sparksSpeed[0][id]) {
    if (sparksSpeed[0][id] > 0)
      sparksSpeed[0][id] -= SpeedDecX;
    else
      sparksSpeed[0][id] += SpeedDecX;
    if (abs(sparksSpeed[0][id]) <= SpeedDecX)
      sparksSpeed[0][id] = 0;
  }
  if (SpeedDecY && sparksSpeed[1][id]) {
    if (sparksSpeed[1][id] > 0)
      sparksSpeed[1][id] -= SpeedDecY;
    else
      sparksSpeed[1][id] += SpeedDecY;
    if (abs(sparksSpeed[1][id]) <= SpeedDecY)
      sparksSpeed[1][id] = 0;
  }
  sparksPos[0][id] += sparksSpeed[0][id];
  sparksPos[1][id] += sparksSpeed[1][id];
}

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

void render(byte id, CRGB Col) {
  phisics(id);
  if (sparksPos[1][id] < ((HEIGHT - 1) * 10) and sparksPos[1][id] >= 0)
    if (sparksPos[0][id] < ((WIDTH - 1) * 10) and sparksPos[0][id] >= 0) {
      CRGB color = Col;
      wu_pixel(sparksPos[0][id] * 25.6, sparksPos[1][id] * 25.6, & color);
    }
}

void setGenPos(int x, int y) {
  genPos[0] = x;
  genPos[1] = y;
}

void setGravityPos(int x, int y) {
  gravityPos[0] = x;
  gravityPos[1] = y;
}

void setRegenRule(byte id, bool b) {
  if (b) reg(id);
}

void start() {
  for (byte i = 0; i < SPARKS_AM; i++) {
    reg(i);
    for (byte a = 0; a < i; a++) {
      setRegenRule(a, (sparksPos[0][a] <= 0 || sparksPos[0][a] >= (WIDTH - 1) * 10 || sparksPos[1][a] < 0 || sparksPos[1][a] >= (HEIGHT - 1) * 10 || sparksFade[a] < 20) ? 1 : 0);
      phisics(a);
    }
  }
}

void drawFire() {
  setGenPos(WIDTH * 5, HEIGHT * 1);
  byte noise = inoise8(millis() / 10);
  setGravityPos(map(noise, 0, 255, WIDTH * 4, WIDTH * 6), map(abs(128 - noise), 0, 127, HEIGHT * 6, HEIGHT * 9));
  if (loadingFlag) {
    start();
    loadingFlag = false;
  }
  //FastLED.clear();
  //fadeToBlackBy(leds, NUM_LEDS, 20);
  for (byte i = 0; i < SPARKS_AM; i++) {
    setRegenRule(i, (sparksPos[0][i] <= 0 || sparksPos[0][i] >= (WIDTH - 1) * 10 || sparksPos[1][i] < 0 || sparksPos[1][i] >= (HEIGHT - 1) * 10 || sparksFade[i] < 20) ? 1 : 0);
    render(i, ColorFromPalette(HeatColors_p, sparksFade[i]));
  }
  blur2d(leds, WIDTH, HEIGHT, 32);
  delay(16);
}

void drawGravityDemo() {
  fadeToBlackBy(leds, NUM_LEDS, 20);
  setGenPos(beatsin16(10, 0, WIDTH * 10), beatsin16(10, 0, HEIGHT * 10, 0, 16384));
  setGravityPos((WIDTH / 2) * 10, (HEIGHT / 2) * 10);
  for (byte i = 0; i < SPARKS_AM; i++) {
    setRegenRule(i, (sparksPos[0][i] <= 0 || sparksPos[0][i] >= (WIDTH - 1) * 10 || sparksPos[1][i] < 0 || sparksPos[1][i] >= (HEIGHT - 1) * 10 || sparksFade[i] <= 35) ? 1 : 0);
    render(i, CHSV(sparksColor[i], 255, constrain(sparksFade[i], 32, 255)));
  }
  delay(16);
}

void drawBengalFire() {
  fadeToBlackBy(leds, NUM_LEDS, beatsin8(5, 20, 100));
  setGenPos(WIDTH * 5, HEIGHT * 5);
  setGravityPos(-1205, 0);
  for (byte i = 0; i < SPARKS_AM; i++) {
    setRegenRule(i, (sparksPos[0][i] <= 0 || sparksPos[0][i] >= (WIDTH - 1) * 10 || sparksPos[1][i] < 0) ? 1 : 0);
    render(i, CHSV(sparksColor[i], constrain(sparksSat[i], 5, 255), constrain(sparksFade[i], 32, 255)));
    EVERY_N_SECONDS(period) {
      for (byte i = 0; i < SPARKS_AM; i++) reg(i);
      period = random(10, 60);
    }
  }
  delay(16);
}

void drawWind() {
  setGenPos(0,-1205);
  byte noise = inoise8(millis() / 10);
  setGravityPos(WIDTH * 11, map(abs(128 - noise), 0, 127, HEIGHT, HEIGHT * 9));
  if (loadingFlag) {
    start();
    loadingFlag = false;
  }
  //FastLED.clear();
  fadeToBlackBy(leds, NUM_LEDS, 20);
  for (byte i = 0; i < SPARKS_AM; i++) {
    setRegenRule(i, (sparksPos[0][i] >= (WIDTH - 1) * 10 || sparksPos[1][i] < 0 || sparksPos[1][i] >= (HEIGHT - 1) * 10 || sparksFade[i] < 20) ? 1 : 0);
    render(i, CHSV(0,0,sparksFade[i]));
  }
  //blur2d(leds, LED_COLS, LED_ROWS, 32);
  delay(16);
}

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

void loop() {
  EVERY_N_SECONDS(10) {(effect== 3) ? effect = 0 : effect++; FastLED.clear(); }
  switch (effect) {
    case 0: drawFire(); break;
    case 1: drawGravityDemo(); break;
    case 2: drawBengalFire(); break;
    case 3: drawWind(); break;
  }
  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);
  }
}