#ifndef functions_h
#define functions_h

#include <FS.h>
#include "ArduinoJson.h"

extern "C" {
  #include "user_interface.h"
}

/*
  Here is a collection of useful functions and variables.
  They are used globally via an 'extern' reference in every class.
  Making everything static will lead to problems with the Arduino ESP8266 2.0.0 SDK, 
  there were some fixed in later version but we need to use the old version for injecting deauth packets.
*/

uint8_t broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t wifi_channel = 1;

// ===== UTF8 FIX ===== //
String escape(String str){
  str.replace(String(BACKSLASH), String(BACKSLASH) + String(BACKSLASH));
  str.replace(String(DOUBLEQUOTES), String(BACKSLASH) + String(DOUBLEQUOTES));
  return str;
}
bool ascii(char c){
  return c >= 0 && c <= 127;
}

bool printableAscii(char c){
  return c >= 32 && c <= 126;
}

bool getBit(uint8_t b, uint8_t n){
  return (b >> n) % 2 != 0;
}

uint8_t utf8(uint8_t c){
  if(!getBit(c,7)) return 1;
  if(getBit(c,7) && getBit(c,6) && !getBit(c,5)) return 2;
  if(getBit(c,7) && getBit(c,6) && getBit(c,5) && !getBit(c,4)) return 3;
  if(getBit(c,7) && getBit(c,6) && getBit(c,5) && getBit(c,4) && !getBit(c,3)) return 4;
  return 0;
}

bool utf8Part(uint8_t c){
  return getBit(c,7) && !getBit(c,6);
}

String fixUtf8(String str){
  int size = str.length();

  String result = String();
  char c;
  uint8_t len;
  bool ok;
  
  for(int i=0;i<size;i++){
    c = str.charAt(i); // get character
    len = utf8(c); // get utf8 char len
    
    if(len <= 1){
      result += c; // when 1 byte char, add it :)
    }
    else if(i + len > size){ // when char bigger than remaining string, end loop
      i = size+1; 
    }
    else{
      ok = true;
      for(int j=1;j<len && ok;j++){
        ok = utf8Part(str.charAt(i+j)); // if following char is compliant or not
      }
      if(ok) result += c; // everything is ok, add char and continue
      else { // utf8 char is broken
        for(int j=1;j<len;j++){ // go through the next bytes
          c = str.charAt(i+j);
          if(utf8(c) == 1) result += c; // when byte is ascii, add it :)
        }
        i += len - 1; // skip utf8 char because we already managed it
      }
    }
  }
  return result;
}

String removeUtf8(String str){
  str = fixUtf8(str); // fix it in case a utf char is broken
  int size = str.length();

  String result = String();
  char c;
  uint8_t len;
  
  for(int i=0;i<size;i++){
    c = str.charAt(i); // get character
    len = utf8(c); // get utf8 char len
    
    if(len <= 1) result += c; // when 1 byte char, add it :)
    else i += len-1; // skip other chars
  }

  return result;
}

int utf8Len(String str){
  int size = str.length();

  int result = 0;
  char c;
  uint8_t len;
  
  for(int i=0;i<size;i++){
    c = str.charAt(i); // get character
    len = utf8(c); // get utf8 char len
    
    if(len <= 1) result++; // when 1 byte char, add 1 :)
    else{
      result++;
      for(int j=1;j<len;j++){
        c = str.charAt(i+j);
        if(!utf8Part(c) && utf8(c) == 1){
          Serial.println(c, HEX);
          result++; // if following char is compliant or not
        }
      }
      i += len - 1;
    }
  }

  return result;
}

String replaceUtf8(String str, String r){
  str = fixUtf8(str); // fix it in case a utf char is broken
  int size = str.length();

  String result = String();
  char c;
  uint8_t len;
  
  for(int i=0;i<size;i++){
    c = str.charAt(i); // get character
    len = utf8(c); // get utf8 char len
    
    if(len <= 1) result += c; // when 1 byte char, add it :)
    else{
      result += r;
      i += len-1; // skip other chars
    }
  }

  return result;
}

/* ===== WiFi ===== */
void setWifiChannel(uint8_t ch){
  if(ch != wifi_channel && ch > 0 && ch < 15){
    wifi_channel = ch;
    wifi_set_channel(wifi_channel);
  }
}

