/* 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() {
}

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

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

      for (int x=0;x<matrixWidth;x++) {
        if (y+1<matrixHeight) {
          if (XY(x,y+1) == XY(x,y) + 1)
            Serial.print("  ↓   ");
          else if (XY(x,y+1) == XY(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;
*/ 

// Layout based led's.
  leds[XY(0, 0)] = CRGB::Red;
  leds[XY(1, 0)] = CRGB::Green;
  leds[XY(2, 0)] = CRGB::Blue;
  leds[XY(0, 1)] = CRGB::Orange;

  FastLED.show();

  printLayout();
}


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

// Advanced XY layout supports multiple orientations.

uint16_t XY(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 nrOfHorizontalPanels = 1;
  int nrOfVerticalPanels = 1;

  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;

  //temporary variables for dev/testing purposes, now FirstLed and LastLed in Time & Macros settings used
  
  //Width, Height and Size of panel. Same as matrixWidth and Height if only one panel 
  int panelWidth = matrixWidth / nrOfHorizontalPanels;
  int panelHeight = matrixHeight / nrOfVerticalPanels;
  int panelSize = panelWidth * panelHeight;

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

  int panelFirstLed = 0; //0 if only one panel
  if (matrixRowmajor) {
    panelFirstLed = panelSize * (panelHorizontalNr + nrOfHorizontalPanels * panelVerticalNr);
    major = x%panelWidth, minor = y%panelHeight, sz_major = panelWidth,  sz_minor = panelHeight;
  }
  else {
    panelFirstLed = panelSize * (panelVerticalNr + nrOfVerticalPanels * panelHorizontalNr); //0 if only one panel, more panels: multitudes of 256 normally
    major = y%panelHeight, minor = x%panelWidth, sz_major = panelHeight, sz_minor = panelWidth;
  }

  if (((matrixFlipmajor) != 0) ^ (((minor & 1) != 0) && ((matrixSerpentine) != 0)))    // A line of magic.
    major = sz_major - 1 - major;
  if (matrixFlipminor)
    minor = sz_minor - 1 - minor;

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




////////////////////////////////////////////
// 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;
  }
}
*/