#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"




#define DEBUG 0
#define SERIAL 1  // ds("This will write to the serial monitor %i\n", int);

#if DEBUG == 1
  #define debug(x) Serial.print(x)
  #define debugln(x) Serial.println(x)
  
#else
#define debug(x)
#define debugln(x)
#endif

#if SERIAL == 1
  #define dss(x, y) printf(x, y)
  #define ds(x) printf(x)
#else
#define dss(x, y)
#define ds(x)
#endif

#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))   // ARRAYSIZE(k1) Returns int with element couont of k1

//External Identifiers
  extern void SetAll(byte red, byte green, byte blue);
  extern void SetAll(int function, byte red, byte green, byte blue);
  extern void SetHue(int hue);
  extern byte red;
  extern byte green;
  extern byte blue;
  extern byte brightness;
  
  extern byte rPlus;
  extern byte gPlus;
  extern byte bPlus;
  extern byte rMinus;
  extern byte gMinus;
  extern byte bMinus;

  

struct CompChars
  {
      char key0[10];
      char value0[10];
      char key1[10];
      char value1[10];
      char key2[10];
      char value2[10];
      char key3[10];
      char value3[10];
      char key4[10];
      char value4[10];
      char key5[10];
      char value5[10];  
      char answer[10];
      char power[10];
  };

struct CompChars c[10] = 		
  {//  key0     value0   key1       value1 key2       value2      key3       value3  key4    value4  key5    value5   answer    power
  	{ "set",    "\0",    "function", "0",  "status",  "up",       "rgb",     "\0",   "\0",   "\0",   "\0",   "\0",    "yes",    "on"},
    { "get",    "\0",    "wifi",     "1",  "bright",  "down",     "red",     "\0",   "\0",   "\0",   "\0",   "\0",    "no",     "off"},
 	  { "clear",  "\0",    "ble",      "2",  "delay",   "add",      "green",   "\0",   "\0",   "\0",   "\0",   "\0",    "\0",     "\0"},
 	  { "reset",  "\0",    "\0",       "3",  "fade",    "subtract", "blue",    "\0",   "\0",   "\0",   "\0",   "\0",    "\0",     "\0"},
 	  { "read",   "\0",    "\0",       "4",  "fillall", "\0",       "hue",     "\0",   "\0",   "\0",   "\0",   "\0",    "\0",     "\0"},
 	  { "write",  "\0",    "\0",       "5",  "effect",  "\0",       "sat",     "\0",   "\0",   "\0",   "\0",   "\0",    "\0",     "\0"},
  	{ "clrbfr", "\0",    "\0",       "6",  "\0",      "\0",       "\0",      "\0",   "\0",   "\0",   "\0",   "\0",    "\0",     "\0"} 
  };
  

  int Comp(char *a, char *b) {
      return (*a == *b && *b == '\0')? 0 : (*a == *b)? Comp(++a, ++b): 1;
    }



//Key:Value Comparisons   
  void KVZero(){
    //0
      //Key0
        for(int i = 0; i < 10; i++) {
          if(strncmp(p[0].keys, c[i].key0, strlen(c[i].key0)) == 0) {
              p[0].kres = i;
              break;
              }
            } 
      //Value0
        do {
        if(isdigit(p[0].values[0]) != 0) { 
            p[0].vres = atoi(p[0].values); 
            printf("Value0 is a numerical entity.\n");
            break;
          }      
        for(int j = 0; j < 10; j++) {
            if(strncmp(p[0].values, c[j].value0, strlen(c[j].value0)) == 0) {
              p[0].vres = j;
              break;
            }
            else {
              p[0].vres = -1;
              break;
          }
        } 
      }while(0);
    }
  void KVOne() {
    //1
      //Key1
         do {
        for(int i = 0; i < 10; i++) {
          if(strncmp(p[1].keys, c[i].key1, strlen(p[1].keys)) == 0) {
            printf("Key 1 match: %s\n", c[i].key1);
              p[1].kres = i;
              break;
              }
            } 
      //Value1
        if(isdigit(p[1].values[0]) != 0) { 
            p[1].vres = atoi(p[1].values); 
            printf("Value1 is a numerical entity.\n");
             break;
           }      
        for(int j = 0; j < 10; j++) {
            if(strncmp(p[1].values, c[j].value1, strlen(c[j].value1)) == 0) {
              p[j].vres = j;
              break;
            }
          }
        } while(0); 
      }  
  void KVTwo() {
    //2
      //Key2
      for(int i = 0; i < 10; i++) {
        if(strncmp(p[2].keys, c[i].key2, strlen(c[i].key2)) == 0) {
            p[2].kres = i;
            break;
            }
          } 
    //Value2
      do {
      if(isdigit(p[2].values[0]) != 0) { 
          p[2].vres = atoi(p[2].values); 
          printf("Value2 is a numerical entity.\n");
          break;
        }
      for(int j = 0; j < 10; j++) {
          if(strncmp(p[2].values, c[j].value2, strlen(c[j].value2)) == 0) {
            p[2].vres = j;
            break;
          }
        }
      } while(0);
    } 

  void KVThree() {
    //3
      //Key3
        for(int i = 0; i < 10; i++) {
          if(strncmp(p[3].values, c[i].key3, strlen(c[i].key3)) == 0) {
            p[3].kres = i;
            break;
          }
        } 
          
      //Value3
        do {
        if(isdigit(p[3].values[0]) != 0) { 
            p[3].vres = atoi(p[3].values); 
            printf("Value3 is a numerical entity.\n");
            break;
          }      
        for(int j = 0; j < 10; j++) {
          if(strncmp(p[3].values, c[j].value3, strlen(c[j].value3)) == 0) {
            p[3].vres = j;
            break;
          }
        }
      } while(0);
    }
  
 
