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

#define AVR false
#define do_half true

#if AVR==true
#include <EEPROM.h>
#endif

#define IDENTIFIER_DEFAULT "ADYSTRIP"
#define NUM_LEDS_PER_STRIP_DEFAULT 48
#define NUM_STRIPS_DEFAULT 3
#define NUM_GROUPS_PER_STRIP_DEFAULT 6
#define SPACER_WIDTH_DEFAULT 1
#define MAX_TERMINALS 48
#define BLINK_INTERVAL 250

#define MAX_INPUT_LEN 15
#define MAX_OUTPUT_LEN 60


char identifier[10] = IDENTIFIER_DEFAULT;
int numLedsPerStrip = NUM_LEDS_PER_STRIP_DEFAULT;
int numStrips = NUM_STRIPS_DEFAULT;
int numGroupsPerStrip = NUM_GROUPS_PER_STRIP_DEFAULT;
int spacerWidth = SPACER_WIDTH_DEFAULT;
// CRGB::OrangeRed
CRGB colors[5] = {CRGB::Black, CRGB::Green, CRGB::DarkOrange, CRGB::Red, CRGB::Blue}; // Default colors for states 0, 1, 2, 3 and 4 (5-8 is same but blinking)

bool BlinkState[MAX_TERMINALS] = {0};
CRGB BlinkColor[MAX_TERMINALS] = {0};
bool blink=false;
uint32_t lastToggleTimes;

