#include <esp_sleep.h>

#include <SevSeg.h>
SevSeg sevseg; //Instantiate a seven segment object

#include <ArduinoJson.h>
#include <ArduinoJson.hpp>
StaticJsonDocument<500> json_doc;

#include <WiFi.h>

const char* ssid = "PUT_YOUR_WIFI_ACCESS_POINT_NAME_HERE";
const char* password = "PUT_YOUR_WIFI_PASSWORD_HERE";

#define DEFAULT_RX_TIMEOUT 5 // Seconds for timeout, default is 3s
#include <AsyncHTTPRequest_Generic.h>
#include <AsyncHTTPRequest_Generic.hpp>

AsyncHTTPRequest request;

String url_path = ":8080/api/values?attrs=0&filter=vba|vsp|vte|vsmg"; //get all values at once
String url = "http://192.168.43.1" + url_path; // android default is 192.168.43.1, but it can be dynamic. this value will be overwrittern later if needed

IPAddress gateway;

bool request_error = false;

int display_mode = 1; // iterate between 1,2,3,4

// json response keys
#define TEMPERATURE_KEY "vte"
#define BATTERY_KEY "vba" // pba
#define SPEED_KEY "vsp"
#define SAFETY_MARGIN_KEY "vsmg"

// 2 character codes to be displayed
#define TEMPERATURE_CHAR "tE"
#define BATTERY_CHAR "bA" 
#define SPEED_CHAR "SP"
#define SAFETY_MARGIN_CHAR "SA"

String selected_key = BATTERY_KEY;
char selected_char[3] = "--";
int selected_num = 0; //number displayed

int temperature_num = 0;
int speed_num = 0;
int battery_num = 0;
int safety_margin_num = 0;

// arbitrary alert values defined here

#define SPEED_ALERT 40 // speed goes above 40kph
#define BATTERY_ALERT 5 // battery goes below 10% regardless speed
#define TEMPERATURE_ALERT 60 //temperature goes above  60 C
#define BATTERY_SPEED_ALERT_1_BATTERY 25 // battery goes below 25% and speed above 35kph
#define BATTERY_SPEED_ALERT_1_SPEED 35
#define BATTERY_SPEED_ALERT_2_BATTERY 20 // battery goes below 20% and speed above 30kph
#define BATTERY_SPEED_ALERT_2_SPEED 30
#define BATTERY_SPEED_ALERT_3_BATTERY 15 // battery goes below 15% and speed above 25kph
#define BATTERY_SPEED_ALERT_3_SPEED 25
#define SAFETY_MARIGN_ALERT 20


#define SEGMENT_A_PIN D9
#define SEGMENT_B_PIN D3
#define SEGMENT_C_PIN D10
#define SEGMENT_D_PIN D4
#define SEGMENT_E_PIN D5
#define SEGMENT_F_PIN D6
#define SEGMENT_G_PIN D7


#define DIGIT_1_PIN D2
#define DIGIT_2_PIN D8

#define BUTTON_PIN D1
#define LIGHT_SENSOR_PIN A0


#define REQUEST_DELAY 330UL // 3 times per second
unsigned long request_timer = millis();

#define MAX_RESPONSE_DELAY 2000UL
unsigned long max_response_timer = millis();

#define FAST_BLINK_DELAY 100UL // for alerts
#define SLOW_BLINK_DELAY 1000UL // for connection lost
unsigned long blink_timer = millis();
bool blink_toggle = false;
bool blink_enabled = true;
int blink_delay = SLOW_BLINK_DELAY;

#define BRIGHTNESS_DELAY 250 

#define BRIGHTNESS_MIN -20 //-200 min
#define BRIGHTNESS_MID 0
#define BRIGHTNESS_MAX 150 // 200 max
#define BRIGHTNESS_MIN_ANALOG 3000 // 4095 max, decrease if too bright
#define BRIGHTNESS_MID_ANALOG 50  // decrease if too bright
#define BRIGHTNESS_MAX_ANALOG 10 // 0 min, decrease if too bright

unsigned long brightness_timer = millis();
int current_brightness = 0;

#define BUTTON_PRESSED_SHORT 50UL
#define BUTTON_PRESSED_LONG 3000UL // deep sleep
unsigned long button_pressed_timer = millis();

#define AUTO_SLEEP_TIME 600000UL //10 minutes
unsigned long auto_sleep_timer = millis();

#define SELECTED_CHAR_DELAY 1000UL // how long 2-character value symbol is displayed when switched
unsigned long selected_char_timer = millis();
bool display_selected_char = true;

