#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <esp_sleep.h>
#else
#error "This sketch supports ESP8266 and ESP32 boards only."
#endif

const int PIN_COMMON_ELECTRODE = 13;  // GPIO13 / D7
const int PIN_LEVEL1 = 5;             // GPIO5  / D1 - najnizsia hladina
const int PIN_LEVEL2 = 4;             // GPIO4  / D2
const int PIN_LEVEL3 = 14;            // GPIO14 / D5
const int PIN_LEVEL4 = 12;            // GPIO12 / D6 - najvyssia hladina

const int DEBUG = 0;

const char WIFI_SSID[] = "TP-Link_DD1E";
const char WIFI_PASS[] = "33784120";
const unsigned long SLEEP_TIME_SEC = 1800;
const char URL_LOG[] = "https://vihorlat.tpsoft.org/hladinac/server/log.php";

const unsigned long WIFI_TIMEOUT_MS = 15000;
const unsigned long MEASUREMENT_STABILIZE_MS = 50;

struct WaterLevels {
  int level1;
  int level2;
  int level3;
  int level4;
};

void initPins();
WaterLevels readLevels();
void printLevels(const WaterLevels &levels);
String buildPostBody(const WaterLevels &levels);
bool connectWifi();
bool sendLevelsToServer(const WaterLevels &levels);
void enterDeepSleep();
void setupLevelPin(int pin);
void setCommonElectrodeOn();
void setCommonElectrodeOff();
int readLevelPin(int pin);

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("Hladinac v0.3 started.");

  initPins();

  if (DEBUG == 0) {
    WaterLevels levels = readLevels();
    printLevels(levels);

    if (connectWifi()) {
      sendLevelsToServer(levels);
      WiFi.disconnect(true);
      WiFi.mode(WIFI_OFF);
    } else {
      Serial.println("WiFi connection failed, skipping upload.");
    }

    enterDeepSleep();
  } else if (DEBUG == 2) {
    connectWifi();
  }
}

void loop() {
  if (DEBUG == 1) {
    WaterLevels levels = readLevels();
    printLevels(levels);
    delay(1000);
  } else if (DEBUG == 2) {
    WaterLevels levels = readLevels();
    printLevels(levels);

    if (WiFi.status() != WL_CONNECTED) {
      connectWifi();
    }

    sendLevelsToServer(levels);
    delay(5000);
  }
}

void initPins() {
  pinMode(PIN_COMMON_ELECTRODE, OUTPUT);
  setCommonElectrodeOff();

  setupLevelPin(PIN_LEVEL1);
  setupLevelPin(PIN_LEVEL2);
  setupLevelPin(PIN_LEVEL3);
  setupLevelPin(PIN_LEVEL4);
}

void setupLevelPin(int pin) {
#if defined(ESP32)
  pinMode(pin, INPUT_PULLDOWN);
#else
  // ESP8266 pouziva externe pull-down rezistory na GND.
  pinMode(pin, INPUT);
#endif
}

void setCommonElectrodeOn() {
#if defined(ESP8266)
  digitalWrite(PIN_COMMON_ELECTRODE, HIGH);
#else
  digitalWrite(PIN_COMMON_ELECTRODE, HIGH);
#endif
}

void setCommonElectrodeOff() {
#if defined(ESP8266)
  digitalWrite(PIN_COMMON_ELECTRODE, LOW);
#else
  digitalWrite(PIN_COMMON_ELECTRODE, LOW);
#endif
}

int readLevelPin(int pin) {
#if defined(ESP8266)
  return digitalRead(pin) == HIGH ? 1 : 0;
#else
  return digitalRead(pin) == HIGH ? 1 : 0;
#endif
}

WaterLevels readLevels() {
  WaterLevels levels;

  // Spolocna elektroda je zapnuta iba pocas merania, aby sa znizila korozia.
  setCommonElectrodeOn();
  delay(MEASUREMENT_STABILIZE_MS);

  levels.level1 = readLevelPin(PIN_LEVEL1);
  levels.level2 = readLevelPin(PIN_LEVEL2);
  levels.level3 = readLevelPin(PIN_LEVEL3);
  levels.level4 = readLevelPin(PIN_LEVEL4);

  setCommonElectrodeOff();

  return levels;
}

void printLevels(const WaterLevels &levels) {
  Serial.print("level1=");
  Serial.print(levels.level1);
  Serial.print(" level2=");
  Serial.print(levels.level2);
  Serial.print(" level3=");
  Serial.print(levels.level3);
  Serial.print(" level4=");
  Serial.println(levels.level4);
}

String buildPostBody(const WaterLevels &levels) {
  String body = "level1=" + String(levels.level1);
  body += "&level2=" + String(levels.level2);
  body += "&level3=" + String(levels.level3);
  body += "&level4=" + String(levels.level4);
  return body;
}

bool connectWifi() {
  if (WiFi.status() == WL_CONNECTED) {
    return true;
  }

  Serial.print("Connecting to WiFi");
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  unsigned long startedAt = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startedAt < WIFI_TIMEOUT_MS) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println();
    Serial.print("WiFi connected, IP=");
    Serial.println(WiFi.localIP());
    return true;
  }

  Serial.println();
  Serial.println("WiFi connection timeout.");
  return false;
}

bool sendLevelsToServer(const WaterLevels &levels) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Cannot send data, WiFi is not connected.");
    return false;
  }

#if defined(ESP8266)
  BearSSL::WiFiClientSecure client;
#else
  WiFiClientSecure client;
#endif
  client.setInsecure();

  HTTPClient http;
  if (!http.begin(client, URL_LOG)) {
    Serial.println("HTTP begin failed.");
    return false;
  }

  String body = buildPostBody(levels);
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");

  int httpCode = http.POST(body);
  if (httpCode > 0) {
    Serial.print("POST sent, HTTP code=");
    Serial.println(httpCode);
  } else {
    Serial.print("POST failed: ");
    Serial.println(http.errorToString(httpCode));
  }

  http.end();
  return httpCode > 0 && httpCode < 400;
}

void enterDeepSleep() {
  Serial.print("Entering deep sleep for ");
  Serial.print(SLEEP_TIME_SEC);
  Serial.println(" seconds.");
  Serial.flush();

#if defined(ESP8266)
  ESP.deepSleep((uint64_t)SLEEP_TIME_SEC * 1000000ULL);
#else
  esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_TIME_SEC * 1000000ULL);
  esp_deep_sleep_start();
#endif
}
