#include "FastLED.h"
#define NUM_LEDS                   8
#define DATA_PIN                   PIN_D6
#define BRIGHTNESS                 255
#define FRAMES_PER_SECOND          120
#define HEARTBEAT_MILLIS           5000
#define HEARTBEAT_CHAR             "A"

bool DEBUG = 0;

CRGB leds[NUM_LEDS];

String inputCmd = "";
boolean cmdComplete = false;

unsigned long previousMillis = 0;
void p(char *fmt, ... )
{
        char tmp[128];
        va_list args;
        va_start (args, fmt );
        vsnprintf(tmp, 128, fmt, args);
        va_end (args);
        Serial.print(tmp);
}

void setup() {
  delay(3000); // 3 second delay for recovery
  FastLED.addLeds<PL9823, DATA_PIN, RGB>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);

  for (int i = 0; i < NUM_LEDS; i++)
    leds[i] = CRGB::Black;
  FastLED.show();


  //Serial.begin(230400);
  Serial.begin(115200);
  inputCmd.reserve(200);
}

/*
  SerialEvent occurs whenever a new data comes in the
 hardware serial RX.  This routine is run between each
 time loop() runs, so using delay inside loop can delay
 response.  Multiple bytes of data may be available.
 */
void serialEvent() {
  unsigned char bytecount = 0;
  while (Serial.available()  && bytecount < 10) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    // if (inChar == '\n') {
    if (inChar == '\n' || inChar == '!') { // ! for puredata
      cmdComplete = true;
    }
    else {
      if (inChar == '_')
        inputCmd += " ";
      else
        inputCmd += inChar;
    }
        bytecount++;
  }
}

byte LED = 0, R = 0, G = 0, B = 0;
unsigned long LEDstate [8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
unsigned long newstate = 0;
int len = 0;
char * pch;
void processCommand(String command) {
  if (command.substring(0, 4) == "led ") {
    if (DEBUG) p("is led command.\n");
    LED = (byte) command.substring(4, 5).toInt();
    if (DEBUG) p("for LED %d\n", LED);

    command.remove(0, 6);

    // if (command.substring(0, 4) == "name") {
    //   command.remove(0, 5);
    //   if (DEBUG) p("has Color name: %s\n", command.c_str());
    //   // FastLED/pixeltypes.h:
    //   if (command == "white")       newstate = CRGB::White;
    //   else if (command == "black")  newstate = CRGB::Black;
    //   else if (command == "red")    newstate = CRGB::Red;
    //   else if (command == "darkred")    newstate = CRGB::DarkRed;
    //   else if (command == "lightred")    newstate = CRGB::Pink;
    //   else if (command == "pink")    newstate = CRGB::Pink;
    //
    //   else if (command == "green")  newstate = CRGB::Green;
    //   else if (command == "dakrgreen")  newstate = CRGB::DarkGreen;
    //   else if (command == "lightgreen")  newstate = CRGB::LightGreen;
    //   else if (command == "orange")   newstate = CRGB::Orange;
    //   else if (command == "darkorange")   newstate = CRGB::DarkOrange;
    //   else if (command == "lightorange")   newstate = CRGB::OrangeRed;
    //
    //   else if (command == "blue")   newstate = CRGB::Blue;
    //   else if (command == "darkblue")   newstate = CRGB::DarkBlue;
    //   else if (command == "lightblue")   newstate = CRGB::LightBlue;
    //
    //
    //   else if (command == "cyan")   newstate = CRGB::Cyan;
    //   else if (command == "darkcyan")   newstate = CRGB::DarkCyan;
    //   else if (command == "lightcyan")   newstate = CRGB::LightCyan;
    //   else if (command == "grey")   newstate = CRGB::Grey;
    //   else if (command == "darkgrey")   newstate = CRGB::DarkGrey;
    //   else if (command == "lightgrey")   newstate = CRGB::LightGrey;
    //   else if (command == "gray")   newstate = CRGB::Grey;
    //   else if (command == "darkgray")   newstate = CRGB::DarkGrey;
    //   else if (command == "lightgray")   newstate = CRGB::LightGrey;
    //
    //   else if (command == "brown")  newstate = CRGB::Brown;
    //   else if (command == "purple") newstate = CRGB::Purple;
    //   else if (command == "yellow")  newstate = CRGB::Yellow;
    //   else if (command == "lime")  newstate = CRGB::Lime;
    //   else if (command == "magenta")  newstate = CRGB::Magenta;
    //   else if (command == "teal")  newstate = CRGB::Teal;
    //   else if (command == "turquoise")  newstate = CRGB::Turquoise;
    //   else if (command == "violet")  newstate = CRGB::Violet;
    //   else {
    //     newstate = LEDstate[LED-1];
    //     if (DEBUG) p("unknown color: %s\n", command.c_str());
    //   }
    // }
    // else {
      if (DEBUG) p("Assuming has RGB or 0|>0 state\n");
      R = (byte) atoi(strtok((char*)command.c_str(), " ")) & 0xff;
      pch = strtok(NULL, " ");
      if (pch == NULL) {
        if (DEBUG) p("Is on/off 0 or >0 state or white\n");
        if (R > 0) newstate = CRGB::White;
        else newstate = CRGB::Black;
      }
      else {
        G = (byte) atoi(pch);
        B = (byte) atoi(strtok(NULL, " ")) & 0xff;
        newstate = (unsigned long)R << 16 | (unsigned long)G << 8 | B; // bloody hell
      }
      if (DEBUG) Serial.println(newstate); // printf has some issues it appears with %x and %lu
      if (DEBUG || 1) Serial.println(newstate, HEX);
    // }

    if (LEDstate[LED-1] != newstate) {
      if (DEBUG) p("Setting LED to 0x%08X %lu - ", ((unsigned long) newstate) & 0xffffff, ((unsigned long)newstate)& 0xffffff);
      if (DEBUG) Serial.println(newstate);
      LEDstate[LED-1] = newstate;
      leds[LED-1] = newstate;
      FastLED.show();
    }
  }
  else if (command.substring(0, 5) == "debug") {
      DEBUG = !DEBUG;
      p("change debug state to: %s\n", DEBUG ? "true" : "false");
  }
}

unsigned long currentMillis;
void loop() {
  // send heartbeat
  currentMillis = millis();
  if (currentMillis - previousMillis >= HEARTBEAT_MILLIS) {
    // save the last time you sent heartbeat
    previousMillis = currentMillis;
    Serial.println(HEARTBEAT_CHAR);
  }

  // process command
  if (cmdComplete) {
    processCommand(inputCmd);
    // clear the string:
    inputCmd = "";
    cmdComplete = false;
  }

  serialEvent(); // teensy main.cpp doesn't have this after loop() call so, we call it
  // Turn the LED on, then pause

  //  for (int i = 0; i < NUM_LEDS; i++)
  //    leds[i] = CRGB::White;
  //  FastLED.show();
  //  delay(500);
  //  // Now turn the LED off, then pause
  //  for (int i = 0; i < NUM_LEDS; i++)
  //    leds[i] = CRGB::Black;
  //  FastLED.show();
  //  delay(500);
}