#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <time.h>
#include "FS.h"

#define ESPTIMEOUT 4000
#define SECURE_CONNECTION true

File ca;
File key;

char* ssid = "LishaJoinville";
char* password = "12345678";

char* host = "iot.lisha.ufsc.br";
char* route = "/api/put.php";
char* req_method = "POST";
int port = 443;

enum {ESPBUSY, ESPIDLE} machine_state;
enum {ESPDISCONNECTED, ESPCONNECTED} connection_state;
enum {CLIENTCONNECTED, CLIENTDISCONNECTED} client_state;
enum {ESPMOUNTED, ESPNOTMOUNTED} fs_state;

#if SECURE_CONNECTION
  WiFiClientSecure *client = new WiFiClientSecure;
#else
  WiFiClient client;
#endif

void connect();
void hello_host();
void load_files();
void incoming_command(String received_data);

unsigned long int last_time_recordered = 0;
unsigned long int last_response_time = 0;

void setup() {
  machine_state = ESPBUSY;
  Serial.begin(115200);
  
  if (!SPIFFS.begin()) {
    fs_state = ESPNOTMOUNTED; 
  } else {
    fs_state = ESPMOUNTED;
    ca = SPIFFS.open("/cert.der", "r");
    key = SPIFFS.open("/key.der", "r");
  }
  
  connect();

  load_files();
  
  if(connection_state != ESPCONNECTED)
    Serial.print("CONNECTIONFAIL\r");

  if(fs_state != ESPMOUNTED)
    Serial.print("FILESYSTEMFAIL\r");

  if(fs_state == ESPMOUNTED && connection_state == ESPCONNECTED)
    Serial.print("READY\r");

    hello_host();
  
  machine_state = ESPIDLE;
}


bool response_received = false;
bool first_line = false;

void loop() {
  if(client->available() != 0){
    machine_state = ESPBUSY;

    String line = client->readStringUntil('\r');

    if(first_line) {
      last_response_time = (unsigned long int) millis() - last_time_recordered;  
      first_line = false;
    }

    Serial.println(line);

    response_received = true;
    
  } else if(client->available() == 0){
    machine_state = ESPIDLE;

    if(response_received) {
      client_state = CLIENTDISCONNECTED;

      delete(client);

      client = new WiFiClientSecure;

      response_received = false;
      first_line = true;
    }
  }
    
  if (Serial.available() > 0 && machine_state == ESPIDLE){
    machine_state = ESPBUSY;

    String received = Serial.readStringUntil('\r');
    incoming_command(received);

  }

}

char dump[82];
char message[256];

String data;
String command = "";
String parameter = "";
int delimiterPosition = -1;

String msg_size = "";
bool gettingSize = false;
  
