#include <Keypad.h>
#include <BluetoothSerial.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>

// Deklarasi objek
BluetoothSerial SerialBT;
WebServer server(80);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 25200, 60000); // Waktu WIB (UTC +7)

// Konfigurasi WiFi
const char* ssid = "realme XT";
const char* password = "dimasdwi01";

// Konfigurasi keypad
const byte ROWS = 4; // Empat baris pada keypad
const byte COLS = 3; // Tiga kolom pada keypad
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};
byte rowPins[ROWS] = {15, 2, 0, 4};  // Pin yang terhubung ke baris keypad
byte colPins[COLS] = {16, 17, 5};    // Pin yang terhubung ke kolom keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// Variabel global
char btAddress[18];
String btAddressStr = "";
const int EEPROM_SIZE = 512;
const int EEPROM_SLOT_SIZE = 50; // Ukuran slot di EEPROM
const char* adminAddress = "9C:6B:72:57:0C:10";
const int doorControlPin = 12;
const int buzzerPin = 25;
const int buttonPin = 18;
const int magneticSwitchPin = 32;
bool buzzerSounding = false;
unsigned long buzzerStartTime = 0;
unsigned long buzzerDuration = 0;
bool previousDoorStatus = HIGH;
bool currentDoorStatus = HIGH;
String doorOpenerName;
String doorOpenerAddress;
String enteredCode = "";
String activityLog = ""; // String untuk menyimpan log aktivitas
unsigned long lastKeyPressTime = 0; // Waktu terakhir tombol ditekan
const unsigned long keyPressTimeout = 5000; // Timeout 5 detik

