#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <ESP32Servo.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <NewPing.h>

// Uncomment the following line to enable distance printing on OLED and Serial
//#define SHOW_DISTANCE
//#define USE_STA_MODE

// ===== WiFi Credentials =====
const char* ssid = "jai9";
const char* password = "a1234567";

// ===== Pin Definitions =====
// Four parking slot sensors (HC-SR04 with trigger/echo tied together)
const int slot1Pin = 5;
const int slot2Pin = 4;
const int slot3Pin = 2;
const int slot4Pin = 1;

// Two additional sensors for detecting vehicles at the entrance and exit
const int sensorInPin = 46;   // Entrance sensor
const int sensorOutPin = 44;  // Exit sensor

// Servo pins for barrier control (SG90 servos)
const int servoInPin = 47;   // Entrance barrier servo
const int servoOutPin = 45;  // Exit barrier servo

// ===== OLED Display Settings =====
#define SCREEN_WIDTH 128  // OLED display width in pixels
#define SCREEN_HEIGHT 64  // OLED display height in pixels
#define OLED_RESET -1     // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ===== NewPing Settings =====
#define MAX_DISTANCE 200  // Maximum distance (in centimeters) to ping

// ===== Thresholds & Timing =====
const float parkingThreshold = 15.0;             // in centimeters; if measured distance < threshold, slot is "Occupied"
const float sensorThreshold = 15.0;              // in centimeters; for entrance/exit detection
const unsigned long barrierOpenDuration = 3000;  // milliseconds barrier remains open

// ===== Global Variables =====

bool allFull = 0;

// Parking slot occupancy status
bool slot1Occupied = false;
bool slot2Occupied = false;
bool slot3Occupied = false;
bool slot4Occupied = false;

// Barrier state variables
bool barrierInActive = false;   // Entrance barrier state
bool barrierOutActive = false;  // Exit barrier state
unsigned long barrierInStartTime = 0;
unsigned long barrierOutStartTime = 0;

// Measured distances (in cm)
float distanceSlot1 = 0;
float distanceSlot2 = 0;
float distanceSlot3 = 0;
float distanceSlot4 = 0;
float distanceIn = 0;
float distanceOut = 0;

// Create servo objects for the barriers
Servo servoIn;
Servo servoOut;

// Create a web server on port 80
WebServer server(80);

// ----- NewPing Objects for each ultrasonic sensor -----
// For sensors wired with a single pin for both trigger and echo, pass the same pin twice.
NewPing sonarSlot1(slot1Pin, slot1Pin, MAX_DISTANCE);
NewPing sonarSlot2(slot2Pin, slot2Pin, MAX_DISTANCE);
NewPing sonarSlot3(slot3Pin, slot3Pin, MAX_DISTANCE);
NewPing sonarSlot4(slot4Pin, slot4Pin, MAX_DISTANCE);
NewPing sonarIn(sensorInPin, sensorInPin, MAX_DISTANCE);
NewPing sonarOut(sensorOutPin, sensorOutPin, MAX_DISTANCE);

// ===== Function Prototypes =====
void updateDisplay();
void handleRoot();
void handleData();
void handleOpenEntrance();
void handleOpenExit();

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

  // --- Initialize I2C for OLED display (SDA: GPIO21, SCL: GPIO14) ---
  Wire.begin(21, 14);

  // Initialize OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {  // Address 0x3C for most OLEDs
    Serial.println("SSD1306 allocation failed");
    for (;;)
      ;
  }
  display.clearDisplay();
  display.display();

  // --- Initialize sensor pins (set as INPUT initially) ---
  pinMode(slot1Pin, INPUT);
  pinMode(slot2Pin, INPUT);
  pinMode(slot3Pin, INPUT);
  pinMode(slot4Pin, INPUT);
  pinMode(sensorInPin, INPUT);
  pinMode(sensorOutPin, INPUT);

  // --- Attach servos and set initial (closed) positions (0°) ---
  servoIn.attach(servoInPin);
  servoOut.attach(servoOutPin);
  servoIn.write(90);
  servoOut.write(90);

#ifdef USE_STA_MODE
  Serial.println("Starting in WiFi STA mode...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("Connected! IP address: ");
  Serial.println(WiFi.localIP());
#else
  Serial.println("Starting in AP mode...");
  WiFi.mode(WIFI_AP);
  WiFi.softAP("Smart Car Parking");
  Serial.print("AP IP address: ");
  Serial.println(WiFi.softAPIP());
#endif

  // --- Start mDNS (optional) ---
  if (!MDNS.begin("app")) {
    Serial.println("Error setting up mDNS responder!");
  } else {
    Serial.println("mDNS responder started as aiot.local");
  }

  // --- Define web server routes ---
  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.on("/openEntrance", handleOpenEntrance);  // Route for manual entrance open
  server.on("/openExit", handleOpenExit);          // Route for manual exit open

  server.begin();
  Serial.println("Web server started");
}