void incoming_command(String received_data){
  
  String incoming = received_data.substring(0,2);

  int received_length = received_data.length();

  if(incoming != "AT") {
    Serial.print("INVALID\r");
    machine_state = ESPIDLE;
    return;
  }

  data = received_data.substring(2, received_length);

  for(int i = 0; i < data.length(); i++){
     if(data[i] == '!') {
      gettingSize = !gettingSize;
     } else if(gettingSize) {
        if(data[i] == '=') {
          delimiterPosition = i;
          gettingSize = !gettingSize;
        } else
          msg_size += data[i];
     } else if(data[i] != '=' && delimiterPosition == -1)
        command += data[i];
     else if(data[i] == '=') {
        delimiterPosition = i;
     } else if(data[i] != ' ') {
        parameter += data[i];
     }   
  }

  parameter += '\0';

  int parameter_length = parameter.length();

  if(command == "+SYSTEMCHECK"){

    Serial.print("CONNECTION:");
    (connection_state == ESPCONNECTED)?Serial.print("OK"):Serial.print("FAIL\r");
    Serial.print("&FILESYSTEM:");
    (fs_state == ESPMOUNTED)?Serial.print("OK"):Serial.print("FAIL\r");
    machine_state = ESPIDLE;
        
  } else if(command == "+SYSTEMREADY"){

    if(connection_state == ESPCONNECTED && fs_state == ESPMOUNTED)
      Serial.print("OK\r");
    else
      Serial.print("FAIL\r");

    machine_state = ESPIDLE;
    
  } else if(command == "+RESPONSETIME"){

    Serial.print("OK=");
    Serial.print(last_response_time);
    Serial.print('\r');

    machine_state = ESPIDLE;
    
  } else if(command == "+GETHOST"){
  
    Serial.print("OK=");
    Serial.print(host);
    Serial.print("\r");
    machine_state = ESPIDLE;
  
  } else if(command == "+GETROUTE"){

    Serial.print("OK=");
    Serial.print(route);
    Serial.print("\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+GETPORT"){

    Serial.print("OK=");
    Serial.print(port);
    Serial.print("\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+SETHOST"){

    parameter.toCharArray(host, parameter_length + 1);
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+SETROUTE"){

    parameter.toCharArray(route, parameter_length + 1);
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+SETPORT"){

    port = parameter.toInt();
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+SETSSID"){

    parameter.toCharArray(ssid, parameter_length + 1);
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+SETPASSWORD"){

    parameter.toCharArray(password, parameter_length + 1);
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+CONNECTWIFI"){

    connect();
    Serial.print("OK\r");
    machine_state = ESPIDLE;
    
  } else if(command == "+GETTIMESTAMP"){      
      configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
      time_t now = time(nullptr);
      while (now < 1000) {
        delay(2);
        now = time(nullptr);
      }
      Serial.print(now);
      Serial.print("\r");
      
  } else if(command == "+GETHEAPSIZE"){      
      Serial.printf("%u\r", ESP.getFreeHeap());
      
  } else if(command == "+SENDTSTP"){
    
    if(parameter.length() < 82) {
      Serial.print("ERR=INVALIDCONTENT\r");
      machine_state = ESPIDLE;
      return;
    }

    if(!client->connected())
      hello_host();    

    for(int i = 0; i < 82; i++){
      dump[i] = parameter.charAt(i);
    }
    
    last_time_recordered = millis();    

    String test = String(req_method) + " " + String(route) + " HTTP/1.0\r\nConnection: keepalive\r\nContent-type: application/octet-stream\r\nContent-Length: 82\r\n\r\n";
    
    for(int i = 0; i < test.length(); i++){
      message[i] = test.charAt(i);
    }

    memcpy(message + test.length(), dump, 82);

    client->write((uint8_t *) message, test.length() + 82);    
  
  } else if(command == "+SEND"){

    int data_size = msg_size.toInt();

    //if the string is bigger than 128 chars, it refuses the command
    if(parameter.length() > 128 || data_size > 128) {
      Serial.print("ERR=BIGSTRING_MAKEITSMALLERTHAN128BYTES\r");
      machine_state = ESPIDLE;
      return;
    }

    if(client_state != CLIENTCONNECTED) {
      hello_host();
    }
    
    for(int i = 0; i < data_size; i++){
      dump[i] = parameter.charAt(i);
    }
    
    last_time_recordered = millis();    

    String test = String(req_method) + " " + String(route) + " HTTP/1.0\r\nConnection: keep-alive\r\nContent-type: application/octet-stream\r\nContent-Length: " + data_size + "\r\n\r\n";
    
    for(int i = 0; i < test.length(); i++){
      message[i] = test.charAt(i);
    }

    memcpy(message + test.length(), dump, data_size);

    client->write((uint8_t *) message, test.length() + data_size);    

  } else {
    Serial.print("INVALIDCOMMAND");
  }

  delimiterPosition = -1;
  parameter = "";
  data = "";
  command = "";
  msg_size = "";
  gettingSize = false;
}

void connect(){
  WiFi.begin(ssid, password);  
  
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    connection_state = ESPDISCONNECTED;
  } else {
    connection_state = ESPCONNECTED;
  }
}

void hello_host(){
  unsigned long int timeNow = millis();
  
  while(!client->connect(host, port) && (unsigned long int) millis() - timeNow < 5000) {
    delay(0);
  }

  //check if connection was successfull
  if(!client->connected())
    client_state = CLIENTDISCONNECTED;
  else
    client_state = CLIENTCONNECTED;
}

void load_files(){
  #if SECURE_CONNECTION
    client->loadCertificate(ca);
    client->loadPrivateKey(key);
  #endif
}