void ClearMemory() {
  for(int i = 0; i < track.pairs; i++) {
    p[i] = {0};

  }
    //for(int i = 0; i < ARRAYSIZE(p); i++) {
      memset(&p,0,sizeof(KV));
    //}
  }

void SetFunction() {
  // function wifi ble
    int functNum;
  // Value2
    // Check Value2 / p[1].values is a #, Copy to functNum
    if(isdigit(p[1].values[0]) != 0) { functNum = atoi(p[1].values); }
    printf("Function Number = %i\n", functNum);

  // KEY 3 
    // check p[2].keys / key2
    // status, bright, delay fade, fillall, effect
    /*
    for(int i = 0; i < 10; i++) {
      if(strcmp(p[2].keys, c[i].key2) == 0) {
        track.kres3 = i;
      }
    }
    */

  int i = 0;
  for(;;) {
    Comp(p[2].keys, c[i].key2) == 0 ? printf("Yes p[2].keys = %s\n", c[0].key2) : printf("No p[2].keys does not = %s\n", c[i].key2) ? p[2].kres = i ? break : i ++;
    printf("p[2].keys = %i\n", p[2].kres);
  }
/*
    int outcome;
    outcome = Comp(p[2].keys, c[0].key2);
    printf("Outcome = %i\n", outcome);

    outcome = Comp(p[2].keys, c[1].key2);
    printf("Outcome = %i\n", outcome);

    printf("Key1 %i\n", p[1].kres);
    printf("Value1 %i\n", p[1].vres);
    KVOne();
    printf("Key1 %i\n", p[1].kres);
    printf("Value1 %i\n", p[1].vres);
    
    printf("Key2 %i\n", p[2].kres);
    printf("Value2 %i\n", p[2].vres);
    KVTwo();
    printf("Key1 %i\n", p[2].kres);
    printf("Value1 %i\n", p[2].vres);
        
  */
    if(p[2].kres == 0 && functNum == 1) { // Status funct 1
        if(strncmp(p[2].values, "on", int(2)) == 0) { // on
          if(f[0].state == -1) {
            f[0].state  *= -1;
            printf("Enabling Primary Function\n");
          }
          else {
            printf("Primary Function already on\n");
          }
        }
        else if(strncmp(p[2].values, "off", int(3)) == 0) { // off
          if(f[0].state == 1) {
            f[0].state *= -1;
            printf("Disabling Primary Function\n");
          }
          else {
              printf("Primary Function already off\n"); 
          }
        }
      }

    else if(p[2].kres == 0 && functNum == 2) { // Status funct 2
        if(strncmp(p[2].values, "on", int(2)) == 0) { // on
          if(f[1].state == -1) {
            f[1].state  *= -1;
            printf("Enabling Secondary Function\n");
          }
          else {
            printf("Secondary Function already on\n");
          }
        }
        else if(strncmp(p[2].values, "off", int(3)) == 0) { // off
          if(f[1].state == 1) {
            f[1].state *= -1;
            printf("Disabling Secondary Function\n");
          }
          else {
              printf("Secondary Function already off\n"); 
          }
        }
      }

    else if(p[2].kres == 0 && functNum == 3) { // Status funct 2
        if(strncmp(p[2].values, "on", int(2)) == 0) { // on
          if(f[2].state == -1) {
            f[2].state  *= -1;
            printf("Enabling Tertiary Function\n");
          }
          else {
            printf("Tertiary Function already on\n");
          }
        }
        else if(strncmp(p[2].values, "off", int(3)) == 0) { // off
          if(f[2].state == 1) {
            f[2].state *= -1;
            printf("Disabling Tertiary Function\n");
          }
          else {
              printf("Tertiary Function already off\n"); 
          }
        }
      }    

    if(p[2].kres == 1) { // Bright
      

      }

    if(p[2].kres == 2) { // Delay

      }

    if(p[2].kres == 3) { // Fade

      }

    if(p[2].kres == 4) { // Fillall

      }

    if(p[2].kres == 5) { // Effect

      } 
      ClearMemory();
  }