// Memindai apakah perangkat Bluetooth ada
void callback(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) {
  if (event == ESP_SPP_SRV_OPEN_EVT) {
    Serial.println("Client mencoba terhubung dengan alamat:");
    memset(btAddress, 0, sizeof(btAddress));
    byte c = 0;
    byte v, v1, v2;
    const char hx[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
    for (int i = 0; i < 6; i++) {
      Serial.printf("%02X", param->srv_open.rem_bda[i]);
      v = param->srv_open.rem_bda[i];
      v1 = v & 0xF;
      v2 = (v >> 4) & 0xF;
      btAddress[c] = hx[v2];
      c++;
      btAddress[c] = hx[v1];
      c++;
      if (i < 5) {
        Serial.print(":");
        btAddress[c] = ':';
        c++;
      }
    }
    btAddressStr = btAddress;
    Serial.println();
    Serial.print("Alamat: ");
    Serial.println(btAddressStr);

    if (btAddressStr == adminAddress) {
      Serial.println("Admin terhubung.");
      logActivity("Admin terhubung", btAddressStr, true);
      soundBuzzer(2, 100);
      sendIPAddress();
    } else {
      int addr = findDeviceSlot(btAddressStr.c_str());
      if (addr >= 0) {
        String record = EEPROM.readString(addr);
        if (record != "") {
          doorOpenerName = record.substring(0, record.indexOf(',')); // Nama perangkat pembuka pintu
          doorOpenerAddress = btAddressStr; // Gunakan alamat perangkat yang terhubung
          Serial.println("Client terhubung.");
          Serial.print("Stored record: ");
          Serial.println(record);
          Serial.print("Nama perangkat: ");
          Serial.println(doorOpenerName);
          Serial.print("Alamat perangkat: ");
          Serial.println(doorOpenerAddress);
          logActivity(doorOpenerName + " terhubung", doorOpenerAddress, true);
          soundBuzzer(2, 100);
          sendIPAddress();
        } else {
          Serial.println("Client tidak diizinkan untuk terhubung.");
          SerialBT.disconnect();
          logActivity("Client tidak diizinkan untuk terhubung", btAddressStr, true);
          soundBuzzer(1, 1000);
        }
      } else {
        Serial.println("Client tidak diizinkan untuk terhubung.");
        SerialBT.disconnect();
        logActivity("Client tidak diizinkan untuk terhubung", btAddressStr, true);
        soundBuzzer(1, 1000);
      }
    }
  }
}
// Kode komunikasi diinisialisasi, pin diatur sebagai input atau output, EEPROM mulai berjalan
void setup() {
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);
  pinMode(doorControlPin, OUTPUT);
  digitalWrite(doorControlPin, LOW);
  pinMode(buzzerPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(magneticSwitchPin, INPUT_PULLUP);
  SerialBT.register_callback(callback);
  if (!SerialBT.begin("Door Lock")) {
    Serial.println("Terjadi kesalahan saat menginisialisasi Bluetooth");
  } else {
    Serial.println("Bluetooth terinisialisasi");
  }

  WiFi.begin(ssid, password);
  unsigned long startAttemptTime = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
    delay(100);
    Serial.print(".");
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("WiFi terhubung");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    server.on("/", handleRoot);
    server.begin();
    timeClient.begin();
  } else {
    Serial.println("Koneksi WiFi gagal, melanjutkan operasi offline.");
  }
}
// Menambahkan dan Menghapus Perangkat
void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    server.handleClient();
    timeClient.update();
  }

  if (SerialBT.available()) {
    String incomingData = "";
    while (SerialBT.available()) {
      incomingData += char(SerialBT.read());
    }
    Serial.print("Data diterima: ");
    Serial.println(incomingData);
    StaticJsonDocument<200> doc;
    DeserializationError error = deserializeJson(doc, incomingData);
    if (error) {
      Serial.print(F("deserializeJson() gagal: "));
      Serial.println(error.f_str());
      return;
    }
    const char* action = doc["action"];
    const char* name = doc["name"];
    const char* address = doc["address"];
    const char* password = doc["password"];
    if (btAddressStr == adminAddress) {
      if (strcmp(action, "add") == 0) {
        addDevice(name, address, password);
        logActivity(String(name) + ", " + address + " ditambahkan", "", false);
      } else if (strcmp(action, "delete") == 0) {
        deleteDevice(address, name);
      }
    } else {
      int addr = findDeviceSlot(btAddressStr.c_str());
      if (addr >= 0) {
        if (strcmp(action, "open_door") == 0) {
          openDoor(doorOpenerName.c_str(), doorOpenerAddress.c_str()); // Gunakan nama dan alamat perangkat yang terhubung
        } else if (strcmp(action, "close_door") == 0) {
          closeDoor();
        }
      } else {
        Serial.println("Client tidak diizinkan untuk mengontrol pintu.");
      }
    }
  }
  if (digitalRead(buttonPin) == LOW) {
    openDoorWithButton();
  }
  if (buzzerSounding && millis() - buzzerStartTime >= buzzerDuration) {
    digitalWrite(buzzerPin, LOW);
    buzzerSounding = false;
  }
  currentDoorStatus = digitalRead(magneticSwitchPin);
  if (currentDoorStatus != previousDoorStatus) {
    previousDoorStatus = currentDoorStatus;
  }
  
  handleKeypadEntry();
}

void addDevice(const char* name, const char* address, const char* password) {
  Serial.print("Menambahkan perangkat dengan nama: ");
  Serial.print(name);
  Serial.print(", alamat: ");
  Serial.print(address);
  Serial.print(", dan password: ");
  Serial.println(password);
  if (findDeviceSlot(address) == -1) {
    int addr = findEmptySlot();
    if (addr != -1) {
      String record = String(name) + "," + String(address) + "," + String(password);
      EEPROM.writeString(addr, record);
      EEPROM.commit();
      Serial.println("Perangkat ditambahkan ke EEPROM.");
    } else {
      Serial.println("EEPROM penuh, tidak dapat menambahkan perangkat.");
    }
  } else {
    Serial.println("Perangkat dengan alamat ini sudah ada.");
  }
}

