/* Support of multiple LED layouts.

  By: Sutaburosu

  Testing by: Andrew Tuline

  Original code:

  https://gist.github.com/sutaburosu/43d46240dc2b05683d1e98872885f8db#file-stream_serial-ino-L34-L55
  https://github.com/sutaburosu/LEDpijp/blob/main/XYmatrix.h#L37-L58 (has a fix)


  Other references:

  https://github.com/marcmerlin/ArduinoOnPc-FastLED-GFX-LEDMatrix
  https://github.com/Jorgen-VikingGod/LEDMatrix
  https://macetech.github.io/FastLED-XY-Map-Generator/
  https://github.com/macetech/FastLED-XY-Map-Generator
  https://github.com/marcmerlin/Framebuffer_GFX#custom-xy-mapping-for-irregular-arrays



// Helper functions for a two-dimensional XY matrix of pixels.
//
//     XY(x,y) takes x and y coordinates and returns an LED index number,
//             for use like this:  leds[ XY(x,y) ] == CRGB::Red;
//
// If pixels are driven horizontally at the same end, like below, it's a (ROWMAJOR) layout:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//     .----<----<----<----'
//     |
//     5 >  6 >  7 >  8 >  9
//                         |
//     .----<----<----<----'
//     |
//    10 > 11 > 12 > 13 > 14
//                         |
//     .----<----<----<----'
//     |
//    15 > 16 > 17 > 18 > 19
//
//
// If pixels are driven horizontally and laid out back and forth, it's a (ROWMAJOR | SERPENTINE) layout:
//
//     0 >  1 >  2 >  3 >  4
//                         |
//                         |
//     9 <  8 <  7 <  6 <  5
//     |
//     |
//    10 > 11 > 12 > 13 > 14
//                        |
//                        |
//    19 < 18 < 17 < 16 < 15
//

  We have 5 orientation bits. The values are:

  XY_LAYOUT = SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1

  ROWMAJOR   - The x (or Major) value goes horizontal (otherwise vertical).
  SERPENTINE - A serpentine layout (otherwise non-serpentine layout).
  FLIPMAJOR  - Flip the major axis, ie top to bottom (otherwise not).
  FLIPMINOR  - Flip the minor axis, ie left to right (otherwise not).
  TRANSPOSE  - Swap the major and the minor axes (otherwise no swap). Don't use on non-square.

*/


// Here's a list of definitons to test. --------------------------------------------------------------------------------

//#define XY_LAYOUT (ROWMAJOR | SERPENTINE)                // Serpentine with rows at top left. Wokwi standard. Good!
// #define XY_LAYOUT (ROWMAJOR)                             // Non-serpentine with rows at top left. Good!

// #define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMAJOR)    // Serpentine, leds[0] is top right. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMAJOR)                 // Non-serpentine, leds[0] is top right. Good!

#define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMINOR)    // Serpentine, leds[0] is bottom right. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMINOR)                 // Non-serpentine, leds[0] is bottom right. Good!

// #define XY_LAYOUT (ROWMAJOR | SERPENTINE | FLIPMAJOR | FLIPMINOR)    // Serpentine, leds[0] is bottom left. Good!
// #define XY_LAYOUT (ROWMAJOR | FLIPMAJOR | FLIPMINOR)                 // Non-serpentine, leds[0] is bottom left. Good!

// #define XY_LAYOUT 0                                      // NOT ROWMAJOR and NOT SERPENTINE. Good!
// #define XY_LAYOUT SERPENTINE                             // Not ROWMAJOR. Good!
// Rest are untested.

// Have NOT tested when TRANSPOSE is set.                   // We'll get there.


#include <FastLED.h>

#define LED_PIN  12

const uint8_t matrixWidth = 16;
const uint8_t matrixHeight = 16;
#define NUM_LEDS matrixWidth * matrixHeight

CRGB leds[NUM_LEDS+1];                       // The +1 is for the out of bounds LED for any out of bound coordinates.

//const bool    matrixSerpentine = true;


enum XY_Layout {
  SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
};


void setup() {
  Serial.begin(115200);
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(64);
  dolights();
}


void loop() {
  alternates();
}

