/*
  ESP32 + HC-SR04 tank monitor

  Wiring used by this sketch:
    HC-SR04 VCC  -> 5V
    HC-SR04 GND  -> GND
    HC-SR04 TRIG -> ESP32 GPIO 5
    HC-SR04 ECHO -> ESP32 GPIO 18 through a voltage divider

  Important: HC-SR04 Echo is normally 5V. ESP32 GPIO is 3.3V only.
  Use a voltage divider, for example 1 kOhm from Echo to GPIO and
  2 kOhm from GPIO to GND, or another divider that brings Echo near 3.3V.
*/

#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>

// ---------- Wi-Fi ----------
const char* WIFI_SSID = "NOME_DA_REDE";
const char* WIFI_PASS = "PASSWORD_DA_REDE";

// ---------- API ----------
const char* API_URL = "https://teu-dominio.pt/api/nivel.php";
const char* API_KEY = "!?WLDJIE3894lf_!?";
const char* DEVICE_ID = "VLCI 01";

// ---------- Sonar ----------
const int TRIG_PIN = 5;
const int ECHO_PIN = 18;

// Calibracao do deposito, em centimetros.
// EMPTY_DISTANCE_CM: distancia sensor -> agua quando o deposito esta vazio.
// FULL_DISTANCE_CM: distancia sensor -> agua quando o deposito esta cheio.
const float EMPTY_DISTANCE_CM = 120.0;
const float FULL_DISTANCE_CM = 20.0;

const unsigned long READ_INTERVAL_MS = 60000;
const unsigned long WIFI_RETRY_MS = 10000;
const unsigned long PULSE_TIMEOUT_US = 35000;

unsigned long lastReadAt = 0;
unsigned long lastWifiAttemptAt = 0;
int lastPercent = -1;
String lastNivel = "";

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  digitalWrite(TRIG_PIN, LOW);

  connectWiFi();
  lastReadAt = millis() - READ_INTERVAL_MS;
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    if (millis() - lastWifiAttemptAt >= WIFI_RETRY_MS) {
      connectWiFi();
    }
    return;
  }

  if (millis() - lastReadAt < READ_INTERVAL_MS) {
    return;
  }
  lastReadAt = millis();

  float distance = readMedianDistanceCm();
  if (distance < 0) {
    Serial.println("Leitura sonar invalida.");
    return;
  }

  int percent = distanceToPercent(distance);
  String nivel = percentToNivel(percent);

  bool changedEnough = abs(percent - lastPercent) >= 3 || nivel != lastNivel;
  if (lastPercent < 0 || changedEnough) {
    if (sendReading(distance, percent, nivel)) {
      lastPercent = percent;
      lastNivel = nivel;
    }
  }

  Serial.printf("Distancia: %.1f cm | Percentagem: %d%% | Nivel: %s\n",
                distance, percent, nivel.c_str());
}

void connectWiFi() {
  lastWifiAttemptAt = millis();
  Serial.printf("A ligar ao Wi-Fi: %s\n", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

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

  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Wi-Fi ligado. IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("Falha Wi-Fi. Vou tentar novamente.");
  }
}

float readDistanceCm() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  unsigned long duration = pulseIn(ECHO_PIN, HIGH, PULSE_TIMEOUT_US);
  if (duration == 0) {
    return -1.0;
  }

  return duration / 58.0;
}

float readMedianDistanceCm() {
  const int sampleCount = 7;
  float samples[sampleCount];
  int valid = 0;

  for (int i = 0; i < sampleCount; i++) {
    float d = readDistanceCm();
    if (d > 1 && d < 500) {
      samples[valid++] = d;
    }
    delay(60);
  }

  if (valid == 0) {
    return -1.0;
  }

  for (int i = 0; i < valid - 1; i++) {
    for (int j = i + 1; j < valid; j++) {
      if (samples[j] < samples[i]) {
        float tmp = samples[i];
        samples[i] = samples[j];
        samples[j] = tmp;
      }
    }
  }

  return samples[valid / 2];
}

int distanceToPercent(float distanceCm) {
  float span = EMPTY_DISTANCE_CM - FULL_DISTANCE_CM;
  if (span <= 0) {
    return 0;
  }

  float percent = ((EMPTY_DISTANCE_CM - distanceCm) / span) * 100.0;
  if (percent < 0) percent = 0;
  if (percent > 100) percent = 100;
  return (int)(percent + 0.5);
}

String percentToNivel(int percent) {
  if (percent <= 25) return "vazio";
  if (percent < 75) return "meio";
  return "cheio";
}

bool sendReading(float distanceCm, int percent, const String& nivel) {
  HTTPClient http;
  WiFiClient wifiClient;
  WiFiClientSecure secureClient;

  String url = String(API_URL);
  bool https = url.startsWith("https://");
  bool started = false;

  if (https) {
    secureClient.setInsecure();
    started = http.begin(secureClient, url);
  } else {
    started = http.begin(wifiClient, url);
  }

  if (!started) {
    Serial.println("Nao consegui iniciar HTTP.");
    return false;
  }

  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-API-KEY", API_KEY);

  String payload = "{";
  payload += "\"device_id\":\"" + String(DEVICE_ID) + "\",";
  payload += "\"nivel\":\"" + nivel + "\",";
  payload += "\"distancia_cm\":" + String(distanceCm, 1) + ",";
  payload += "\"percentagem\":" + String(percent);
  payload += "}";

  int code = http.POST(payload);
  String response = http.getString();
  http.end();

  Serial.printf("POST %d: %s\n", code, response.c_str());
  return code >= 200 && code < 300;
}
