/*

Name    : Adyen IPP Staging Terminal State Indicator
Version : 0.8
Date    : 2024-03-18
Author  : Bas van Ritbergen <[email protected]>   

*/


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

// Do only 3 ledstrips to save RAM
#define do_half true

#ifdef AVR
#include <EEPROM.h>
#endif

#define CONFIG_IDENTIFIER "ADYENLED"
#define IDENTIFIER_DEFAULT "STGSTRIP"
#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

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

// Set Defaults, wil bve overwritten by load from EEPROM
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 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

struct LedData {
  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;
} LedConfig = {};

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
  }

  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 :-( and that sucks
// 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) { 
#ifdef AVR
    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 writeStringToEEPROM(int addrOffset, const String &strToWrite) {
  byte len = strToWrite.length();
  EEPROM.write(addrOffset, len);
  for (int i = 0; i < len; i++) {
    EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
  }
}

String readStringFromEEPROM(int addrOffset) {
  int newStrLen = EEPROM.read(addrOffset);
  char data[newStrLen + 1];
  for (int i = 0; i < newStrLen; i++) {
    data[i] = EEPROM.read(addrOffset + 1 + i);
  }
  data[newStrLen] = '\0';
  return String(data);
}

void loadConfiguration() {
#ifdef AVR
  if ( readStringFromEEPROM(0) == CONFIG_IDENTIFIER ) {
    EEPROM.get(10, LedConfig);
    *identifier = &LedConfig.identifier;
    numLedsPerStrip = LedConfig.numLedsPerStrip;
    numStrips = LedConfig.numStrips;
    numGroupsPerStrip = LedConfig.numGroupsPerStrip;
    spacerWidth = LedConfig.spacerWidth;
  }
#else
  printMSG(18);
#endif
}


void saveConfiguration() {
#ifdef AVR
  *LedConfig.identifier = &identifier;
  LedConfig.numLedsPerStrip = numLedsPerStrip;
  LedConfig.numStrips = numStrips;
  LedConfig.numGroupsPerStrip = numGroupsPerStrip;
  LedConfig.spacerWidth = spacerWidth;
  
  writeStringToEEPROM(0, CONFIG_IDENTIFIER); 
  EEPROM.put(10, LedConfig);
  // sprintf(output,"Configuration saved in EEPROM.");
  printMSG(7);
  //sprintf(output,ptrMSG(7));
#else
  printMSG(17);
#endif
}


void updateConfiguration() {
  *LedConfig.identifier = &identifier;
  LedConfig.numLedsPerStrip = numLedsPerStrip;
  LedConfig.numStrips = numStrips;
  LedConfig.numGroupsPerStrip = numGroupsPerStrip;
  LedConfig.spacerWidth = spacerWidth;

  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 updateConfiguration() {
//   for (int i = 0; i < numStrips; i++) {
//     FastLED.addLeds<WS2812, LED_PIN_1 + i, GRB>(leds[i], numLedsPerStrip);
//   }
// }


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':
        Serial.print("Set Terminal:");
        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 {
              printMSG(1);
            }
          } else { 
            printMSG(2);
          }
        } else {
          printMSG(3);
        }
        break;
               
      case 'A':
        Serial.print("Set All Terminals:");
        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 {
            printMSG(1);
          }
        } else 
          printMSG(4);
        break;
      
      case 'D':
        printMSG(5);
        displayConfiguration();
        break;

      case 'S':
        Serial.print("Save config:");
        printMSG(6);
        saveConfiguration();
        break;

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

      case 'W':
        Serial.print("Show startup loop:");
        StartupLoop();
        printMSG(9);
        break;

      case 'H':
        Serial.print("Show help:");
        ShowHelp();
        break;

      default:
        printMSG(10);
        break;
    }
  } else
    printMSG(11); 
}


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 displayConfiguration() {
  char output[MAX_OUTPUT_LEN];
  memset(output, '\0', sizeof(output));

  printMSG(12);
  sprintf(output,"%s",identifier);      
  Serial.println(output); 

  printMSG(13);
  sprintf(output,"%d",numLedsPerStrip);      
  Serial.println(output); 

  printMSG(14);
  sprintf(output,"%d",numGroupsPerStrip);      
  Serial.println(output); 

  printMSG(15);
  sprintf(output,"%d",numStrips);      
  Serial.println(output); 

  printMSG(16);
  sprintf(output,"%d",spacerWidth);      
  Serial.println(output); 
}


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);
#if do_half==false
  fill_solid(leds4, numLedsPerStrip, color);
  fill_solid(leds5, numLedsPerStrip, color);
  fill_solid(leds6, numLedsPerStrip, color);
#endif
  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(){
#ifdef AVR
   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);
    }
}