void deleteDevice(const char* address, const char* name) {
  Serial.print("Menghapus perangkat dengan alamat: ");
  Serial.println(address);
  int addr = findDeviceSlot(address);
  if (addr != -1) {
    EEPROM.writeString(addr, "");
    EEPROM.commit();
    Serial.println("Perangkat dihapus dari EEPROM.");
    logActivity(String(name) + " dihapus", address, true);
  } else {
    Serial.println("Perangkat tidak ditemukan di EEPROM.");
  }
}

void openDoor(const char* name, const char* address) {
  Serial.println(String(name) + " membuka pintu.");
  Serial.print("Nama perangkat: ");
  Serial.println(name);
  Serial.print("Alamat perangkat: ");
  Serial.println(address);
  digitalWrite(doorControlPin, HIGH);
  delay(5000);
  digitalWrite(doorControlPin, LOW);
  logActivity(String(name) + " membuka pintu", address, true);
}

void openDoorWithButton() {
  Serial.println("Pintu dibuka dengan tombol.");
  digitalWrite(doorControlPin, HIGH);
  delay(5000);
  digitalWrite(doorControlPin, LOW);
  logActivity("Pintu dibuka dengan tombol", "", false);
}

void closeDoor() {
  Serial.println("Pintu ditutup");
  digitalWrite(doorControlPin, LOW);
}
//pengecekan data
bool isCodeValid(const String& code) {
  for (int addr = 0; addr < EEPROM_SIZE; addr += EEPROM_SLOT_SIZE) {
    String record = EEPROM.readString(addr);
    if (record != "") {
      int lastCommaIndex = record.lastIndexOf(',');
      if (lastCommaIndex != -1) {
        String storedCode = record.substring(lastCommaIndex + 1);
        if (storedCode == code) {
          return true;
        }
      }
    }
  }
  return false;
}

void logActivity(String name, String address, bool includeConnected) {
  time_t rawTime = timeClient.getEpochTime();
  struct tm * timeInfo = localtime(&rawTime);
  char timeBuffer[20];
  strftime(timeBuffer, sizeof(timeBuffer), "%H:%M - %d/%m/%Y", timeInfo); // Ubah format waktu untuk menyertakan tanggal lengkap
  String formattedTime = String(timeBuffer);
  String logEntry = formattedTime + " - " + name + (includeConnected ? " (" + address + ")" : "");
  activityLog += "<tr><td>" + formattedTime + "</td><td>" + name + (includeConnected ? " (" + address + ")" : "") + "</td></tr>";
  Serial.println(logEntry);
}
// web server
void handleRoot() {
  // Cek status pintu
  String doorStatus = digitalRead(magneticSwitchPin) == HIGH ? "Terbuka" : "Tertutup"; // Memperbaiki logika status pintu
  
  String html = "<html><head><title>Log Aktivitas Smart Lock</title>";
  html += "<style>body{font-family: Arial, sans-serif; text-align: center; background-color: #f4f4f4;} h1{background-color: #4CAF50; color: white; padding: 1em;} table{margin: 1em auto; border-collapse: collapse; width: 80%; background-color: white; box-shadow: 0 2px 3px rgba(0,0,0,0.1);} th, td{border: 1px solid #ddd; padding: 12px; text-align: left;} th{background-color: #4CAF50; color: white;} tr:nth-child(even){background-color: #f2f2f2;} .door-status{position: fixed; top: 10px; right: 10px; font-size: 1.2em; padding: 10px; background-color: #ffeb3b; border: 1px solid #ccc; border-radius: 5px;}</style>";
  html += "<meta http-equiv='refresh' content='3'></head><body>";
  html += "<div class='door-status'><strong>Status Pintu: " + doorStatus + "</strong></div>";
  html += "<h1>Log Aktivitas Smart Lock</h1>";
  html += "<table><tr><th>Waktu</th><th>Aktivitas</th></tr>";
  html += activityLog;
  html += "</table></body></html>";
  server.send(200, "text/html", html);
}
// penyimpanan data
int findDeviceSlot(const char* address) {
  for (int addr = 0; addr < EEPROM_SIZE; addr += EEPROM_SLOT_SIZE) {
    String record = EEPROM.readString(addr);
    if (record.indexOf(address) != -1) {
      return addr;
    }
  }
  return -1;
}

