#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>

// Define pins
#define TRIGGER_PIN 9  // Trigger pin for RCWL-1655
#define ECHO_PIN 10    // Echo pin for RCWL-1655
#define RELAY_PIN 14   // Relay pin for pump control

// WiFi credentials
const char* ssid = "YourSSID";
const char* password = "YourPassword";

// Calibration variables
int maxWaterLevel = 0; // Maximum level in cm (set during calibration)
int minWaterLevel = 0; // Minimum level in cm (set during calibration)

// Variables
long pumpStartTime = 0;
long pumpActiveTime = 0;
int currentWaterLevel = 0; // Current water level in %
bool pumpState = false;    // Pump initially off

Preferences preferences; // Preferences object for saving data

// Web page HTML content
static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<!doctype html>
<html>
  <head>
    <title>Water Tank Monitor</title>
    <style>
      body { font-family: Arial, sans-serif; text-align: center; margin: 20px; }
      h1 { color: #333; }
      p { font-size: 18px; }
      button { padding: 10px 20px; font-size: 16px; cursor: pointer; margin: 10px; }
    </style>
  </head>
  <body>
    <h1>Water Tank Monitor</h1>
    <p>Current Water Level: <span id="waterLevel">--</span>%</p>
    <p>Total Water Usage: <span id="waterUsage">--</span> liters</p>
    <button onclick="calibrate()">Recalibrate</button>
    <button onclick="togglePump()">Toggle Pump</button>
    <script>
      async function updateData() {
        const response = await fetch('/data');
        const data = await response.json();
        document.getElementById('waterLevel').innerText = data.waterLevel;
        document.getElementById('waterUsage').innerText = data.waterUsage.toFixed(2);
      }
      async function calibrate() {
        await fetch('/calibrate');
        updateData();
      }
      async function togglePump() {
        await fetch('/toggle');
        updateData();
      }
      setInterval(updateData, 1000); // Update data every second
      updateData(); // Initial data load
    </script>
  </body>
</html>
)rawliteral";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Calculate water usage (arbitrary multiplier for liters based on pump time)
float calculateWaterUsage() {
  return (pumpActiveTime / 1000.0) * 0.5; // Example: 0.5 liters per second
}

// Read water level from RCWL-1655
int readRawDistance() {
  digitalWrite(TRIGGER_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);

  long duration = pulseIn(ECHO_PIN, HIGH);
  return duration / 58; // Convert pulse width to distance in cm
}

// Convert raw distance to percentage based on calibration
int calculateWaterLevelPercentage(int rawDistance) {
  if (rawDistance <= maxWaterLevel) return 100;
  if (rawDistance >= minWaterLevel) return 0;
  return map(rawDistance, maxWaterLevel, minWaterLevel, 100, 0);
}

// Control the pump based on the water level
void controlPump() {
  if (pumpState) {
    // Pump is manually turned on
    digitalWrite(RELAY_PIN, HIGH);
  } else if (currentWaterLevel <= 10) {
    // Automatically turn off pump if near max
    digitalWrite(RELAY_PIN, LOW);
  } else if (currentWaterLevel >= 90) {
    // Automatically turn on pump if near min
    digitalWrite(RELAY_PIN, HIGH);
  }

  // Update pump active time
  if (digitalRead(RELAY_PIN) == HIGH) {
    if (pumpStartTime == 0) pumpStartTime = millis();
  } else {
    if (pumpStartTime != 0) {
      pumpActiveTime += millis() - pumpStartTime;
      pumpStartTime = 0;
    }
  }
}

void saveCalibration() {
  preferences.begin("water-tank", false); // Open preferences with namespace "water-tank"
  preferences.putInt("maxLevel", maxWaterLevel);
  preferences.putInt("minLevel", minWaterLevel);
  preferences.end();
}

void loadCalibration() {
  preferences.begin("water-tank", true); // Open preferences for read-only
  maxWaterLevel = preferences.getInt("maxLevel", 0);
  minWaterLevel = preferences.getInt("minLevel", 0);
  preferences.end();

  // Set default values if not calibrated
  if (maxWaterLevel == 0 && minWaterLevel == 0) {
    maxWaterLevel = 20; // Default max level in cm
    minWaterLevel = 40; // Default min level in cm
  }
}

void setup() {
  // Pin configurations
  pinMode(TRIGGER_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Ensure the relay is off

  // Serial Monitor
  Serial.begin(115200);

  // Load calibration data
  loadCalibration();

  // WiFi Setup
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
  Serial.println(WiFi.localIP());

  // Web Server Routes
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", INDEX_HTML);
  });

  server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) {
    String json = "{";
    json += "\"waterLevel\":" + String(currentWaterLevel) + ",";
    json += "\"waterUsage\":" + String(calculateWaterUsage());
    json += "}";
    request->send(200, "application/json", json);
  });

  server.on("/calibrate", HTTP_GET, [](AsyncWebServerRequest *request) {
    maxWaterLevel = readRawDistance();
    minWaterLevel = maxWaterLevel + 20; // Example: Tank depth is 20 cm
    saveCalibration();
    request->send(200, "text/plain", "Calibration complete");
  });

  server.on("/toggle", HTTP_GET, [](AsyncWebServerRequest *request) {
    pumpState = !pumpState;
    request->send(200, "text/plain", "Pump state toggled");
  });

  // Start server
  server.begin();
}

void loop() {
  // Read current water level
  int rawDistance = readRawDistance();
  currentWaterLevel = calculateWaterLevelPercentage(rawDistance);

  // Control the pump based on water level and state
  controlPump();

  // Debugging
  Serial.print("Raw Distance: ");
  Serial.print(rawDistance);
  Serial.println(" cm");
  Serial.print("Water Level: ");
  Serial.print(currentWaterLevel);
  Serial.println(" %");
  Serial.print("Water Usage: ");
  Serial.print(calculateWaterUsage());
  Serial.println(" liters");

  delay(1000); // Wait 1 second
}