#include "FastLED.h"
// Matrix size
#define HEIGHT 32
#define WIDTH 32
#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];

//// ----------------------------- Wandering souls ------------------------------
//(c)stepko
//https://editor.soulmatelights.com/gallery/503
//--Settings------
#define Speed 255
#define Scale 8
#define Run 3 // 0-AllIn1Direction/1-beatsin/2-linear/3-circular
#define col 1 //0-Whited/1-Colored/2-fromPallete
#define trace 2 //0-None/1-DimAll/2-Blur2D/3-wings
#define reseting 10 //reset time in seconds, 0-off
#define subPixel 1 //
#define broad 1 //
//---------------
#define LIGHTERS_AM WIDTH + HEIGHT
int lightersPosX[LIGHTERS_AM];
int lightersPosY[LIGHTERS_AM];
uint16_t lightersSpeedX[LIGHTERS_AM];
uint16_t lightersSpeedY[LIGHTERS_AM];
byte lightersSpeedZ[LIGHTERS_AM];
byte lcolor[LIGHTERS_AM];
byte mass[LIGHTERS_AM];
bool loadingFlag = true;

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 draw() {
  if (loadingFlag) {
    loadingFlag = false;
    randomSeed(millis());
    for (byte i = 0; i < LIGHTERS_AM; i++) {
      if (Run == 2) {
        lightersSpeedX[i] = random(-10, 10);
        lightersSpeedY[i] = random(-10, 10);
      } else if (Run == 3) {
        lightersSpeedX[i] = random(-10, 10); //angle speed
        lightersSpeedY[i] = random(0, 360); //angle
        mass[i] = random(5, 10); //vspeed
      } else {
        lightersSpeedX[i] = random(3, 25);
        lightersSpeedY[i] = random(3, 25);
        mass[i] = random(15, 100);
      }
      lightersSpeedZ[i] = random(3, 25);
      lightersPosX[i] = random(0, WIDTH * 10);
      lightersPosY[i] = random(0, HEIGHT * 10);
      lcolor[i] = random(0, 9) * 28;
    }
  }
  switch (trace) {
    case 0:
      FastLED.clear();
      break;
    case 1:
      fadeToBlackBy(leds, NUM_LEDS, 50);
      break;
    case 2:
      blur2d(leds, WIDTH, HEIGHT, 30);
      fadeToBlackBy(leds, NUM_LEDS, 5);
      break;
    case 3:
      fadeToBlackBy(leds, NUM_LEDS, 200);
      break;
  }

  for (byte i = 0; i < map(Scale, 1, 16, 2, LIGHTERS_AM); i++) {
    lcolor[i]++;
    switch (Run) {
      case 0:
        lightersPosX[i] += beatsin88(lightersSpeedX[0] * Speed, 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
        lightersPosY[i] += beatsin88(lightersSpeedY[0] * Speed, 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
        break;
      case 1:
        if (broad) {
          lightersPosX[i] = beatsin16(lightersSpeedX[i] / map(Speed, 1, 255, 10, 1), 0, (WIDTH - 1) * 10);
          lightersPosY[i] = beatsin16(lightersSpeedY[i] / map(Speed, 1, 255, 10, 1), 0, (HEIGHT - 1) * 10);
        } else {
          lightersPosX[i] += beatsin16(lightersSpeedX[i] / map(Speed, 1, 255, 10, 1), 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
          lightersPosY[i] += beatsin16(lightersSpeedY[i] / map(Speed, 1, 255, 10, 1), 0, mass[i] / 10 * ((HEIGHT + WIDTH) / 8)) - mass[i] / 10 * ((HEIGHT + WIDTH) / 16);
        }
        break;
      case 2:
        lightersPosX[i] += lightersSpeedX[i] / map(Speed, 1, 255, 10, 1);
        lightersPosY[i] += lightersSpeedY[i] / map(Speed, 1, 255, 10, 1);
        break;
      case 3:
        lightersPosX[i] += mass[i] * cos(radians(lightersSpeedY[i])) / map(Speed, 1, 255, 10, 1);
        lightersPosY[i] += mass[i] * sin(radians(lightersSpeedY[i])) / map(Speed, 1, 255, 10, 1);
        lightersSpeedY[i] += lightersSpeedX[i] / map(Speed, 1, 255, 20, 2);
        break;
    }
    if (broad) {
      if (Run == 3) {
        if (lightersPosY[i] < 0) {
          lightersPosY[i] = 1;
          lightersSpeedY[i] = 360 - lightersSpeedY[i];
        }
        if (lightersPosX[i] < 0) {
          lightersPosX[i] = 1;
          lightersSpeedY[i] = 180 - lightersSpeedY[i];
        }
        if (lightersPosY[i] >= (HEIGHT - 1) * 10) {
          lightersPosY[i] = ((HEIGHT - 1) * 10) - 1;
          lightersSpeedY[i] = 360 - lightersSpeedY[i];
        }
        if (lightersPosX[i] >= (WIDTH - 1) * 10) {
          lightersPosX[i] = ((WIDTH - 1) * 10) - 1;
          lightersSpeedY[i] = 180 - lightersSpeedY[i];
        }
      } else if (Run == 1) {} else {
        if ((lightersPosX[i] <= 0) || (lightersPosX[i] >= (WIDTH - 1) * 10)) lightersSpeedX[i] = -lightersSpeedX[i];
        if ((lightersPosY[i] <= 0) || (lightersPosY[i] >= (HEIGHT - 1) * 10)) lightersSpeedY[i] = -lightersSpeedY[i];
      }
    } else {
      if (lightersPosX[i] < 0) lightersPosX[i] = (WIDTH - 1) * 10;
      if (lightersPosX[i] > (WIDTH - 1) * 10) lightersPosX[i] = 0;
      if (lightersPosY[i] < 0) lightersPosY[i] = (HEIGHT - 1) * 10;
      if (lightersPosY[i] > (HEIGHT - 1) * 10) lightersPosY[i] = 0;
    }
    CRGB color = 0;
    switch (col) {
      case 0:
        color = CHSV(lcolor[i], 40, (trace == 3) ? 128 + random8(2) * 111 : beatsin8(lightersSpeedZ[i] / map(Speed, 1, 255, 10, 1), 128, 255));
        break;
      case 1:
        color = CHSV(lcolor[i], 255, (trace == 3) ? 128 + random8(2) * 111 : beatsin8(lightersSpeedZ[i] / map(Speed, 1, 255, 10, 1), 128, 255));
        break;
      case 2:
        color = ColorFromPalette(PartyColors_p, lcolor[i], (trace == 3) ? 128 + random8(2) * 111 : beatsin8(lightersSpeedZ[i] / map(Speed, 1, 255, 10, 1), 128, 255));
        break;
    }
    if (subPixel)
      drawPixelXYF((float) lightersPosX[i] / 10, (float) lightersPosY[i] / 10, color);
    else
      leds[XY(lightersPosX[i] / 10, lightersPosY[i] / 10)] = color;
  }
  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) {
          lightersSpeedX[i] = random(-10, 10);
          lightersSpeedY[i] = random(-10, 10);
        } else if (Run == 3) {
          lightersSpeedX[i] = random(-10, 10);
          lightersSpeedY[i] = random(0, 360);
          mass[i] = random(5, 10);
        } else {
          lightersSpeedX[i] = random(3, 25);
          lightersSpeedY[i] = random(3, 25);
        }
        lightersSpeedZ[i] = random(3, 25);
        mass[i] + random(-25, 25);
      }
    }
  }
  delay(16);
}

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