void Set() {  
  // key1 options - function-wifi-ble
   
    printf("Triggered set\n");

  //  Key2
    // p[1].keys / Key2
    for(int i = 0; i < 10; i++) {
      if(strcmp(p[1].keys, c[i].key1) == 0) {
        p[1].kres = i;
        }
      } 
    switch(p[1].kres) {

      case 0: // Function 
        SetFunction();      
        break;

      case 1: //  wifi
        printf("Triggered WiFi\n");
        break;

      case 2: //  ble
        printf("Triggered BLE\n");
        break;
      }
    }

void Locate(char* tempInput, char* d, int* index) {
    char input[strlen(tempInput)];
    strcpy(input, tempInput);
    
    *index = 0;

    int i = 0;
      for(; i < input[i] != '\0'; i++) {
        if(input[i] != *d) {
          *index += 1;
        }
        else {
         *index += 1;
          break;
        }
      
      if(input[i] == *d) { break; }
      }
    }
  
//Instructions 
  
  //Commands
    // All commands are listed with double quotes " surrounding them.  
    // Do not enter the command with the doublt quotes, it will not work.
    // Only enter the text inside the quotes.
    
    // Example:   
      // cmd:set function:1 delay:2 ms:5
  
  //Emergency Operations
    // Clear the input buffer  "clearbuffer"
    // Reset the ESP32 CPU's   "resetesp" 

  //Parsing the Input into pairs
    // token temorarily stores the the k:v pair before transferring to p1.pairs
    // p1.pairs stores the k:v pairs after they are parsed by the space delimiter 
    // p1.pairCnt tracks the iterations and gives us a count of the pairs
    // p1.len captures and stores the length of each of the pairs
      // Each p1 element lines up with one another to provide different properties
  //

char* ParseInput(char* tempInput, byte tempChars, size_t* length) { //Parse the input using a (space) as the delimiter
  //Checking input
    static char* input = NULL;
              debugln("1");

      if(tempInput != NULL) {
        track.parsing = true;
              debugln("2");
        input = tempInput;
        }
      if(input == NULL) {
              debugln("3");
        return "\0";
        }
  //Dynamic memory allocaion and iteration through the input
    char* result = new char[strlen(input) + 1];
      int i = 0;
      for(; input[i] != '\0'; i++) {
              debugln("4");
        if(input[i] != ' ') {
              debugln("5");
          result[i] = input[i];
        }

      else {
              debugln("6");
        result[i] = '\0';
        input = input + i + 1;
        *length = strlen(result);
        return result;
       }
      }
              debugln("7");
      track.parsing = false;
      result[i] = '\0';
      input = NULL;
      return result;
    }

