// RESOLVED - RAN OUT OF MEMORY FOR MORE THAN ONE SNOWFLAKE.  CHANGE PROCESSOR FOR ESP32
// DISPLAYS ONE SNOWFLAKE MOVING FROM TOP TO BOTTOM in random vertical positions
// RESOLVED - CANNOT PRINT FLAKE OUT OF BOUNDS - INVESTIGATE

// next phase of development - show multiple snowflakes
// create array to store for each snowflake
// - size (Large, Medium, Small)
// - column to decend down
// - rotation - future development
// - colour
// - current position down the screen (row or virtual row (step 2))
// - speed of desent
// - height to start from to introduce some kind of delay and speed variation
// -
// display snowflake by combining current position with snowflake dimensions.
// rotate through snowflakes so it looks like they are all moving at the same time
// large will be less frequent than medium.  tere will be more small than medium

// other ideas
// -----------
// define number of lines of each snowflake/object
// define direction of movement of object in x and axis or both.
// load the snowflake designs into an array so they can be called by their position in the array

// There are 8 bits shifted in this code
// 1 bit is shifted by doubling the dimensions of the object
// 1 bit is shifted when the snowflakes are loaded into the snowflakes[] array.
// 6 bit are shifted when calling wu_pixel.
//
// All the movement in the loop is done by stepping by two (+= 2) as the odd numbered
// objects look very different to the even numbered objects

#include <Arduino.h>
#include <FastLED.h>
#include <colorutils.h>

enum XY_matrix_config {
  SERPENTINE = 1,
  ROWMAJOR = 2,
  FLIPMAJOR = 4,
  FLIPMINOR = 8
};

#define LED_PIN         21
#define MATRIX_WIDTH    40
#define MATRIX_HEIGHT   12
#define NUM_LEDS        (MATRIX_WIDTH * MATRIX_HEIGHT)
#define XY_MATRIX       (SERPENTINE | ROWMAJOR)
// #define XY_MATRIX (SERPENTINE | ROWMAJOR | FLIPMINOR)
#define frame_delay     100
CRGB leds[NUM_LEDS + 1];

#define snowflakeDesignCount 5
#define num_snowLines 8
#define num_snowFlakes 2

typedef struct {
  int x1, y1, x2, y2;
} sfline;

typedef struct {
  sfline sflines[num_snowLines];
  CRGB color;
} snowflake;

snowflake snowFlakes[num_snowFlakes];

// snowflake snowFlake1 = {  // diagonal line only
//   {
//     {-10, -10, 0, 0}
//   },
//   CRGB::White
// };

snowflake snowFlake1 = {  // large 1
  {
    {-10, -10, 0, 0},
    {-3, -9, -9, -3 },
    {10, -10, 0, 0},
    {3, -9, 9, -3},
    {0, 0, 10, 10},
    {9, 3, 3, 9},
    {0, 0, -10, 10},
    {-9, 3, -3, 9}
  },
  CRGB::White
};

// snowflake snowFlake1 = {  // large 2
//   {
//     {-10, -10, 0, 0},
//     {-10, -4, -4, -10},
//     {10, -10, 0, 0},
//     {4, -10, 10, -4},
//     {10, 10, 0, 0},
//     {4, 10, 10, 4},
//     {-10, 10, 0, 0},
//     {-10, 4, -4, 10}
//   },
//   CRGB::White
// };

// snowflake snowFlake1 = {  // large 3
//   {
//     {-10, -10, 0, 0},
//     {-9, -3, -3, -9},
//     {10, -10, 0, 0},
//     {3, -9, 9, -3},
//     {10, 10, 0, 0},
//     {3, 9, 9, 3},
//     {-10, 10, 0, 0},
//     {-9, 3, -3, 9}
//   },
//   CRGB::White
// };

// snowflake snowFlake1 = {  // medium 1
//   {
//     {-5, -5, 0, 0},
//     {-6, -3, -3, -6},
//     {5, -5, 0, 0},
//     {3, -6, 6, -3},
//     {5, 5, 0, 0},
//     {3, 6, 6, 3},
//     {-5, 5, 0, 0},
//     {-6, 3, -3, 6}
//   },
//   CRGB::White
//  };

// snowflake snowFlake1 = {  // Small 1
//   {
//     {-3, -3, 0, 0},
//     {-4, -2, -2, -4},
//     {3, -3, 0, 0},
//     {2, -4, 4, -2},
//     {3, 3, 0, 0},
//     {2, 4, 4, 2},
//     {-3, 3, 0, 0},
//     {-4, 2, -2, 4}
//   },
//   CRGB::White
//  };

// snowflake snowFlake1 = {  // Tiny 1
//   {
//     {-2, -2, 0, 0},
//     {2, -2, 1, -1},
//     {2, 2, 1, 1},
//     {-2, 2, -1, 1}
//   },
//   CRGB::White
//  };

void setup() {
  Serial.begin(9600);
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);


  int xCent = 11;
  int yCent = 11;
  int i = 0;

  //for (int i = 0; i < num_snowLines; i++) {
  // snowFlakes[0].sflines[i].x1 = (snowFlake1.sflines[i].x1 + xCent) << 1;
  // snowFlakes[0].sflines[i].y1 = (snowFlake1.sflines[i].y1 + yCent) << 1;
  // snowFlakes[0].sflines[i].x2 = (snowFlake1.sflines[i].x2 + xCent) << 1;
  // snowFlakes[0].sflines[i].y2 = (snowFlake1.sflines[i].y2 + yCent) << 1;

  // Serial.print("i: ");
  // Serial.println(i);
  // Serial.print("snowFlake1 x1: ");
  // Serial.print(snowFlake1.sflines[i].x1);
  // Serial.print(", y1: ");
  // Serial.print(snowFlake1.sflines[i].y1);
  // Serial.print(", x2: ");
  // Serial.print(snowFlake1.sflines[i].x2);
  // Serial.print(", y2: ");
  // Serial.println(snowFlake1.sflines[i].y2);
  // Serial.print("snowFlakes[0] x1: ");
  // Serial.print(snowFlakes[0].sflines[i].x1);
  // Serial.print(", y1: ");
  // Serial.print(snowFlakes[0].sflines[i].y1);
  // Serial.print(", x2: ");
  // Serial.print(snowFlakes[0].sflines[i].x2);
  // Serial.print(", y2: ");
  // Serial.println(snowFlakes[0].sflines[i].y2);
  //}
  snowFlakes[0].color = snowFlake1.color;
}