void setOutputPower(float dBm) {
  if (dBm > 20.5) {
    dBm = 20.5;
  } else if (dBm < 0) {
    dBm = 0;
  }

  uint8_t val = (dBm * 4.0f);
  system_phy_set_max_tpw(val);
}

/* ===== MAC ADDRESSES ===== */
bool macBroadcast(uint8_t* mac) {
  for (uint8_t i = 0; i < 6; i++)
    if (mac[i] != broadcast[i]) return false;
  return true;
}

bool macValid(uint8_t* mac) {
  for (uint8_t i = 0; i < 6; i++)
    if (mac[i] != 0x00) return true;
  return false;
}

bool macMulticast(uint8_t* mac) {
  // see https://en.wikipedia.org/wiki/Multicast_address
  if (mac[0] == 0x33 && mac[1] == 0x33) return true;
  if (mac[0] == 0x01 && mac[1] == 0x80 && mac[2] == 0xC2) return true;
  if (mac[0] == 0x01 && mac[1] == 0x00 && (mac[2] == 0x5E || mac[2] == 0x0C)) return true;
  if (mac[0] == 0x01 && mac[1] == 0x0C && mac[2] == 0xCD && (mac[3] == 0x01 || mac[3] == 0x02 || mac[3] == 0x04) && (mac[4] == 0x00 || mac[4] == 0x01)) return true;
  if (mac[0] == 0x01 && mac[1] == 0x00 && mac[2] == 0x0C && mac[3] == 0xCC && mac[4] == 0xCC && (mac[5] == 0xCC || mac[5] == 0xCD)) return true;
  if (mac[0] == 0x01 && mac[1] == 0x1B && mac[2] == 0x19 && mac[3] == 0x00 && mac[4] == 0x00 && mac[5] == 0x00) return true;
  return false;
}

/* ===== VENDOR LIST (oui.h) ===== */
void getRandomMac(uint8_t* mac) {
  int num = random(sizeof(data_vendors) / 11 - 1);
  uint8_t i;
  for (i = 0; i < 3; i++) mac[i] = pgm_read_byte_near(data_macs + num * 5 + i);
  for (i = 3; i < 6; i++) mac[i] = random(256);
}

int binSearchVendors(uint8_t* searchBytes, int lowerEnd, int upperEnd) {
  uint8_t listBytes[3];
  int res;
  int mid = (lowerEnd + upperEnd) / 2;

  while (lowerEnd <= upperEnd) {

    listBytes[0] = pgm_read_byte_near(data_macs + mid * 5);
    listBytes[1] = pgm_read_byte_near(data_macs + mid * 5 + 1);
    listBytes[2] = pgm_read_byte_near(data_macs + mid * 5 + 2);

    res = memcmp(searchBytes, listBytes, 3);

    if (res == 0) {
      return mid;
    } else if (res < 0) {
      upperEnd = mid - 1;
      mid = (lowerEnd + upperEnd) / 2;
    } else if (res > 0) {
      lowerEnd = mid + 1;
      mid = (lowerEnd + upperEnd) / 2;
    }

  }

  return -1;
}

String searchVendor(uint8_t* mac) {
  String vendorName = String();
  int pos = binSearchVendors(mac, 0, sizeof(data_macs) / 5 - 1);
  int realPos = pgm_read_byte_near(data_macs + pos*5 + 3) | pgm_read_byte_near(data_macs + pos*5 + 4) << 8;
  
  if (pos >= 0) {
    char tmp;
    for (int i = 0; i < 8; i++){
      tmp = (char)pgm_read_byte_near(data_vendors + realPos * 8 + i);
      if(tmp != ENDOFLINE) vendorName += tmp;
      tmp += SPACE;
     }
  }

  return vendorName;
}


/* ===== STRING ===== */
String bytesToStr(uint8_t* b, uint32_t size) {
  String str;
  for (uint32_t i = 0; i < size; i++) {
    if (b[i] < 0x10) str += ZERO;
    str += String(b[i], HEX);
    if (i < size-1) str += DOUBLEPOINT;
  }
  return str;
}

String macToStr(uint8_t* mac) {
  return bytesToStr(mac, 6);
}