void ReadSerial2() {
  //Setting up variables

    debugln("Reading Serial");
    
    char userInput[75] = "";  
    size_t length = 0;
    track.tokcnt = 0;
    track.pairs = 0;
  //Readin the Serial Input
    while(Serial.available() > 0) {
      track.readingSerial = true;
                debugln("Serial available");
      size_t numChars = Serial.readBytesUntil('\n', userInput, sizeof(userInput) - 1);
        if(!numChars) { 
          printf("Error: incorect or corrupt input,\nplease check and try again.\n");
          break; // If the incoming bytes are < 1 bye, cancel t5he operation to avoid issues
          }
      userInput[numChars] = '\0';
      size_t userInputLen = sizeof(userInput);
      printf("You entered: %s\n", userInput);

  //Checking for emergency reset cmd
    if (strcmp(userInput, "resetesp") == 0) { 
                debugln("1");
      printf(" Reseting ESP CPU!\n");
      esp_cpu_reset(0);
      esp_cpu_reset(1);
                debugln("2");
      printf(" ESP CPU has been reset.\n");
    }
  
  //Checking for emergency buffer clear
    else if(strcmp(userInput, "clearbuffer") == 0) {
      debugln("3");
      printf("Clearing Buffers\n");
      memset(userInput,0,sizeof(userInput));
      debugln("4");
      ClearMemory();
      debugln("5");
    }   

  // Parsing input into tokens
    track.tokcnt = 0; // Tracks the number of key:value pairs
    char* token = ParseInput(userInput, userInputLen, &length);
    strcpy(p[track.tokcnt].pairs, token); //Store the parsed token data in the struct
    track.toklen[track.tokcnt] = length; //Capture the length of the token and store in the struct
                  debugln("Token: "+String(p[track.tokcnt].pairs)); 
                  debugln("Token length: "+String(track.toklen[track.tokcnt]));
    track.tokcnt++;


    while(token != "\0") { //Remaining string is stored in NULL for futher parsing 
      token = ParseInput(NULL, userInputLen, &length);
        strcpy(p[track.tokcnt].pairs, token);
                  debugln("Token: "+String(p[track.tokcnt].pairs)); 
                  debugln("Token length: "+String(track.toklen[track.tokcnt]));
        track.toklen[track.tokcnt] = length;
        track.tokcnt++;

                  debugln("*1");
        if(track.parsing == false) {
                  debugln("*2");
          break;
          }
                  debugln("*3");
      }

  // Separating tokens to key values
    track.keys = 0;
    track.values = 0;
    track.pairs = 0;

    printf("Pairs = %i\n", track.pairs);

    for(int i = 0; i < track.tokcnt; i++) { 
      if(!strchr(p[i].pairs, ':')) { 
        printf("Colon \":\" Not Found in pair %i\n", i); 
        strcpy(p[i].keys, p[i].pairs);
        printf("Copied the contents of p[%i].pairs,\nto p[%i].keys and advanced the index by 1.\nThe first value will be empty.\n", i, i);
        i++;
        } 

  //Breaking pairs to keys and values
        Locate(p[i].pairs, ":", &p[i].ci); // Locate the Comma (:)  and store in p[i].ci

        strncpy(p[i].keys, p[i].pairs, p[i].ci - 1); // Copy text inp[i].pairs before the comma (key) to p[i].keys
        if(p[i].keys > 0) { track.keys++; } // tracker to track the number of keys parsed
          printf("keys %i: %s\n", i, p[i].keys);
        
        strncpy(p[i].values, &p[i].pairs[p[i].ci], strlen(p[i].pairs) - p[i].ci);
        if(p[i].values > 0) { track.values++; }
          printf("Values %i: %s\n", i, p[i].values);
          
        track.pairs++;
        memset(userInput,0,sizeof(userInput));
        track.readingSerial = false;
      }
    }

  // Key 0
    // comparing keys and values to set commands
    for(int i = 0; i < 10; i++) {
      if(strcmp(p[0].keys, c[i].key0) == 0) {
        switch (i) {
          case 0: //  set
          //value0 not currently in use, moving to compare key1
            //key1 - capture for use with Set()
            Set();

            break;
          case 1:
            printf("Triggered get\n");
            break;
          case 2:
            printf("Triggered clear\n");
            break;
          case 3:
            printf("Triggered reset\n");
            break;      
            }
          } 
        }
      }



void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("Hello, ESP32-S3!");
}

void loop() { 
  if(Serial.available() > 0) {
    //ReadSerial();
    ReadSerial2();
    }

if(f[0].state == 1 && track.readingSerial == false) {
  FastLED.setBrightness(f[0].bright);
    //WhiteFlame(red, green, blue);
    //WhiteFlameBackup(red, green, blue);
  }
  
if(f[1].state == 1 && track.readingSerial == false) {
  FastLED.setBrightness(f[1].bright);
    //CandleFlame();
  }
if(f[2].state == 1 && track.readingSerial == false) {
    
  }
  }