#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP8266WiFi.h>
#include <Firebase_ESP_Client.h>
#include <ArduinoJson.h>

// --- Konfigurasi WiFi ---
#define WIFI_SSID "LMAO"
#define WIFI_PASSWORD "awokawok"

// --- Konfigurasi Firebase ---
#define FIREBASE_HOST "https://meja-iotv0-default-rtdb.firebaseio.com/"
#define FIREBASE_AUTH "AIzaSyC9YQJWMSajJQTlh1tGLRcIeui4rqGvl8E"

FirebaseData firebaseData;
FirebaseConfig config;
FirebaseAuth auth;

LiquidCrystal_I2C lcd(0x27, 16, 2);

// --- Definisi Pin ---
#define TRIG_PIN D5
int echoPins[4] = {D6, D7, D8, D4};
#define BUZZER_PIN D0
#define FORCE_BUTTON_PIN D3

float distances[4];
float lastValidDistances[4];
float lastSensorDistances[4];

// --- Sistem Reservasi ---
bool tableActivationSensorActive = false;
bool tableReserved = false;
bool tableOccupied = false;
bool refreshTriggered = false;

unsigned long reservationSessionStartOrRefreshTime = 0;
unsigned long lastPresenceTime = 0;

String tempLine1 = "";
String tempLine2 = "";
unsigned long messageStartTime = 0;
unsigned long messageDuration = 2000; 
bool showTempMessage = false;
String lastLine1 = "";
String lastLine2 = "";
unsigned long lastLCDUpdate = 0;
const unsigned long LCD_UPDATE_INTERVAL = 1000;

const unsigned long RESERVATION_ACTIVE_DURATION = 30 * 60 * 1000;
const unsigned long IDLE_OCCUPIED_TIMEOUT = 10 * 60 * 1000;
const unsigned long WARNING_TIME_10_MIN = 10 * 60 * 1000;
const unsigned long WARNING_TIME_5_MIN = 5 * 60 * 1000;

bool warning10MinTriggered = false;
bool warning5MinTriggered = false;

const float PRESENCE_MIN = 50.0;
const float PRESENCE_MAX = 80.0;
const float MOTION_THRESHOLD = 5.0;

bool presenceDetected = false;

String customerName = "N/A";

bool forceShutdownRequested = false;
unsigned long lastButtonPress = 0;
const unsigned long BUTTON_DEBOUNCE = 200;
unsigned long lastFirebaseUpdate = 0;
const unsigned long FIREBASE_UPDATE_INTERVAL = 5000;

#define TABLE_ID "meja_001"
String deviceID = TABLE_ID;

void showTemporaryMessage(String line1, String line2, int duration = 2000) {
  tempLine1 = line1;
  tempLine2 = line2;
  messageDuration = duration;
  showTempMessage = true;
  messageStartTime = millis();

  lcd.clear();
  lcd.setCursor(0, 0); lcd.print(tempLine1);
  lcd.setCursor(0, 1); lcd.print(tempLine2);

  lastLCDUpdate = millis(); // supaya tidak update terlalu cepat
}

// ==== SETUP ====
void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  lcd.init(); lcd.backlight();
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(FORCE_BUTTON_PIN, INPUT_PULLUP);
  for (int i = 0; i < 4; i++) pinMode(echoPins[i], INPUT);

  connectWiFi();
  if (WiFi.status() == WL_CONNECTED) initFirebase();
    showTemporaryMessage("Sistem Siap!", deviceID);
}

// ==== LOOP ====
void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWiFi();
  if (Firebase.ready() && millis() - lastFirebaseUpdate > FIREBASE_UPDATE_INTERVAL) {
    updateFromFirebase();
    updateToFirebase();
    lastFirebaseUpdate = millis();
  }

  if (forceShutdownRequested) {
    forceShutdown();
    if (Firebase.ready()) {
      Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/force_shutdown", 0);
    }
    forceShutdownRequested = false;
  }

  if (digitalRead(FORCE_BUTTON_PIN) == LOW && millis() - lastButtonPress > BUTTON_DEBOUNCE) {
    forceShutdown(); lastButtonPress = millis();
  }

  readSensors();
  analyzeActivity();
  if (tableActivationSensorActive) {
    if (!tableReserved) startReservation();
    if (tableReserved) {
      checkSessionTimeout();
      checkIdle();
      checkWarnings();
    }
  } else if (tableReserved) {
    endReservation();
  }
  displayLCD();
  printSerial();
  delay(500);
}

// ==== SENSOR & AKTIVITAS ====
float readDistance(int pin) {
  float sum = 0; int valid = 0;
  for (int i = 0; i < 3; i++) {
    digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    long d = pulseIn(pin, HIGH, 30000);
    float dist = d * 0.034 / 2;
    if (dist > 2 && dist < 400) { sum += dist; valid++; }
    delay(10);
  }
  return (valid == 0) ? 0 : sum / valid;
}