void printLayout(void) {
    for (int y=0;y<matrixHeight;y++) {
      for (int x=0;x<matrixWidth;x++) {
        char result[10];
        sprintf(result, "%03d", XYP(x,y));
        Serial.print(result);

        if (x+1<matrixWidth) {
          if (XYP(x+1,y) == XYP(x,y) + 1)
            Serial.print(" → ");
          else if (XYP(x+1,y) == XYP(x,y) - 1)
            Serial.print(" ← ");
          else
            Serial.print("   ");
        }
      }
      Serial.println();

      for (int x=0;x<matrixWidth;x++) {
        if (y+1<matrixHeight) {
          if (XYP(x,y+1) == XYP(x,y) + 1)
            Serial.print("  ↓   ");
          else if (XYP(x,y+1) == XYP(x,y) - 1)
            Serial.print("  ↑   ");
          else
            Serial.print("      ");
        }
      }
      Serial.println();

    }
    Serial.println();
}


void dolights() {

// Raw LED writing to confirm physical layout.
 /*   leds[0] = CRGB::Red;
    leds[1] = CRGB::Green;
    leds[2] = CRGB::Blue;
    leds[31] = CRGB::Orange;
*/ 



  printLayout();
}


void alternates() {

  leds[XY(0, 0)] = CRGB::Violet;
  leds[XY(1, 0)] = CRGB::Green;
  leds[XY(2, 0)] = CRGB::Blue;
  leds[XY(0, 1)] = CRGB::Orange;
  FastLED.show();

  delay(500);

  leds[XYP(0, 0)] = CRGB::Red;
  leds[XYP(1, 0)] = CRGB::Green;
  leds[XYP(2, 0)] = CRGB::Blue;
  leds[XYP(0, 1)] = CRGB::Orange;
  FastLED.show();

  delay(500);
}


// enum XY_Layout {
//   SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
// };

// Advanced XY layout supports multiple orientations.

uint16_t XYP(uint8_t x, uint8_t y) {                 // By: Sutaburosu
  bool matrixSerpentine = XY_LAYOUT & SERPENTINE;
  bool matrixRowmajor = XY_LAYOUT & ROWMAJOR;
  bool matrixFlipmajor = XY_LAYOUT & FLIPMAJOR;
  bool matrixFlipminor = XY_LAYOUT & FLIPMINOR;
  bool matrixTranspose = XY_LAYOUT & TRANSPOSE;
  int matrixHorizontal = 1;
  int matrixVertical = 1;
  bool matrixPanels = true;

  //temporary code, until major and minor variables have been replaced by matrixFirstLedPosition and matrixLedOrientation
  //Proposed new values (instead of rowmajor/flipmajor/flipminor)
  uint8_t matrixFirstLedTopBottom; //Prompt: First Led Top or Bottom Drop Down: Top/Bottom Values 0/1
  uint8_t matrixFirstLedLeftRight; //Prompt: First Led Left or Right Drop Down: Left/Right Values 0/1
  bool matrixLedOrientationHorVert = !matrixRowmajor; //Prompt: Led Orientation Drop down: Horizontal/Vertical Values 0/1

  //temp conversion of old to new values
  if (matrixLedOrientationHorVert == 0) { //horizontal
    matrixFirstLedTopBottom = matrixFlipminor;
    matrixFirstLedLeftRight = matrixFlipmajor;
  }
  else { //vertical
    matrixFirstLedTopBottom = matrixFlipmajor;
    matrixFirstLedLeftRight = matrixFlipminor;
  }
  //end of temporary code

  if (x >= matrixWidth || y >= matrixHeight)
    return NUM_LEDS;                                  // Off the charts, so it's only useable by routines that use leds[x]!!!!
  uint8_t major, minor, sz_major, sz_minor;

  //Width, Height and Size of panel. Same as matrixWidth and Height if only one panel 
  uint16_t panelWidth = matrixPanels?matrixWidth / matrixHorizontal:matrixWidth;
  uint16_t panelHeight = matrixPanels?matrixHeight / matrixVertical:matrixHeight;
  uint16_t panelSize = panelWidth * panelHeight;

  //Horizontal and vertical panel number. 0 if only one panel
  uint8_t panelHorizontalNr = x / panelWidth;
  uint8_t panelVerticalNr = y / panelHeight;

  uint16_t panelFirstLed = 0; //0 if only one panel
  if (matrixLedOrientationHorVert == 0) { //horizontal
    if (matrixPanels) panelFirstLed = panelSize * (panelHorizontalNr + matrixHorizontal * panelVerticalNr);
    major = x%panelWidth, minor = y%panelHeight, sz_major = panelWidth,  sz_minor = panelHeight;
  }
  else { //vertical
    if (matrixPanels) panelFirstLed = panelSize * (panelVerticalNr + matrixVertical * panelHorizontalNr);
    major = y%panelHeight, minor = x%panelWidth, sz_major = panelHeight, sz_minor = panelWidth;
  }

  bool flipmajor = (matrixLedOrientationHorVert == 0)?matrixFirstLedLeftRight == 1:matrixFirstLedTopBottom == 1;//right:bottom
  bool flipminor = (matrixLedOrientationHorVert == 0)?matrixFirstLedTopBottom == 1:matrixFirstLedLeftRight == 1;//bottom:right
  //flip minor if needed, this needs to be done before flipmajor because minor value needed to identify serpentine row
  if (flipminor) minor = sz_minor - 1 - minor;
  // A line of magic. &=Binary AND, minor&1 is odd rows, ^=Binary XOR => flapmajor or serpentine (odd) row, but not both (XOR)
  if (flipmajor ^ ((minor & 1) && matrixSerpentine)) major = sz_major - 1 - major;

  if (matrixTranspose)
    return major * (uint16_t) sz_minor + minor + panelFirstLed;
  else
    return minor * (uint16_t) sz_major + major + panelFirstLed;
}