void loop() {
  int xCent = random8(0,45)*2;
  for (int yCent = -16; yCent < 40; yCent += 2) {
    for (int i = 0; i < num_snowLines; i++) {
      // NOTE: bitshift changes the size of the object normlly set to << 1 but could try << 2
      snowFlakes[0].sflines[i].x1 = (snowFlake1.sflines[i].x1 + xCent); // << 1;
      snowFlakes[0].sflines[i].y1 = (snowFlake1.sflines[i].y1 + yCent); // << 1;
      snowFlakes[0].sflines[i].x2 = (snowFlake1.sflines[i].x2 + xCent); // << 1;
      snowFlakes[0].sflines[i].y2 = (snowFlake1.sflines[i].y2 + yCent); // << 1;
    }
FastLED.clear();
  for (int i = 0; i < num_snowLines; i++) {
    draw_line(snowFlakes[0].sflines[i], (snowFlakes[0].color));
  }
  
  // draw_line(snowFlakes[0].sflines[0], (snowFlakes[0].color));
  FastLED.show();
  delay(frame_delay);
  //while (1) {}

  }
}

void draw_line(sfline line, CRGB color) {
  int x1 = line.x1;
  int y1 = line.y1;
  int x2 = line.x2;
  int y2 = line.y2;

  // Serial.print("x1: ");
  // Serial.print(x1);
  // Serial.print(", y1: ");
  // Serial.print(y1);
  // Serial.print(", x2: ");
  // Serial.print(x2);
  // Serial.print(", y2: ");
  // Serial.println(y2);

  // Calculate the absolute differences in x and y coordinates.
  int dx = abs(x2 - x1);
  int dy = abs(y2 - y1);

  // Serial.print("dx: ");
  // Serial.print(dx);
  // Serial.print(", dy: ");
  // Serial.println(dy);


  // Determine the direction of the line in x and y.
  int sx = x1 < x2 ? 1 : -1;
  int sy = y1 < y2 ? 1 : -1;

  // Initialize the error term to handle line drawing.
  int err = dx - dy;

  // Initialize the starting coordinates of the line.
  int x = x1;
  int y = y1;

  // Start an infinite loop to continue drawing the line until it's finished.
  while (true) {
    // Serial.print("x << 7: ");
    // Serial.print(x << 7);
    // Serial.print(", y << 7: ");
    // Serial.println(y << 7);
    // Serial.print("color r/g/b: ");
    // Serial.print(color.r);
    // Serial.print("/");
    // Serial.print(color.g);
    // Serial.print("/");
    // Serial.println(color.b);

    wu_pixel(x << 7, y << 7, &color);
    if (x == x2 && y == y2) {
      break;
    }
    int e2 = 2 * err;
    if (e2 > -dy) {
      err -= dy;
      x += sx;
    }
    if (e2 < dx) {
      err += dx;
      y += sy;
    }
  }
}

// Xiaolin Wu's line algorithm for drawing a smoothed line
void wu_pixel(uint32_t x, uint32_t y, CRGB *col) {
  uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
  uint8_t wu[4] = {((ix * iy) + ix + iy) >> 8, ((xx * iy) + xx) >> 8,
                   ((ix * yy) + ix + yy) >> 8, ((xx * yy) + xx) >> 8
                  };
  // Serial.print("x: ");
  // Serial.print(x);
  // Serial.print(", xx: ");
  // Serial.print(xx);
  // Serial.print(", y: ");
  // Serial.print(y);
  // Serial.print(", yy: ");
  // Serial.print(yy);
  // Serial.print(", ix: ");
  // Serial.print(ix);
  // Serial.print(", iy: ");
  // Serial.println(iy);

  for (uint8_t i = 0; i < 4; i++) {
    uint16_t xy = XY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1));
    CRGB *led = &leds[xy];
    led->r = qadd8(led->r, col->r * wu[i] >> 8);
    led->g = qadd8(led->g, col->g * wu[i] >> 8);
    led->b = qadd8(led->b, col->b * wu[i] >> 8);
  }
}

uint16_t XY(uint8_t x, uint8_t y) {
  uint8_t major, minor, sz_major, sz_minor;
  if (x >= MATRIX_WIDTH || y >= MATRIX_HEIGHT)
    return NUM_LEDS;
  if (XY_MATRIX & ROWMAJOR)
    major = x, minor = y, sz_major = MATRIX_WIDTH,  sz_minor = MATRIX_HEIGHT;
  else
    major = y, minor = x, sz_major = MATRIX_HEIGHT, sz_minor = MATRIX_WIDTH;
  if ((XY_MATRIX & FLIPMAJOR) ^ (minor & 1 && (XY_MATRIX & SERPENTINE)))
    major = sz_major - 1 - major;
  if (XY_MATRIX & FLIPMINOR)
    minor = sz_minor - 1 - minor;
  return (uint16_t) minor * sz_major + major;
}