void setup(){
  //Serial.begin(115200);

  byte numDigits = 2;
  byte digitPins[] = {DIGIT_1_PIN, DIGIT_2_PIN};
  byte segmentPins[] = {SEGMENT_A_PIN,SEGMENT_B_PIN,SEGMENT_C_PIN,SEGMENT_D_PIN,SEGMENT_E_PIN,SEGMENT_F_PIN,SEGMENT_G_PIN};
  bool resistorsOnSegments = true; // 'false' means resistors are on digit pins
  byte hardwareConfig = COMMON_ANODE; 
  bool updateWithDelays = false;
  bool leadingZeros = false; 
  bool disableDecPoint = true; 
  sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments, updateWithDelays, leadingZeros, disableDecPoint);
  sevseg.setBrightness(current_brightness);
  pinMode(BUTTON_PIN, INPUT);

  esp_deep_sleep_enable_gpio_wakeup((1ULL << 3), ESP_GPIO_WAKEUP_GPIO_LOW);

  WiFi.begin(ssid, password);

  request.onReadyStateChange(requestCallBack);

  // init sequence - blink fast to indicate restart
  enableFastBlink();
  unsigned long init_timer = millis();
  bool ip_not_set = true;
  while(millis() - init_timer < 1000UL || ip_not_set){
    handleBlinkActions();
    sevseg.refreshDisplay();
	if(checkButtonPressed()){
      button_pressed_timer = millis();
      while(checkButtonPressed()){
        sevseg.setChars("--");
        sevseg.refreshDisplay();
      }
      if(millis() - button_pressed_timer > BUTTON_PRESSED_LONG){
        goToDeepSleep();
      }
    }
    if(millis() - auto_sleep_timer > AUTO_SLEEP_TIME){ //
        goToDeepSleep();
    }
    if (ip_not_set){
      String url_ip = WiFi.gatewayIP().toString();
      if(url_ip.compareTo("0.0.0.0") != 0){
        ip_not_set = false;
        url = "http://" + url_ip + url_path;
      }
    }
  }
  strcpy(selected_char,BATTERY_CHAR);
  displaySelectedChar();
}

void loop(){
    handleButtonActions();
    handleSelectedKeyCharExpiration(); // selected value characters are displayed for 1s after switched
    handleBlinkActions();
    handleRequestActions();
    handleTimeoutActions();
    handleBrightnessActions();
    sevseg.refreshDisplay();
}

bool checkButtonPressed(){
  if(digitalRead(D1) == LOW){
    return true;
  } else {
    return false;
  }
}

void handleButtonActions(){
  // button actions
  if(checkButtonPressed()){
    button_pressed_timer = millis();
    while(checkButtonPressed()){
      sevseg.setChars("--");
      sevseg.refreshDisplay();
    }
    if(millis() - button_pressed_timer > BUTTON_PRESSED_LONG){
      goToDeepSleep();
    } else if(millis() - button_pressed_timer > BUTTON_PRESSED_SHORT){
      toggleDisplayMode();
      displaySelectedChar();
    } else {
      displaySelectedCharOrNum();
    }
  }
}

void goToDeepSleep(){
  sevseg.blank();
  sevseg.refreshDisplay();

  delay(100);
  esp_deep_sleep_start();
}

void toggleDisplayMode(){
  if(display_mode <= 3){
    display_mode = display_mode + 1;
  } else {
    display_mode = 1;
  }

  if(display_mode == 1){
    selected_num = battery_num;
    selected_key = BATTERY_KEY;
    strcpy(selected_char, BATTERY_CHAR);

  } else if(display_mode == 2){
    selected_num = speed_num;
    selected_key = SPEED_KEY;
    strcpy(selected_char, SPEED_CHAR);

  } else if(display_mode == 3){
    selected_num = temperature_num;
    selected_key = TEMPERATURE_KEY;
    strcpy(selected_char, TEMPERATURE_CHAR);

  } else if(display_mode == 4){
    selected_num = safety_margin_num;
    selected_key = SAFETY_MARGIN_KEY;
    strcpy(selected_char, SAFETY_MARGIN_CHAR);

  }
}

void displaySelectedChar(){
  selected_char_timer = millis();
  display_selected_char = true;
  request_error = false; //reset error flag
  sevseg.setChars(selected_char);
  disableBlink();
}

void displaySelectedCharOrNum(){
  if(display_selected_char){
    sevseg.setChars(selected_char);
  } else {
    sevseg.setNumber(selected_num);
  }
}

void handleSelectedKeyCharExpiration(){
  if(display_selected_char){
    if(millis() - selected_char_timer > SELECTED_CHAR_DELAY){
      display_selected_char = false;
    }
  } 
}

void handleBlinkActions(){
  if(blink_enabled){
    if(millis() - blink_timer > blink_delay){
        blink_timer = millis();
        blink_toggle = !blink_toggle;
        if(blink_toggle){
          sevseg.blank();
        } else {
          displaySelectedCharOrNum();
        }
    }
  } 
}

void handleTimeoutActions(){
  //handle timeout 
  unsigned long current_time = millis();
  unsigned long max_response_timer_diff = current_time - max_response_timer;

  // additional check to avoid int overflow issues when max_response_timer > current_time (happens sometimes)
  if(max_response_timer_diff > MAX_RESPONSE_DELAY && current_time > max_response_timer){ 
    if(!request_error && !display_selected_char){
      request_error = true;
      enableSlowBlink(); // enable only if not already running
    }
    if(millis() - auto_sleep_timer > AUTO_SLEEP_TIME){ //
      goToDeepSleep();
    }
  } else {
    request_error = false;
    auto_sleep_timer = millis();
  }
}