// This is the non-panel version
uint16_t XY(uint8_t x, uint8_t y) {
  
  uint8_t major, minor, sz_major, sz_minor;

  if (x >= matrixWidth || y >= matrixHeight)
    return NUM_LEDS;                                                             // Failsafe maximum value is one beyond the matrix.

  if (XY_LAYOUT & ROWMAJOR) {
    major = x, minor = y, sz_major = matrixWidth,  sz_minor = matrixHeight;      // The STANDARD layout, leds[0] is top left.
  } else {
    major = y, minor = x, sz_major = matrixHeight, sz_minor = matrixWidth;       // Not sure how to set this. Maybe just use a '0'.
  }

  if (((XY_LAYOUT & FLIPMAJOR) != 0) ^ (((minor & 1) != 0) && ((XY_LAYOUT & SERPENTINE) != 0))) {    // A line of magic.
    major = sz_major - 1 - major;
  }

  if (XY_LAYOUT & FLIPMINOR) {
    minor = sz_minor - 1 - minor;
  }

  if (XY_LAYOUT & TRANSPOSE) {
    return major * (uint16_t) sz_major + minor;
  } else {
    return minor * (uint16_t) sz_major + major;
  }
}



////////////////////////////////////////////
// OLD AND REFERENCE CODE BELOW HERE.     //
////////////////////////////////////////////

/*
// Basic XY layout doesn't support multiple orientations.

#define XY_LAYOUT (ROWMAJOR | SERPENTINE)  

uint16_t OLD_XY( uint8_t x, uint8_t y)
{
  uint16_t i;

  if( matrixSerpentine == false) {
    i = (y * matrixWidth) + x;
  }

  if( matrixSerpentine == true) {
    if( y & 0x01) {
      // Odd rows run backwards
      uint8_t reverseX = (matrixWidth - 1) - x;
      i = (y * matrixWidth) + reverseX;
    } else {
      // Even rows run forwards
      i = (y * matrixWidth) + x;
    }
  }

  return i;
}
*/



/*

// Advanced XY with debug code.

// enum XY_Layout {
//   SERPENTINE = 16, ROWMAJOR = 8, TRANSPOSE = 4, FLIPMAJOR = 2, FLIPMINOR = 1
// };

uint16_t XY(uint8_t x, uint8_t y) {
  
  uint8_t major, minor, sz_major, sz_minor;

  if (x >= matrixWidth || y >= matrixHeight)
    return NUM_LEDS;                                                             // Failsafe maximum value is one beyond the matrix.

  if (XY_LAYOUT & ROWMAJOR) {
    Serial.println("Rowmajor");
    major = x, minor = y, sz_major = matrixWidth,  sz_minor = matrixHeight;      // The STANDARD layout, leds[0] is top left.
  } else {
    Serial.println("Not Rowmajor");
    major = y, minor = x, sz_major = matrixHeight, sz_minor = matrixWidth;       // Not sure how to set this. Maybe just use a '0'.
  }

  if (((XY_LAYOUT & FLIPMAJOR) != 0) ^ (((minor & 1) != 0) && ((XY_LAYOUT & SERPENTINE) != 0))) {    // A line of magic.
    Serial.println("Flipmajor and Serpentine");
    major = sz_major - 1 - major;
  }

  if (XY_LAYOUT & FLIPMINOR) {
    Serial.println("Flipminor");
    minor = sz_minor - 1 - minor;
  }

  if (XY_LAYOUT & TRANSPOSE) {
    Serial.println("Transposed");
    Serial.println(major *(uint16_t) sz_major + minor);
    return major * (uint16_t) sz_major + minor;
  } else {
    Serial.println("Not Transposed");
    Serial.println(minor * (uint16_t) sz_major + major);
    return minor * (uint16_t) sz_major + major;
  }
}
*/