#include <SPI.h>
#include <MFRC522.h>
#include "DFRobotDFPlayerMini.h"
#include <WiFi.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <PubSubClient.h> 

/* ================= CONFIGURATION ================= */
const char* ssid      = "OPPO A57";
const char* password  = "11223344";
const char* mqtt_server = "192.168.254.121";
const int mqtt_port     = 1883;

/* ================= OBJECTS & PINS ================= */
#define SS_PIN 5
#define RST_PIN 4
#define BUZZER_PIN 13 
MFRC522 rfid(SS_PIN, RST_PIN);
HardwareSerial mySerial(2);
DFRobotDFPlayerMini dfplayer;
LiquidCrystal_I2C lcd(0x27, 16, 2);

WiFiClient espClient;
PubSubClient client(espClient);

int merah = 23, hijau = 22, biru = 21;

/* ================= STATE & TIMERS ================= */
String mode = "standby"; 
unsigned long lastWifiCheck = 0;
unsigned long lastHeartbeat = 0; 
bool isAudioPlaying = false;
unsigned long audioStartTime = 0;
const unsigned long AUDIO_DURATION = 3500; // jeda 3 detik

String lastUID = "";
unsigned long lastScanTime = 0;
const unsigned long SCAN_DELAY = 3000;   // jeda 3 detik

String scrollMessage = "    NAWASENA SMART LIBRARY SYSTEM - BAKORWIL III MALANG    ";
unsigned long lastScroll = 0;
int scrollCursor = 0;

/* ================= HELPERS ================= */
void setRGB(bool r, bool g, bool b) {
  digitalWrite(merah, r); 
  digitalWrite(hijau, g); 
  digitalWrite(biru, b);
}

void beep(int duration) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(duration);
  digitalWrite(BUZZER_PIN, LOW);
}

void playTrack(int track) {
  if (isAudioPlaying) return;  
  Serial.print("🔊 Memutar Track: "); Serial.println(track);
  dfplayer.play(track);
  isAudioPlaying = true;
  audioStartTime = millis();
}

void applyModeVisual() {
  if (mode == "admin") setRGB(1, 1, 0);           
  else if (mode == "scan_member") setRGB(0, 1, 0); 
  else if (mode == "wait") setRGB(1, 0, 0);        
  else if (mode == "identified") setRGB(0, 1, 1);  
  else setRGB(0, 0, 1);                            
}


/* ================= MQTT CALLBACK ================= */
void callback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) message += (char)payload[i];
  
  Serial.println("📥 MQTT Diterima [" + String(topic) + "]: " + message);

  if (String(topic) == "rfid/mode/set") {
    mode = message;
    lcd.clear();
    if (mode == "scan_member") playTrack(6); 
    else if (mode == "admin") playTrack(5);  
    applyModeVisual();
  }

  if (String(topic) == "rfid/status") {
    lcd.clear();
    if (message == "valid") {
      mode = "identified"; 
      lcd.setCursor(0, 0); lcd.print(" KARTU TERBACA ");
      lcd.setCursor(0, 1); lcd.print("   SILAHKAN...  ");
      playTrack(4);        
    }
    else if (message == "success" || message == "finish") {
      setRGB(0, 1, 0);     
      lcd.setCursor(0, 0); lcd.print("  TRANSAKSI OK  ");
      lcd.setCursor(0, 1); lcd.print("  TERIMA KASIH  ");
      playTrack(8);        
      delay(2500);
      mode = "standby";
    } 
    else if (message == "registered") {
      setRGB(0, 1, 0);
      lcd.setCursor(0, 0); lcd.print(" REGISTRASI OK  ");
      lcd.setCursor(0, 1); lcd.print(" KARTU TERTAUT  ");
      playTrack(7);
      delay(2500);
      mode = "standby";
    }
    else if (message == "error" || message == "failed") {
      setRGB(1, 0, 0);     
      lcd.setCursor(0, 0); lcd.print(" AKSES DITOLAK  ");
      lcd.setCursor(0, 1); lcd.print("  COBA LAGI...  ");
      playTrack(3);        
      delay(2500);
    }
    lcd.clear();
    applyModeVisual();
  }
}
void reconnectMQTT() {
  while (!client.connected()) {
    Serial.print("🔄 Menghubungkan MQTT...");
    String clientId = "ESP32_Nawasena_" + String(random(0xffff), HEX);
    
    if (client.connect(clientId.c_str(), NULL, NULL, "rfid/device/status", 1, true, "offline")) {
      Serial.println("TERHUBUNG!");
      client.subscribe("rfid/mode/set");
      client.subscribe("rfid/status");
      client.subscribe("rfid/scan/admin"); 
      client.publish("rfid/device/status", "online", true);
    } else {
      Serial.print("Gagal, rc="); Serial.print(client.state());
      delay(5000);
    }
  }
}