int findEmptySlot() {
  for (int addr = 0; addr < EEPROM_SIZE; addr += EEPROM_SLOT_SIZE) {
    String record = EEPROM.readString(addr);
    if (record == "") {
      return addr;
    }
  }
  return -1;
}

void soundBuzzer(int beeps, int duration) {
  for (int i = 0; i < beeps; i++) {
    digitalWrite(buzzerPin, HIGH);
    delay(duration);
    digitalWrite(buzzerPin, LOW);
    delay(100);
  }
}

void handleKeypadEntry() {
  char key = keypad.getKey();
  if (key) {
    lastKeyPressTime = millis(); // Set waktu terakhir tombol ditekan
    soundBuzzer(1, 100);  // Suara buzzer setiap tombol ditekan
    Serial.print("Keypad input: ");
    Serial.println(key);
    if (key == '*') {
      enteredCode = "";
      Serial.println("Kode yang dimasukkan direset.");
    } else if (key == '#') {
      Serial.println("Memverifikasi kode yang dimasukkan...");
      Serial.print("Kode yang dimasukkan: ");
      Serial.println(enteredCode);
      if (enteredCode.length() == 0 || !isCodeValid(enteredCode)) {
        Serial.println("Kode salah atau tidak ada perangkat terdaftar.");
        logActivity("Kode salah atau tidak ada perangkat terdaftar", "", false); // Tambahkan log aktivitas
        soundBuzzer(1, 1000); // Buzzer berbunyi 1 kali lebih panjang
      } else {
        Serial.println("Kode benar.");
        String deviceName = getDeviceNameByCode(enteredCode);
        soundBuzzer(2, 100); // Buzzer berbunyi 2 kali
        openDoorWithKeypad(deviceName.c_str());
      }
      enteredCode = "";
    } else {
      enteredCode += key;
    }
  }

  // Cek apakah waktu timeout tercapai
  if (millis() - lastKeyPressTime >= keyPressTimeout && enteredCode != "") {
    Serial.println("Timeout: Kode yang dimasukkan salah.");
    logActivity("Timeout: Kode yang dimasukkan salah", "", false); // Tambahkan log aktivitas
    enteredCode = "";
    soundBuzzer(1, 1000); // Buzzer berbunyi 1 kali lebih panjang
  }
}

void openDoorWithKeypad(const char* name) {
  Serial.println(String(name) + " membuka pintu dengan keypad.");
  digitalWrite(doorControlPin, HIGH);
  delay(5000);
  digitalWrite(doorControlPin, LOW);
  logActivity(String(name) + " membuka pintu dengan keypad", "", false);
}
//Pengiriman data ke aplikasi
String getDeviceNameByCode(const String& code) {
  for (int addr = 0; addr < EEPROM_SIZE; addr += EEPROM_SLOT_SIZE) {
    String record = EEPROM.readString(addr);
    if (record != "") {
      int lastCommaIndex = record.lastIndexOf(',');
      if (lastCommaIndex != -1) {
        String storedCode = record.substring(lastCommaIndex + 1);
        if (storedCode == code) {
          int firstCommaIndex = record.indexOf(',');
          if (firstCommaIndex != -1) {
            return record.substring(0, firstCommaIndex);
          }
        }
      }
    }
  }
  return "Pengguna tidak dikenal";
}

// Function to send the IP address to the connected Bluetooth client
void sendIPAddress() {
  if (WiFi.status() == WL_CONNECTED) {
    String ipAddress = WiFi.localIP().toString();
    String message = "{\"ip\":\"" + ipAddress + "\"}";
    SerialBT.print(message);
    Serial.println("IP address sent: " + ipAddress);
  } else {
    Serial.println("WiFi not connected, cannot send IP address.");
  }
}