bool strToMac(String macStr, uint8_t* mac){
  macStr.replace(String(DOUBLEPOINT), String()); // ":" -> ""
  macStr.replace("0x", String()); // "0x" -> ""
  macStr.replace(String(COMMA), String()); // "," -> ""
  macStr.replace(String(DOUBLEQUOTES), String()); // "\"" -> ""
  macStr.toUpperCase();
  
  if(macStr.length() != 12){
    prntln(F_ERROR_MAC);
    return false;
  }
  
  for (uint8_t i = 0; i < 6; i++)
    mac[i] = strtoul((macStr.substring(i * 2, i * 2 + 2)).c_str(), NULL, 16);
    
  return true;
}

void strToColor(String str, uint8_t* buf){
  str.replace(":", "");
  str.replace("0x", "");
  str.replace(",", "");
  str.replace("#", "");
  str.toUpperCase();

  if(str.length() != 6){
    prntln(F_COLOR_INVALID);
    return;
  }
  
  for (uint8_t i = 0; i < 3; i++)
    buf[i] = strtoul((str.substring(i * 2, i * 2 + 2)).c_str(), NULL, 16);
}

String buildString(String left, String right, int maxLen){
  String result = left;
  int spacesToAdd = maxLen - left.length()/*utf8Len(left)*/ - right.length()/*utf8Len(right)*/;
  for(int i=0;i<spacesToAdd;i++){
    result += SPACE;
  }
  result += right;
  return result;
}

/* ===== SPIFFS ===== */
bool progmemToSpiffs(const char* adr, int len, String path) {
  prnt(str(SETUP_COPYING) + path + str(SETUP_PROGMEM_TO_SPIFFS));
  File f = SPIFFS.open(path, "w+");
  if (!f){
    prntln(SETUP_ERROR);
    return false;
  }

  for (int i = 0; i < len; i++) {
    f.write(pgm_read_byte_near(adr + i));
  }
  f.close();
  
  prntln(SETUP_OK);
}

bool readFile(String path, String &buf) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  File f = SPIFFS.open(path, "r");
  if (!f) return false;
  if (f.size() == 0) return false;

  while (f.available()) buf += (char)f.read();

  f.close();

  return true;
}

void readFileToSerial(String path, bool showLineNum) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  File f = SPIFFS.open(path, "r");
  if (!f) {
    prnt(F_ERROR_READING_FILE);
    prntln(path);
    return;
  }

  uint32_t c = 0;
  char tmp;
  if(showLineNum){
    prnt(buildString(String(),(String)c + String(VERTICALBAR),6));
  }
  
  while (f.available()){
    tmp = f.read();
    prnt(tmp);
    if(tmp == NEWLINE && showLineNum){
      c++;
      prnt(buildString(String(),(String)c + String(VERTICALBAR),6));
    }
  }

  f.close();
}

bool copyFile(String pathFrom, String pathTo){
  if(pathFrom.charAt(0) != SLASH) pathFrom = String(SLASH) + pathFrom;
  if(pathTo.charAt(0) != SLASH) pathTo = String(SLASH) + pathTo;

  if(!SPIFFS.exists(pathFrom)){
    prnt(F_ERROR_FILE);
    prntln(pathFrom);
    return false;
  }
  
  File f1 = SPIFFS.open(pathFrom, "r");
  File f2 = SPIFFS.open(pathTo, "w+");
  
  if(!f1 || !f2) return false;
  
  while (f1.available()){
    f2.write(f1.read());
  }

  return true;
}

bool renameFile(String pathFrom, String pathTo){
  if(pathFrom.charAt(0) != SLASH) pathFrom = String(SLASH) + pathFrom;
  if(pathTo.charAt(0) != SLASH) pathTo = String(SLASH) + pathTo;

  if(!SPIFFS.exists(pathFrom)){
    prnt(F_ERROR_FILE);
    prntln(pathFrom);
    return false;
  }

  SPIFFS.rename(pathFrom, pathTo);
  return true;
}

bool writeFile(String path, String &buf) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  File f = SPIFFS.open(path, "w+");
  if (!f) return false;

  uint32_t len = buf.length();
  for (uint32_t i = 0; i < len; i++)
    f.write(buf.charAt(i));
  f.close();

  return true;
}