void readSensors() {
  for (int i = 0; i < 4; i++) {
    float d = readDistance(echoPins[i]);
    if (d > 0) lastValidDistances[i] = d;
    distances[i] = lastValidDistances[i];
  }
}

void analyzeActivity() {
  unsigned long now = millis();
  bool motion = false;
  for (int i = 0; i < 4; i++) {
    float delta = abs(distances[i] - lastSensorDistances[i]);
    if (lastSensorDistances[i] > 0 && delta >= MOTION_THRESHOLD) motion = true;
    lastSensorDistances[i] = distances[i];
  }

  if (now - reservationSessionStartOrRefreshTime > 10000) refreshTriggered = false;

  if (tableReserved && motion && !refreshTriggered) {
    reservationSessionStartOrRefreshTime = now;
    warning10MinTriggered = warning5MinTriggered = false;
    refreshTriggered = true;
    Serial.println("Sesi diperpanjang karena gerakan.");
  }

  presenceDetected = false;
  for (int i = 0; i < 4; i++) {
    if (distances[i] >= PRESENCE_MIN && distances[i] <= PRESENCE_MAX) {
      presenceDetected = true;
      break;
    }
  }

  if (presenceDetected) {
    lastPresenceTime = now;
    tableOccupied = true;
  } else if (now - lastPresenceTime > IDLE_OCCUPIED_TIMEOUT) {
    tableOccupied = false;
  }

  // Tambahkan warning jika terlalu dekat
  static unsigned long lastWarningTime = 0;
  for (int i = 0; i < 4; i++) {
    if (distances[i] > 0 && distances[i] < 10.0 && millis() - lastWarningTime > 5000) {
      showTooCloseWarning();
      lastWarningTime = millis();
      break;
    }
  }
}

void showTooCloseWarning() {
  showTemporaryMessage("!! TERLALU DEKAT !!", "Jarak < 10 cm");
}

// ==== WIFI & FIREBASE ====
void connectWiFi() {
  lcd.clear(); lcd.setCursor(0, 0); lcd.print("Koneksi WiFi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  for (int i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) {
    delay(500); Serial.print(".");
  }
  lcd.clear();
  if (WiFi.status() == WL_CONNECTED) {
    showTemporaryMessage("WiFi Terhubung!", WiFi.localIP().toString());
    Serial.println("WiFi IP: " + WiFi.localIP().toString());
  } else {
    showTemporaryMessage("Gagal WiFi!", "Coba lagi...");
  }
}

void initFirebase() {
  config.host = FIREBASE_HOST;
  config.signer.tokens.legacy_token = FIREBASE_AUTH;
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
  Serial.println(Firebase.ready() ? "Firebase Terhubung!" : "Gagal Firebase!");
}

void updateFromFirebase() {
  String base = "/" + deviceID;
  if (Firebase.RTDB.getInt(&firebaseData, base + "/sensors/table_activation_sensor_active"))
    tableActivationSensorActive = firebaseData.intData() == 1;
  else
    tableActivationSensorActive = false;

  if (Firebase.RTDB.getString(&firebaseData, base + "/reserved_by")) {
    customerName = firebaseData.stringData();
    if (customerName.length() > 8) customerName = customerName.substring(0, 8);
  } else customerName = "N/A";

  if (Firebase.RTDB.getInt(&firebaseData, base + "/force_shutdown")) {
  forceShutdownRequested = firebaseData.intData() == 1;
  } else {
    forceShutdownRequested = false;
  }
}

void updateToFirebase() {
  if (!Firebase.ready()) return;
  String path = "/" + deviceID;
  FirebaseJson json;
  json.set("device_id", deviceID);
  json.set("mac_address", WiFi.macAddress());
  json.set("last_update", millis() / 1000);
  json.set("table_occupied", presenceDetected ? 1 : 0);
  json.set("time_remaining_minutes", (int)(getRemainingSessionTime() / 60000));
  for (int i = 0; i < 4; i++) json.set("sensors/s" + String(i + 1), (int)distances[i]);
  json.set("sensors/table_activation_sensor_active", tableActivationSensorActive ? 1 : 0);
  Firebase.RTDB.updateNode(&firebaseData, path, &json);
}

// ==== RESERVASI ====
void resetFlags() {
  for (int i = 0; i < 4; i++) lastSensorDistances[i] = 0;
  warning10MinTriggered = warning5MinTriggered = refreshTriggered = false;
}