/* ================= CORE FUNCTIONS ================= */

void handleRFID() {
  String uid = "";
  for (byte i = 0; i < rfid.uid.size; i++) {
    uid += (rfid.uid.uidByte[i] < 0x10 ? "0" : "");
    uid += String(rfid.uid.uidByte[i], HEX);
  }
  uid.toUpperCase();

  if (uid == lastUID && (millis() - lastScanTime < SCAN_DELAY)) {
    // Sesi dihentikan dengan aman jika kartu yang sama di-tap berulang kali
    rfid.PICC_HaltA();
    rfid.PCD_StopCrypto1();
    return; 
  }

  // Perbarui waktu scan terakhir yang sukses
  lastUID = uid;
  lastScanTime = millis();

  Serial.println("🆔 UID Terdeteksi: " + uid);
  
  beep(150); 
  setRGB(1, 0, 1); 

  lcd.clear();
  lcd.setCursor(0, 0); lcd.print("     MEMPROSES    ");
  lcd.setCursor(0, 1); lcd.print(" MOHON TUNGGU.. ");

  // Publikasikan data murni berdasarkan mode via MQTT saja (API Dihapus)
  if (mode == "admin") {
    client.publish("rfid/scan/admin", uid.c_str());
  } 
  else if (mode == "scan_member") {
    client.publish("rfid/scan/member", uid.c_str());
  } 
  else {
    // Default fallback jika standby atau mode lainnya ke topic umum
    client.publish("rfid/scan/general", uid.c_str());
  }

  delay(500); 
  applyModeVisual();

  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}

void updateScrollText() {
  if (millis() - lastScroll > 350) {
    lastScroll = millis();
    lcd.setCursor(0, 1);
    String displayMsg = scrollMessage.substring(scrollCursor, scrollCursor + 16);
    if (displayMsg.length() < 16) {
      displayMsg += scrollMessage.substring(0, 16 - displayMsg.length());
    }
    lcd.print(displayMsg);
    scrollCursor++;
    if (scrollCursor >= scrollMessage.length()) scrollCursor = 0;
  }
}

/* ================= MAIN PROGRAM ================= */
void setup() {
  Serial.begin(115200);
  pinMode(merah, OUTPUT); pinMode(hijau, OUTPUT); pinMode(biru, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT); 
  
  digitalWrite(BUZZER_PIN, LOW); 
  setRGB(1, 1, 1); 
  
  Wire.begin(32, 33);
  lcd.init(); lcd.backlight();
  lcd.setCursor(0, 0); lcd.print(" NAWASENA SMART ");
  lcd.setCursor(0, 1); lcd.print("    BOOTING...   ");

  mySerial.begin(9600, SERIAL_8N1, 25, 26);
  if (dfplayer.begin(mySerial)) {
    dfplayer.volume(30);
  }

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  SPI.begin(18, 15, 19, 5);
  rfid.PCD_Init();
  
  delay(1000);
  playTrack(1); 
  lcd.clear();
  applyModeVisual();
}

void loop() {
  if (!client.connected()) {
    reconnectMQTT();
  }
  client.loop();

  if (millis() - lastHeartbeat > 30000) {
    lastHeartbeat = millis();
    if(client.connected()) {
      client.publish("rfid/device/status", "online", true);
    }
  }

  if (isAudioPlaying && (millis() - audioStartTime >= AUDIO_DURATION)) {
    isAudioPlaying = false;
  }

  // Logika Tampilan LCD
  if (mode != "identified" && mode != "wait") {
      lcd.setCursor(0, 0);
      if (mode == "scan_member") {
        lcd.print(" TAP KARTU ANDA ");
        lcd.setCursor(0, 1);
        lcd.print("      ( ANGGOTA ) ");
      } else if (mode == "admin") {
        lcd.print(" MODE REGISTRASI");
        lcd.setCursor(0, 1);
        lcd.print("  SCAN KARTU... ");
      } else {
        lcd.print(" NAWASENA READY ");
        updateScrollText();
      }
  }

  if (!isAudioPlaying) {
    if (rfid.PICC_IsNewCardPresent()) {
      if (rfid.PICC_ReadCardSerial()) {
        handleRFID();
      }
    }
  }
}