//CRGB leds[numStrips][numLedsPerStrip];
//CRGB leds[NUM_STRIPS_DEFAULT][NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds1[NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds2[NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds3[NUM_LEDS_PER_STRIP_DEFAULT];
#if do_half==false
CRGB leds4[NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds5[NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds6[NUM_LEDS_PER_STRIP_DEFAULT];
#endif

const char HelpText[500] PROGMEM = 
"  Syntax:\n"
"    H                    This help\n"
"    T<TermID>:<State>    Set Terminal state. TermID: 1-48 and State: 0-9\n"
"    X                    Set all states to off (same as sending'A:0'\n"
"    A:<State>            Set state for all Terminals, state (0-9)\n"
"    D                    Show current configuration\n"
"    C<ltwi>:value        Set config for leds per shelve, terminals per shelve, spacer-width, identifier.\n"
"    S                    Store current configuration in EEPROM\n"
"\n"
;


const int MSG_MAX_NUMBER = 18;
const int MSG_MAX_SIZE = 48;
const char messages [MSG_MAX_NUMBER] [MSG_MAX_SIZE] PROGMEM = { 
 { "-=[ USB LedStrip ]=-\n" }, 
 { "Invalid state, use 0-9\n" }, 
 { "Invalid Terminal-ID, use 1-48\n" }, 
 { "Syntax error: Use T<TERMINAL-ID>:<STATE>\n" }, 
 { "Syntax error: Use A:<STATE>\n" }, 
 { "Display configuration:\n" }, 
 { "Save configuration: " }, 
 { "Configuration saved in EEPROM.\n" }, 
 { "Reset all Terminal states.\n" }, 
 { "Startup loop shown.\n" }, 
 { "No such command.\n" }, 
 { "INVALD SYNTAX: Use <A-Z>, H for help.\n" }, 
 { "Identifier        : " }, 
 { "LEDs per shelve   : " }, 
 { "Groups per shelve : " }, 
 { "Shelves           : " }, 
 { "Spacer width      : " },
 { "No EEPROM on non AVR controller"} 
 };

void setup() {
  Serial.begin(57600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  //Serial.println("-=[ USB LedStrip ]=-");
  printMSG(0);
 
  Serial.setTimeout(300);
  // loadConfiguration();
  updateConfiguration();

  //startupAnimation();
  StartupLoop();

  // Show help & config:
  ShowHelp();
  displayConfiguration() ;

  // Ready to go.:
  Serial.println("> ");
}
 
void loop() {
  if (Serial.available()) {
    checkInput();
    Serial.println("> ");
  }
  // delay(10);
  SetLEDs();
}

// This does not work :-(
// char* ptrMSG (int i) {
//   if (i <= MSG_MAX_NUMBER) { 
//     // sprintf(output,"Message: %d, ", i);
//     // Serial.print(output);
//     char *ptr=((const char *) &messages[i]);
//     // sprintf(output,"Address: 0x%04X", ptr);
//     // Serial.println(output);
//     if (!ptr) {
//       return 0;
//     }
//     return ptr;
//   } else {
//     return 0;
//   }
// }


void printMSG (int i) {
  if (i <= MSG_MAX_NUMBER) { 
#if AVR==true
    char *ptr=((const char *) &messages[i]);
#else
    char *ptr=((char *) &messages[i]);
#endif
    char chr;
    if (!ptr) 
      return;
    while ((chr = pgm_read_byte(ptr++))) {
      Serial.print(chr);
    }
  }
}

void checkInput() { 

  char input[MAX_INPUT_LEN];
  char output[MAX_OUTPUT_LEN];
  memset(input, '\0', sizeof(input));
  memset(output, '\0', sizeof(output));

  int test=0;
  int State;
  int TermID;

  Serial.readBytesUntil('\n', input, MAX_INPUT_LEN);
  //Serial.flush();
  // sprintf(output,"Input: [%s].",input);      
  // Serial.println(output); 

  char Command = input[0];
  char *Data = input +1; 

  if (Command >= 'A' && Command <= 'Z') {
    // sprintf(output,"Command [%c] with Data [%s]",Command, Data); 
    // Serial.println(output);

    switch(Command) {
      // case 'C':
      //   Serial.print("Config - ");
      //   char Parameter;
      //   String Value;
      //   test = sscanf(Data, "%c:%c", &Parameter, &Value);
      //   if (test == 2) {
      //     switch (Parameter) {
      //       case 'l':
      //         Serial.print("Set lets per shelve:");
      //         Serial.print(Value);
      //         numLedsPerStrip = Value.toInt();
      //         Serial.println(numLedsPerStrip);
      //         break;
      //       case 'g':
      //         Serial.print("Set terminals per shelve:");
      //         numGroupsPerStrip = Value.toInt();
      //         Serial.println(numGroupsPerStrip);
      //         break;
      //       case 's':
      //         Serial.print("Set spacer width:");
      //         spacerWidth = Value.toInt();
      //         Serial.println(spacerWidth);
      //         break;
      //       case 'i':
      //         Serial.print("Set identifier code:");
      //         Serial.println(Value);
      //         Value.toCharArray(identifier, 8);;
      //         break;
      //       default:
      //         Serial.println("Syntax error.");
      //         break;
      //     }
      //     updateConfiguration();
      //   } else {
      //     sprintf(output,"Syntax parsing failed %d of 3 parameters detected.",test);
      //   }
      //   break;


      case 'T':
        test = sscanf(Data, "%2d:%d", &TermID, &State);
        if ( test == 2) {
          if (TermID >= 1 && TermID <= MAX_TERMINALS) {
            if (State >= 0 && State <= 9) {
              setGroupState(TermID -1,State);
              sprintf(output,"Terminal %d state set to %d", TermID, State);
              Serial.println(output);
            } else {
              //sprintf(output,"Invalid state, use 0-9.");
              printMSG(1);
              //sprintf(output,ptrMSG(1));
            }
          } else { 
            //sprintf(output,"Invalid Terminal-ID, use 1-%d.", MAX_TERMINALS);
            printMSG(2);
            //sprintf(output,ptrMSG(2));
          }
        } else {
          //sprintf(output,"Syntax error:  Use T<TERMINAL-ID>:<STATE>.",test);
          printMSG(3);
          //sprintf(output,ptrMSG(3));
        }
        break;
               
      case 'A':
        test = sscanf(Data, ":%d", &State);
        if (test == 1) {
          if (State >= 0 && State <= 9) {
            sprintf(output,"All terminals set to state %d.",State);
            for(int i=0; i < MAX_TERMINALS; i++){
              setGroupState(i,State);
            }
          } else {
            //sprintf(output,"Invalid state, use 0-9.");
            printMSG(1);
            //sprintf(output,ptrMSG(1));
          }
        } else 
          //sprintf(output,"Syntax error. Use A:<STATE>");
          printMSG(4);
          //sprintf(output,ptrMSG(4));
        break;
      
      case 'D':
        //Serial.println("Display configuration:");
        printMSG(5);
        displayConfiguration();
        break;

      case 'S':
        //Serial.println("Save configurartion:");
        printMSG(6);
        saveConfiguration();
        break;

      case 'X':
        //Serial.println("Reset all Terminal states");
        printMSG(8);
        for(int i=0; i < MAX_TERMINALS; i++){
          setGroupState(i,0);
        }
        SetAllLEDs(CRGB::Black);
        break;

      case 'W':
        StartupLoop();
        //sprintf(output,"Startup loop shown.");
        printMSG(9);
        //sprintf(output,ptrMSG(9));
        break;

      case 'H':
        ShowHelp();
        break;

      default:
        //printf(output,"No such command.");
        printMSG(10);
        //sprintf(output,ptrMSG(10));

        break;
    }
  } else
    printMSG(11);
  
  // if (strlen(output) > 0 ) {
  //   Serial.println(output);
  // }
}


void setGroupState(int group, int state) {
  switch (state) {
    case 0:
      BlinkState[group]=false;
      BlinkColor[group]=CRGB::Black;
      break;
    case 1:
      BlinkState[group]=false;
      BlinkColor[group]=colors[1];
      break;
    case 2:
      BlinkState[group]=false;
      BlinkColor[group]=colors[2];
      break;
    case 3:
      BlinkState[group]=false;
      BlinkColor[group]=colors[3];
      break;
    case 4:
      BlinkState[group]=false;
      BlinkColor[group]=colors[4];
      break;
    case 5:
      BlinkState[group]=true;
      BlinkColor[group]=colors[1];
      break;
    case 6:
      BlinkState[group]=true;
      BlinkColor[group]=colors[2];
      break;
    case 7:
      BlinkState[group]=true;
      BlinkColor[group]=colors[3];
      break;
    case 8:
      BlinkState[group]=true;
      BlinkColor[group]=colors[4];
      break;
    case 9:
      BlinkColor[group]= CRGB::White;
      break;
    default:
      break;     
  }
}


void SetLEDGroupColor(int group, CRGB color) {
  int stripIndex = (group / numGroupsPerStrip)+1;
  int groupIndex = group % numGroupsPerStrip;
  int startLEDIndex = groupIndex * (numLedsPerStrip / numGroupsPerStrip);
  int widthLED = (numLedsPerStrip / numGroupsPerStrip) - spacerWidth;
  int endLEDIndex = startLEDIndex + widthLED; 

  //  fill_solid(&(leds[stripIndex][startLEDIndex]), numLedsPerStrip / numGroupsPerStrip, color);
  if (stripIndex == 1 ) {
    fill_solid(&(leds1[startLEDIndex]), widthLED, color);
  } 
  if (stripIndex == 2 ) {
    fill_solid(&(leds2[startLEDIndex]), widthLED, color);
  }
  if (stripIndex == 3 ) {
    fill_solid(&(leds3[startLEDIndex]), widthLED, color);
  }
#if do_half==false
  if (stripIndex == 4 ) {
    fill_solid(&(leds4[startLEDIndex]), widthLED, color);
  } 
  if (stripIndex == 5 ) {
    fill_solid(&(leds5[startLEDIndex]), widthLED, color);
  } 
  if (stripIndex == 6 ) {
    fill_solid(&(leds6[startLEDIndex]), widthLED, color);
  } 
#endif
  FastLED.show();
}

// void updateConfiguration() {
//   for (int i = 0; i < numStrips; i++) {
//     FastLED.addLeds<WS2812, LED_PIN_1 + i, GRB>(leds[i], numLedsPerStrip);
//   }
// }



void updateConfiguration() {
  FastLED.addLeds<WS2812B, 2, GRB>(leds1, numLedsPerStrip);
  FastLED.addLeds<WS2812B, 3, GRB>(leds2, numLedsPerStrip);
  FastLED.addLeds<WS2812B, 4, GRB>(leds3, numLedsPerStrip);
#if do_half==false
  FastLED.addLeds<WS2812B, 5, GRB>(leds4, numLedsPerStrip);
  FastLED.addLeds<WS2812B, 6, GRB>(leds5, numLedsPerStrip);
  FastLED.addLeds<WS2812B, 7, GRB>(leds6, numLedsPerStrip);
#endif
}


void displayConfiguration() {
  char output[MAX_OUTPUT_LEN];
  memset(output, '\0', sizeof(output));

  //sprintf(output,"Identifier        : %s",identifier);      
  //sprintf(output,ptrMSG(12),identifier);      
  printMSG(12);
  sprintf(output,"%s",identifier);      
  Serial.println(output); 

  //sprintf(output,"LEDs per shelve   : %d",numLedsPerStrip);      
  //sprintf(output,ptrMSG(13),numLedsPerStrip);      
  printMSG(13);
  sprintf(output,"%d",numLedsPerStrip);      
  Serial.println(output); 

  //sprintf(output,"Terminals per shelve : %d",numGroupsPerStrip);      
  //sprintf(output,ptrMSG(14),numGroupsPerStrip);      
  printMSG(14);
  sprintf(output,"%d",numGroupsPerStrip);      
  Serial.println(output); 

  //sprintf(output,"Shelves           : %d",numStrips);      
  //sprintf(output,ptrMSG(15),numStrips);      
  printMSG(15);
  sprintf(output,"%d",numStrips);      
  Serial.println(output); 

  //sprintf(output,"Spacer width      : %d",spacerWidth);      
  //sprintf(output,ptrMSG(16),spacerWidth);      
  printMSG(16);
  sprintf(output,"%d",spacerWidth);      
  Serial.println(output); 
}


void saveConfiguration() {
#if AVR==true
  EEPROM.put(0, numLedsPerStrip);
  EEPROM.put(sizeof(int), numGroupsPerStrip);
  EEPROM.put(2 * sizeof(int), spacerWidth);
  EEPROM.put(3 * sizeof(int), colors[1]);
  EEPROM.put(3 * sizeof(int) + sizeof(uint32_t), colors[2]);
  EEPROM.put(3 * sizeof(int) + 2 * sizeof(uint32_t), colors[3]);
  // sprintf(output,"Configuration saved in EEPROM.");
  printMSG(7);
  //sprintf(output,ptrMSG(7));
#else
  printMSG(17);
#endif
}


void loadConfiguration() {
#if AVR==true
  EEPROM.get(0, numLedsPerStrip);
  EEPROM.get(sizeof(int), numGroupsPerStrip);
  EEPROM.get(2 * sizeof(int), spacerWidth);
  EEPROM.get(3 * sizeof(int), colors[1]);
  EEPROM.get(3 * sizeof(int) + sizeof(uint32_t), colors[2]);
  EEPROM.get(3 * sizeof(int) + 2 * sizeof(uint32_t), colors[3]);
#else
  printMSG(18);
#endif
}


void FadeAll(int StartLed=1, int EndLed=numLedsPerStrip) {
  for(int i = StartLed; i < EndLed; i++) { 
    leds1[i].nscale8(200); 
    leds2[i].nscale8(200); 
    leds3[i].nscale8(200);
#if do_half==false
    leds4[i].nscale8(200); 
    leds5[i].nscale8(200); 
    leds6[i].nscale8(200); 
#endif
  }
}


void StartupLoop() {
  // Boot animmation only on 1st strip.

  // Blue ><><
  #define DELAY (numLedsPerStrip/8)
  for(int i = 0; i < numLedsPerStrip/2; i+=1) {
    leds1[i]                     = 0x0080FF;
    leds1[numLedsPerStrip -i -1] = 0x0080FF;
    leds2[i]                     = 0x0080FF;
    leds2[numLedsPerStrip -i -1] = 0x0080FF;
    leds3[i]                     = 0x0080FF;
    leds3[numLedsPerStrip -i -1] = 0x0080FF;
#if do_half==false
    leds4[i]                     = 0x0080FF;
    leds4[numLedsPerStrip -i -1] = 0x0080FF;
    leds5[i]                     = 0x0080FF;
    leds5[numLedsPerStrip -i -1] = 0x0080FF;
    leds6[i]                     = 0x0080FF;
    leds6[numLedsPerStrip -i -1] = 0x0080FF;
#endif
    FastLED.show();
    FadeAll(0,numLedsPerStrip);
    delay(DELAY);
  }
 
  // White Flash
  SetAllLEDs(CRGB::White);
  FastLED.show();
  delay(75);
  for(int i = 0; i < 16; i++) {
    FadeAll(0,numLedsPerStrip);
    FastLED.show();
    delay(65);
  }
 
  // All Off
  SetAllLEDs(CRGB::Black);
}


void SetAllLEDs(CRGB color){
  fill_solid(leds1, numLedsPerStrip, color);
  fill_solid(leds2, numLedsPerStrip, color);
  fill_solid(leds3, numLedsPerStrip, color);
  // fill_solid(leds4, numLedsPerStrip, color);
  // fill_solid(leds5, numLedsPerStrip, color);
  // fill_solid(leds6, numLedsPerStrip, color);
  FastLED.show();
}


void SetLEDs() {
  // Check if it's time to toggle each group's state
  if (millis() - lastToggleTimes >= BLINK_INTERVAL) { // Toggle every BLINK_INTERVAL ms
    blink = blink == 0 ? 1 : 0;
    lastToggleTimes = millis(); // Update last toggle time
    for(int i=0; i < MAX_TERMINALS; i++){
      if (BlinkState[i] == true) {
        if (blink == true){ 
          SetLEDGroupColor(i, BlinkColor[i]);
        } else {
          SetLEDGroupColor(i, CRGB::Black);
        }
      } else {
        SetLEDGroupColor(i, BlinkColor[i]);
      }
    }
  }
}


void ShowHelp(){
#if AVR==true
   char *ptr=((const char *) &HelpText);
#else
   char *ptr=((char *) &HelpText);
#endif
    char chr;
    if (!ptr) 
      return;
    while ((chr = pgm_read_byte(ptr++))) {
      Serial.print(chr);
    }
  // Serial.println("Syntax:");
  // Serial.println("  H                    This help");
  // Serial.println("  T<TermID>:<State>    Where TermID: 1-48 and State: 0-9");
  // Serial.println("  X                    Set all states to off (same as sending'A:0'");
  // Serial.println("  A:<State>            Set state for all Terminals");
  // Serial.println("  D                    Show current configuration");
  // Serial.println("  C<lgs>:value         Set config for leds per strip, groups per strip or spacer-width.");
  // Serial.println("  S                    Store current configuration in EEPROM");
}