void loop() {
  // ----- Update Parking Slot Statuses using NewPing -----
  distanceSlot1 = sonarSlot1.ping_cm();
  distanceSlot2 = sonarSlot2.ping_cm();
  distanceSlot3 = sonarSlot3.ping_cm();
  distanceSlot4 = sonarSlot4.ping_cm();

  // Mark as "Occupied" if a valid (nonzero) distance is read and it is less than the threshold.
  slot1Occupied = (distanceSlot1 > 0 && distanceSlot1 < parkingThreshold);
  slot2Occupied = (distanceSlot2 > 0 && distanceSlot2 < parkingThreshold);
  slot3Occupied = (distanceSlot3 > 0 && distanceSlot3 < parkingThreshold);
  slot4Occupied = (distanceSlot4 > 0 && distanceSlot4 < parkingThreshold);

  // ----- Entrance Barrier Handling -----
  distanceIn = sonarIn.ping_cm();
  if (!barrierInActive && (distanceIn > 0 && distanceIn < sensorThreshold)) {
    barrierInActive = true;
    barrierInStartTime = millis();
    if (!allFull) servoIn.write(0);  // Open the entrance barrier
    Serial.println("Entrance Barrier: Opened");
  }
  if (barrierInActive && (millis() - barrierInStartTime >= barrierOpenDuration)) {
    servoIn.write(90);  // Close the entrance barrier
    barrierInActive = false;
    Serial.println("Entrance Barrier: Closed");
  }

  // ----- Exit Barrier Handling -----
  distanceOut = sonarOut.ping_cm();
  if (!barrierOutActive && (distanceOut > 0 && distanceOut < sensorThreshold)) {
    barrierOutActive = true;
    barrierOutStartTime = millis();
    servoOut.write(0);  // Open the exit barrier
    Serial.println("Exit Barrier: Opened");
  }
  if (barrierOutActive && (millis() - barrierOutStartTime >= barrierOpenDuration)) {
    servoOut.write(90);  // Close the exit barrier
    barrierOutActive = false;
    Serial.println("Exit Barrier: Closed");
  }

  // --- Update the OLED display with parking slot statuses and distances ---
  updateDisplay();

#ifdef SHOW_DISTANCE
  // Optionally print distances to Serial Monitor
  Serial.print("Slot1: ");
  Serial.print(distanceSlot1 > 0 ? distanceSlot1 : 0);
  Serial.print(" cm, Slot2: ");
  Serial.print(distanceSlot2 > 0 ? distanceSlot2 : 0);
  Serial.print(" cm, Slot3: ");
  Serial.print(distanceSlot3 > 0 ? distanceSlot3 : 0);
  Serial.print(" cm, Slot4: ");
  Serial.print(distanceSlot4 > 0 ? distanceSlot4 : 0);
  Serial.print(" cm, Entrance: ");
  Serial.print(distanceIn > 0 ? distanceIn : 0);
  Serial.print(" cm, Exit: ");
  Serial.println(distanceOut > 0 ? distanceOut : 0);
#endif

  // --- Handle incoming client requests ---
  server.handleClient();
  delay(100);
}

// ----- Function: Update OLED Display -----
// Displays the occupancy status and (if enabled) the measured distance for each sensor,
// then prints a summary line showing if all parking slots are full or if there is availability.
void updateDisplay() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Parking Status:");

  // Slot 1
  display.print("Slot 1: ");
  display.print(slot1Occupied ? "Occupied" : "Available");
#ifdef SHOW_DISTANCE
  display.print(" (");
  display.print(distanceSlot1 > 0 ? distanceSlot1 : 0);
  display.println(" cm)");
#else
  display.println();
#endif

  // Slot 2
  display.print("Slot 2: ");
  display.print(slot2Occupied ? "Occupied" : "Available");
#ifdef SHOW_DISTANCE
  display.print(" (");
  display.print(distanceSlot2 > 0 ? distanceSlot2 : 0);
  display.println(" cm)");
#else
  display.println();
#endif

  // Slot 3
  display.print("Slot 3: ");
  display.print(slot3Occupied ? "Occupied" : "Available");
#ifdef SHOW_DISTANCE
  display.print(" (");
  display.print(distanceSlot3 > 0 ? distanceSlot3 : 0);
  display.println(" cm)");
#else
  display.println();
#endif

  // Slot 4
  display.print("Slot 4: ");
  display.print(slot4Occupied ? "Occupied" : "Available");
#ifdef SHOW_DISTANCE
  display.print(" (");
  display.print(distanceSlot4 > 0 ? distanceSlot4 : 0);
  display.println(" cm)");
#else
  display.println();
#endif