void startReservation() {
  tableReserved = true;
  reservationSessionStartOrRefreshTime = millis();
  lastPresenceTime = millis();
  resetFlags();
  buzzerAlert(2, 500, 150);
  Serial.println("Reservasi dimulai oleh " + customerName);
  showTemporaryMessage("MEJA " + deviceID.substring(5), "RVSP: " + customerName);
}

void endReservation() {
  tableReserved = false;
  tableOccupied = false;
  resetFlags();
  showTemporaryMessage("RESERVASI", "BERAKHIR");
}

void forceShutdown() {
  tableActivationSensorActive = false;
  endReservation();
  Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/sensors/table_activation_sensor_active", 0);
  Firebase.RTDB.setString(&firebaseData, "/" + deviceID + "/reserved_by", "N/A");
  customerName = "N/A";
  buzzerAlert(5, 100, 50);
  lcd.clear(); lcd.setCursor(0, 0); lcd.print("TERIMA KASIH");
  lcd.setCursor(0, 1); lcd.print("SUDAH DATANG!"); delay(3000);
}

unsigned long getRemainingSessionTime() {
  if (!tableReserved) return 0;
  unsigned long elapsed = millis() - reservationSessionStartOrRefreshTime;
  return (elapsed >= RESERVATION_ACTIVE_DURATION) ? 0 : RESERVATION_ACTIVE_DURATION - elapsed;
}

void checkSessionTimeout() {
  if (getRemainingSessionTime() == 0) {
    tableActivationSensorActive = false;
    endReservation();
    Firebase.RTDB.setInt(&firebaseData, "/" + deviceID + "/sensors/table_activation_sensor_active", 0);
    Firebase.RTDB.setString(&firebaseData, "/" + deviceID + "/reserved_by", "N/A");
  }
}

void checkIdle() {
  if (millis() - lastPresenceTime >= IDLE_OCCUPIED_TIMEOUT) tableOccupied = false;
  else tableOccupied = true;
}

void checkWarnings() {
  unsigned long remain = getRemainingSessionTime();
  if (remain <= WARNING_TIME_5_MIN && !warning5MinTriggered) {
    warning5MinTriggered = true;
    buzzerAlert(5, 200, 80);
    showWarningLCD("< 5 menit!");
  } else if (remain <= WARNING_TIME_10_MIN && !warning10MinTriggered) {
    warning10MinTriggered = true;
    buzzerAlert(3, 300, 100);
    showWarningLCD("< 10 menit!");
  }
}

void showWarningLCD(String text) {
  showTemporaryMessage("** PERINGATAN **", text);
}

// ==== TAMPILAN ====
void displayLCD() {
  if (millis() - lastLCDUpdate < LCD_UPDATE_INTERVAL) return;

  if (showTempMessage) {
    if (millis() - messageStartTime >= messageDuration) {
      showTempMessage = false;
      lastLine1 = ""; // paksa refresh ke tampilan normal
      lastLine2 = "";
    }
    return;
  }

  String line1 = "MEJA " + deviceID.substring(5);
  String line2;

  if (WiFi.status() != WL_CONNECTED) line2 = "WiFi terputus";
  else if (!tableReserved) line2 = "TERSEDIA";
  else line2 = "RVSP: " + customerName;

  if (line1 != lastLine1 || line2 != lastLine2) {
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print(line1);
    lcd.setCursor(0, 1); lcd.print(line2);
    lastLine1 = line1;
    lastLine2 = line2;
    lastLCDUpdate = millis();
  }
}

void printSerial() {
  Serial.println("\n=== STATUS " + deviceID + " ===");
  Serial.println("WiFi: " + String(WiFi.status() == WL_CONNECTED ? "YA" : "TIDAK"));
  Serial.println("Firebase: " + String(Firebase.ready() ? "YA" : "TIDAK"));
  Serial.print("Sensor: ");
  for (int i = 0; i < 4; i++) Serial.print("S" + String(i+1) + ":" + String(distances[i]) + " ");
  Serial.println();
  Serial.println("Aktif: " + String(tableActivationSensorActive ? "YA" : "TIDAK"));
  Serial.println("Reserved: " + String(tableReserved ? "YA" : "TIDAK"));
  Serial.println("Occupied: " + String(tableOccupied ? "YA" : "TIDAK"));
  Serial.println("Sisa Waktu: " + String(getRemainingSessionTime() / 60000) + " menit");
  Serial.println("Pemesan: " + customerName);
  Serial.println("Warning10: " + String(warning10MinTriggered));
  Serial.println("Warning5: " + String(warning5MinTriggered));
}

// ==== BUZZER ====
void buzzerAlert(int count, int onTime, int offTime) {
  for (int i = 0; i < count; i++) {
    digitalWrite(BUZZER_PIN, HIGH); delay(onTime);
    digitalWrite(BUZZER_PIN, LOW); delay(offTime);
  }
}