void handleBrightnessActions(){
  if(millis() - brightness_timer > BRIGHTNESS_DELAY){
      brightness_timer = millis();
      calculateBrightness();
  }
}

void handleRequestActions(){
  if(millis() - request_timer > REQUEST_DELAY){
    request_timer = millis();
    if(WiFi.status() == WL_CONNECTED){ 
      sendRequest();
    } 
  }
}

void enableFastBlink(){
  enableBlink(FAST_BLINK_DELAY);
}

void enableSlowBlink(){
  enableBlink(SLOW_BLINK_DELAY);
}

void enableBlink(int delay){
  blink_delay = delay;
  blink_enabled = true;
  blink_toggle = true; //blank
  sevseg.blank();
  blink_timer = millis();
}

void disableBlink(){
  blink_enabled = false;
  blink_toggle = false;
}

void calculateBrightness(){
  int analog_reading = analogRead(A0);
  analog_reading = MIN(BRIGHTNESS_MIN_ANALOG, analog_reading);
  analog_reading = MAX(BRIGHTNESS_MAX_ANALOG, analog_reading);

  int tmp_brightness = 0;
  if(analog_reading < BRIGHTNESS_MID_ANALOG){
    //day mode
    tmp_brightness = map(analog_reading, BRIGHTNESS_MID_ANALOG, BRIGHTNESS_MAX_ANALOG, BRIGHTNESS_MID, BRIGHTNESS_MAX);
  } else {
    //night mode
    tmp_brightness = map(analog_reading, BRIGHTNESS_MIN_ANALOG, BRIGHTNESS_MID_ANALOG, BRIGHTNESS_MIN, BRIGHTNESS_MID);
  }
  
  if(tmp_brightness > current_brightness){
    current_brightness = current_brightness + 1;
  } else if(tmp_brightness < current_brightness){
    current_brightness = current_brightness - 1;
  }

  sevseg.setBrightness(current_brightness);
}

void sendRequest(){
  static bool requestOpenResult;
  
  if (request.readyState() == readyStateUnsent || request.readyState() == readyStateDone)  {
    
    requestOpenResult = request.open("GET", url.c_str());
    if (requestOpenResult){
      request.send();
    } 
  }  
}

void requestCallBack(void *optParm, AsyncHTTPRequest *request, int readyState){
  (void) optParm;
  if (readyState == readyStateDone)  {
    if (request->responseHTTPcode() == 200)    {
      String http_response = request->responseText();
      deserializeJson(json_doc, http_response);
      
      bool valid_response = true;
      bool alert = false;

      bool valid_temperature = json_doc[TEMPERATURE_KEY]["d"];
      bool valid_speed = json_doc[SPEED_KEY]["d"];
      bool valid_battery = json_doc[BATTERY_KEY]["d"];
      bool valid_safety_margin = json_doc[SAFETY_MARGIN_KEY]["d"];

      if(valid_temperature){
        temperature_num = json_doc[TEMPERATURE_KEY]["v"];
      } else {
        valid_response = false;
      }

      if(valid_speed){
        speed_num = json_doc[SPEED_KEY]["v"];
      } else {
        valid_response = false;
      }

      if(valid_battery){
        battery_num = int(json_doc[BATTERY_KEY]["v"]);
      } else {
        valid_response = false;
      }

      if(valid_safety_margin){
        safety_margin_num = json_doc[SAFETY_MARGIN_KEY]["v"];
      } else {
        valid_response = false;
      }

      if(valid_response){
        max_response_timer = millis();
        // check alerts here:
  
        if(speed_num > SPEED_ALERT){
          alert = true;
        } else if(temperature_num > TEMPERATURE_ALERT){
          alert = true;
        } else if(battery_num < BATTERY_ALERT){
          alert = true;
        } else if(speed_num > BATTERY_SPEED_ALERT_3_SPEED && battery_num < BATTERY_SPEED_ALERT_3_BATTERY){
          alert = true;
        } else if(speed_num > BATTERY_SPEED_ALERT_2_SPEED && battery_num < BATTERY_SPEED_ALERT_2_BATTERY){
          alert = true;
        } else if(speed_num > BATTERY_SPEED_ALERT_1_SPEED && battery_num < BATTERY_SPEED_ALERT_1_BATTERY){
          alert = true; 
        } else if (safety_margin_num < SAFETY_MARIGN_ALERT){
          alert = true; 
        }

        //value to be finally displayed
        if(display_mode == 1){
          selected_num = battery_num;
        } else if(display_mode == 2){
          selected_num = speed_num;
        } else if(display_mode == 3){
          selected_num = temperature_num;
        } else if(display_mode == 4){
          selected_num = safety_margin_num;
        }

        selected_num = MIN(selected_num, 99); //numbers above 99 cannot be displayed on 2 digit display anyway

        if(alert){
          enableFastBlink();
        } else if(blink_enabled){
          //if there was no valid response there can be already timeout blinking enabled
          disableBlink();
        }

        //display selected number immediately if not blinking and showing number before
        if(!blink_toggle && !display_selected_char){
          sevseg.setNumber(selected_num);
        }
      } 
    }
  }
}