#ifdef SHOW_DISTANCE
  // Entrance and exit sensor distances
  display.println();
  display.print("Entrance: ");
  display.print(distanceIn > 0 ? distanceIn : 0);
  display.print(" cm  Exit: ");
  display.print(distanceOut > 0 ? distanceOut : 0);
  display.println(" cm");
#endif

  // Summary line: show if all parking slots are full or available.
  allFull = slot1Occupied && slot2Occupied && slot3Occupied && slot4Occupied;
  display.println();
  display.print("Parking Slots: ");
  display.println(allFull ? "Full" : "Available");

  display.display();
}

// ----- Web Server Handler: Mobile-Friendly Root Page -----
// Contains two buttons for manually opening the entrance and exit barriers.
void handleRoot() {
  String html = "<!DOCTYPE html><html lang='en'>"
                "<head>"
                "<meta charset='UTF-8'>"
                "<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
                "<title>Parking Status</title>"
                "<style>"
                "body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f2f2f2; }"
                ".container { padding: 10px; }"
                "h2 { text-align: center; margin: 10px 0; }"
                ".card { background: #fff; margin: 10px; padding: 15px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }"
                "p { margin: 5px 0; font-size: 1.2em; }"
                "button { padding: 10px 20px; font-size: 1em; margin: 5px; }"
                "</style>"
                "<script>"
                "function fetchData(){"
                "  var xhr = new XMLHttpRequest();"
                "  xhr.onreadystatechange = function(){"
                "    if(xhr.readyState == 4 && xhr.status == 200){"
                "      var data = JSON.parse(xhr.responseText);"
                "      document.getElementById('slot1').innerHTML = data.slot1;"
                "      document.getElementById('slot2').innerHTML = data.slot2;"
                "      document.getElementById('slot3').innerHTML = data.slot3;"
                "      document.getElementById('slot4').innerHTML = data.slot4;"
                "      document.getElementById('barrierIn').innerHTML = data.barrierIn;"
                "      document.getElementById('barrierOut').innerHTML = data.barrierOut;"
                "    }"
                "  };"
                "  xhr.open('GET', '/data', true);"
                "  xhr.send();"
                "}"
                "function sendCommand(endpoint){"
                "  var xhr = new XMLHttpRequest();"
                "  xhr.open('GET', endpoint, true);"
                "  xhr.send();"
                "}"
                "setInterval(fetchData, 2000);"
                "</script>"
                "</head>"
                "<body onload='fetchData()'>"
                "<div class='container'>"
                "  <h2>Parking Slot Status</h2>"
                "  <div class='card'>"
                "    <p>Slot 1: <span id='slot1'>Loading...</span></p>"
                "    <p>Slot 2: <span id='slot2'>Loading...</span></p>"
                "    <p>Slot 3: <span id='slot3'>Loading...</span></p>"
                "    <p>Slot 4: <span id='slot4'>Loading...</span></p>"
                "  </div>"
                "  <h2>Barriers</h2>"
                "  <div class='card'>"
                "    <p>Entrance Barrier: <span id='barrierIn'>Loading...</span></p>"
                "    <p>Exit Barrier: <span id='barrierOut'>Loading...</span></p>"
                "    <button onclick=\"sendCommand('/openEntrance')\">Open Entrance Barrier</button>"
                "    <button onclick=\"sendCommand('/openExit')\">Open Exit Barrier</button>"
                "  </div>"
                "</div>"
                "</body>"
                "</html>";

  server.send(200, "text/html", html);
}

// ----- Web Server Handler: Return Status Data as JSON -----
void handleData() {
  String json = "{";
  json += "\"slot1\":\"" + String(slot1Occupied ? "Occupied" : "Available") + "\",";
  json += "\"slot2\":\"" + String(slot2Occupied ? "Occupied" : "Available") + "\",";
  json += "\"slot3\":\"" + String(slot3Occupied ? "Occupied" : "Available") + "\",";
  json += "\"slot4\":\"" + String(slot4Occupied ? "Occupied" : "Available") + "\",";
  json += "\"barrierIn\":\"" + String(barrierInActive ? "Open" : "Closed") + "\",";
  json += "\"barrierOut\":\"" + String(barrierOutActive ? "Open" : "Closed") + "\"";
  json += "}";
  server.send(200, "application/json", json);
}

// ----- Web Server Handler: Manually Open Entrance Barrier -----
void handleOpenEntrance() {
  barrierInActive = true;
  barrierInStartTime = millis();
  servoIn.write(0);  // Open the entrance barrier
  Serial.println("Entrance Barrier (manual): Opened");
  server.send(200, "text/plain", "Entrance barrier opened");
}

// ----- Web Server Handler: Manually Open Exit Barrier -----
void handleOpenExit() {
  barrierOutActive = true;
  barrierOutStartTime = millis();
  servoOut.write(0);  // Open the exit barrier
  Serial.println("Exit Barrier (manual): Opened");
  server.send(200, "text/plain", "Exit barrier opened");
}
