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

#define IDENTIFIER_DEFAULT "ADYSTRIP"
#define NUM_LEDS_PER_STRIP_DEFAULT 60
#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[8] = 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];
// CRGB leds4[NUM_LEDS_PER_STRIP_DEFAULT];
// CRGB leds5[NUM_LEDS_PER_STRIP_DEFAULT];
// CRGB leds6[NUM_LEDS_PER_STRIP_DEFAULT];

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

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

  //startupAnimation();
  StartupLoop();

  // Ready to go. show help:
  // ShowHelp();
  displayConfiguration() ;

  Serial.println(">");
}
 
void loop() {
  if (Serial.available()) {
    checkInput();
    Serial.println("> ");
  }
  // delay(10);
  SetLEDs();
}
 
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);
            } else {
              sprintf(output,"Invalid state, use 0-9.");
            }
          } else { 
            sprintf(output,"Invalid Terminal-ID, use 1-%d.", MAX_TERMINALS);
          }
        } else {
          sprintf(output,"Syntax error:  Use T<TERMINAL-ID>:<STATE>.",test);
        }
        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.");
          }
        } else 
          sprintf(output,"Syntax error. Use A:<STATE>");
        break;
      
      case 'D':
        Serial.println("Display configuration:");
        displayConfiguration();
        break;

      case 'S':
        Serial.println("Save configurartion:");
        saveConfiguration();
        sprintf(output,"Configuration saved in EEPROM.");
        break;

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

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

      case 'H':
        ShowHelp();
        break;

      default:
        sprintf(output,"No such command.");
        break;
    }
  } else {
  if (Command == 0) 
    sprintf(output,"INVALD SYNTAX: Use <A-Z>, H for help.", Command);
  else
    sprintf(output,"INVALD SYNTAX: Command=[%c], Use <A-Z>, H for help.", Command);
  }   
  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 (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);
  // } 
  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);
  // FastLED.addLeds<WS2812B, 5, GRB>(leds4, numLedsPerStrip);
  // FastLED.addLeds<WS2812B, 6, GRB>(leds5, numLedsPerStrip);
  // FastLED.addLeds<WS2812B, 7, GRB>(leds6, numLedsPerStrip);
}



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

  sprintf(output,"Identifier        : %s",identifier);      
  Serial.println(output); 

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

  sprintf(output,"Groups per shelve : %d",numGroupsPerStrip);      
  Serial.println(output); 

  sprintf(output,"Shelves           : %d",numStrips);      
  Serial.println(output); 

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



void saveConfiguration() {
  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]);
}



void loadConfiguration() {
  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]);
}



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); 
    // leds4[i].nscale8(200); 
    // leds5[i].nscale8(200); 
    // leds6[i].nscale8(200); 
  }
}



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;
    // leds4[i]                     = 0x0080FF;
    // leds4[numLedsPerStrip -i -1] = 0x0080FF;
    // leds5[i]                     = 0x0080FF;
    // leds5[numLedsPerStrip -i -1] = 0x0080FF;
    // leds6[i]                     = 0x0080FF;
    // leds6[numLedsPerStrip -i -1] = 0x0080FF;
    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(){
  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");
}