bool appendFile(String path, String &buf) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  File f = SPIFFS.open(path, "a+");
  if (!f) return false;

  uint32_t len = buf.length();
  for (uint32_t i = 0; i < len; i++)
    f.write(buf[i]);
  f.close();

  return true;
}

void checkFile(String path, String data) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  if (!SPIFFS.exists(path))
    writeFile(path, data);
}

bool removeLines(String path, int lineFrom, int lineTo){
  int c = 0;
  char tmp;
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;

  String tmpPath = str(F_TMP)+path+str(F_COPY);
  
  File f = SPIFFS.open(path, "r");
  File f2 = SPIFFS.open(tmpPath, "w");
  
  if (!f || !f2) return false;

  while (f.available()){
    tmp = f.read();
    if(c < lineFrom || c > lineTo) 
      f2.write(tmp);
    if(tmp == NEWLINE) c++;
  }
  
  f.close();
  f2.close();
  SPIFFS.remove(path);
  SPIFFS.rename(tmpPath, path);
}

bool replaceLine(String path, int line, String &buf){
  int c = 0;
  char tmp;
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;

  String tmpPath = "/tmp"+path+"_copy";
  
  File f = SPIFFS.open(path, "r");
  File f2 = SPIFFS.open(tmpPath, "w");
  
  if (!f || !f2) return false;

  while (f.available()){
    tmp = f.read();
    if(c != line) 
      f2.write(tmp);
    else{
      f2.println(buf);
      while(f.read() != NEWLINE && f.available()){}
      c++;
    }
    if(tmp == NEWLINE) c++;
  }
  
  f.close();
  f2.close();
  SPIFFS.remove(path);
  SPIFFS.rename(tmpPath, path);
}

JsonVariant parseJSONFile(String path, DynamicJsonBuffer &jsonBuffer) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;

  //create JSON Variant
  JsonVariant root;

  //create buffer
  String buf = "";

  //read file into buffer
  if (!readFile(path, buf)) { //if file couldn't be opened, send 404 error
    prnt(F_ERROR_OPEN);
    prntln(path);
    buf = "{}";
  }

  //parse file-buffer into a JSON Variant
  root = jsonBuffer.parse(buf);

  //if parsing unsuccessful
  if (!root.success()) {
    prnt(F_ERROR_PARSING_JSON);
    prntln(path);
    prntln(buf);
  }

  return root;
}

bool removeFile(String path) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;
  return SPIFFS.remove(path);
}

void saveJSONFile(String path, JsonObject &root) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;

  //create buffer
  String buf;

  //convert JSON object into string and write it into buffer
  root.printTo(buf);

  //if buffer too big
  if (buf.length() > 2048) {
    prntln(F_ERROR_TO_BIG );
    prntln(path);
    prntln(buf);
    return;
  }

  //write buffer into SPIFFS file
  writeFile(path, buf);
}

void saveJSONFile(String path, JsonArray &root) {
  if (path.charAt(0) != SLASH) path = String(SLASH) + path;

  //create buffer
  String buf;

  //convert JSON object into string and write it into buffer
  root.printTo(buf);

  //if buffer too big
  if (buf.length() > 2048) {
    prntln(F_ERROR_TO_BIG );
    prntln(path);
    prntln(buf);
    return;
  }

  //write buffer into SPIFFS file
  writeFile(path, buf);
}

String formatBytes(size_t bytes) {
  if (bytes < 1024) return String(bytes) + "B";
  else if (bytes < (1024 * 1024)) return String(bytes / 1024.0) + "KB";
  else if (bytes < (1024 * 1024 * 1024)) return String(bytes / 1024.0 / 1024.0) + "MB";
  else return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
}

/*
  void parseBytes(const char* str, char sep, byte* bytes, int maxBytes, int base) {
  for (int i = 0; i < maxBytes; i++) {
    bytes[i] = strtoul(str, NULL, base);  // Convert byte
    str = strchr(str, sep);               // Find next separator
    if (str == NULL || *str == '\0') {
      break;                            // No more separators, exit
    }
    str++;                                // Point to next character after separator
  }
  }
*/
#endif



