#include <WiFiManager.h>
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
#include <ESP8266WiFi.h>
#include <LiquidCrystal_I2C.h>
#include <ESP8266WebServer.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

#define LED_R D6
#define LED_G D7
#define LED_B D8
#define LED_ON HIGH
#define LED_OFF LOW

MAX30105 particleSensor;
LiquidCrystal_I2C lcd(0x27, 16, 2); // Inisialisasi alamat I2C dan ukuran LCD
WiFiManager wm;

ESP8266WebServer server(80);
const char* mqtt_server = "test.mosquitto.org";

// Inisialisasi WiFi dan MQTT client
WiFiClient espClient;
PubSubClient client(espClient);

String lastReceivedID = "";
String lastReceivedName = "";
unsigned long lastSendTime = 0;
const unsigned long interval = 60000; // 1 menit dalam milidetik

String serverIP = ""; // Variabel global untuk menyimpan alamat IP server
const char* URL = ""; // Alamat server, akan diinisialisasi setelah mendapat IP dari handleUpdateServerIP
const int serverPort = 80; // Port server HTTP

const byte RATE_SIZE = 4; // Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; // Array of heart rates
byte rateSpot = 0;
long lastBeat = 0; // Time at which the last beat occurred

// nilai SPO2
uint32_t irBuffer[100]; // infrared LED sensor data
uint32_t redBuffer[100]; // red LED sensor data
int32_t bufferLength; // data length
int32_t spo2; // SPO2 value
int8_t validSPO2; // indicator to show if the SPO2 calculation is valid
int32_t heartRate; // heart rate value
int8_t validHeartRate; // indicator to show if the heart rate calculation is valid

float beatsPerMinute;
int beatAvg;

int buttonState = 0;
int lastButtonState = 0;
long lastDebounceTime = 0;
long debounceDelay = 50;

enum SensorMode {
  BPM_MODE,
  SPO2_MODE
};

float predictedGlucose;
SensorMode currentMode = BPM_MODE; // Default mode

String receivedValue = "";

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  
  String message = "";
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);
  
  // Extract ID and name from the JSON message
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, message);
  
  if (error) {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return;
  }
  
  // Mengambil nilai id sebagai integer
  lastReceivedID = doc["id"].as<int>();
  // Mengambil nilai name sebagai string
  lastReceivedName = doc["nama"].as<String>();
  lastSendTime = millis(); // Reset the timer

  Serial.print("Id: ");
  Serial.println(lastReceivedID);
  Serial.print("Nama: ");
  Serial.println(lastReceivedName);
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("SELAMAT DATANG");
  lcd.setCursor(0, 1);
  int nameLength = lastReceivedName.length();
  int spaces = (16 - nameLength) / 2; // Assuming 16 characters per line
  String paddedName = "";
  for (int i = 0; i < spaces; i++) {
    paddedName += " ";
  }
  paddedName += lastReceivedName;
  lcd.print(paddedName);

  startSensor();
}

void reconnect() {
  // Loop sampai terkoneksi
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Buat client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Coba koneksi
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      // Subscribe ke topic
      client.subscribe("amantuzh");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Tunggu 5 detik sebelum mencoba lagi
      delay(5000);
    }
  }
}

void startSensor() {
  if (!particleSensor.begin(Wire, I2C_SPEED_STANDARD)) { // Gunakan port I2C default ESP8266, kecepatan 100kHz
    Serial.println("MAX30105 was not found. Please check wiring/power.");
    while (1);
  }
  Serial.println("Place your index finger on the sensor with steady pressure.");
  delay(3000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("  TEKAN MULAI");
  lcd.setCursor(0, 1);
  lcd.print("UNTUK PENGUKURAN");
  digitalWrite(LED_R, LED_OFF);
  digitalWrite(LED_G, LED_ON);
  digitalWrite(LED_B, LED_OFF);
  particleSensor.setup(); // Konfigurasi sensor dengan pengaturan default
  particleSensor.setPulseAmplitudeRed(0x0A); // Menyalakan LED merah untuk menunjukkan sensor berjalan
  particleSensor.setPulseAmplitudeGreen(0); // Matikan LED hijau
}

void setup() {
  WiFi.mode(WIFI_STA);
  Serial.begin(115200);
  Serial.println("Initializing...");
  
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(D3, INPUT_PULLUP); // Internal pull-up resistor
  
  digitalWrite(LED_R, LED_OFF);
  digitalWrite(LED_G, LED_OFF);
  digitalWrite(LED_B, LED_OFF);

  lcd.init(); // Inisialisasi LCD
  lcd.backlight(); // Nyalakan backlight LCD
  Wire.begin(); // Inisialisasi I2C pada ESP8266

  wm.setDebugOutput(false);
  // Remove any previous network settings
  // wm.resetSettings();
  // Define a text box, 50 characters maximum
  WiFiManagerParameter custom_text_box("my_text", "Enter your string here", "default string", 50);

  // Add custom parameter
  wm.addParameter(&custom_text_box);
 
  // Blink LED B while searching for WiFi
  digitalWrite(LED_B, LED_ON);
  digitalWrite(LED_G, LED_OFF);

  lcd.clear();
  lcd.print("NOT CONNECT WIFI");
  bool res = wm.autoConnect("GlucoseMetter", "password"); // password protected ap
  if (!res) {
    Serial.println("Failed to connect");
    ESP.restart();
    delay(1000);
  }
  // Connected!
  digitalWrite(LED_B, LED_OFF);
  digitalWrite(LED_G, LED_ON);
  
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("  CONNECTED TO");
  lcd.setCursor(0, 1);
  int SsidLength = WiFi.SSID().length();
  int spaces = (16 - SsidLength) / 2; // Assuming 16 characters per line
  String paddedSsid = "";
  for (int i = 0; i < spaces; i++) {
    paddedSsid += " ";
  }
  paddedSsid += WiFi.SSID();
  lcd.print(paddedSsid);

  delay(2000);
  
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  server.begin();
  // Display "Pilih ID" until data is received
  lcd.clear();
  lcd.setCursor(0, 0);      
  lcd.print(" PILIH PENGGUNA");
}

void loop() {
  wm.process();
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  
  // Check if 1 minute has passed since the last ID was received
  if (lastReceivedID.length() > 0) {
    unsigned long currentMillis = millis();
    if (currentMillis - lastSendTime >= interval) {
      // sendID(lastReceivedID.c_str());
      lastSendTime = currentMillis; // Reset the timer for the next send
    }
  }

  int reading = digitalRead(D3);

  // Check if the button state has changed
  if (reading != lastButtonState) {
    // Reset the debounce timer
    lastDebounceTime = millis();
  }

  // Check if the debounce delay has passed
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // If the reading has changed, update the button state
    if (reading != buttonState) {
      buttonState = reading;
      
      // If the button is pressed
      if (buttonState == LOW) {
        // Toggle sensor mode
        if (currentMode == BPM_MODE) {
          runBPMMode();
          runSPO2Mode();
          KalibrasiGLucosa();
          startSensor();
        }
      }
    }
  }

  lastButtonState = reading;
}

void runBPMMode() {
  Serial.println("Mulai BPM Mode");
  unsigned long startTime = millis();
  while (millis() - startTime < 30000) {
    digitalWrite(LED_B, LED_OFF);
    digitalWrite(LED_G, LED_OFF);
    long irValue = particleSensor.getIR();
    
    if (irValue < 50000) {
      beatsPerMinute = 0.0;
      beatAvg = 0.0;
    } else {
      if (checkForBeat(irValue)) {
        long delta = millis() - lastBeat;
        lastBeat = millis();
        beatsPerMinute = 60 / (delta / 1000.0);
        
        if (beatsPerMinute < 255 && beatsPerMinute > 20) {
          rates[rateSpot++] = (byte)beatsPerMinute;
          rateSpot %= RATE_SIZE;

          beatAvg = 0;
          for (byte x = 0; x < RATE_SIZE; x++)
            beatAvg += rates[x];
          beatAvg /= RATE_SIZE;
        }
      }
    }
    Serial.print("IR=");
    Serial.print(irValue);
    Serial.print(", BPM=");
    Serial.print(beatsPerMinute);
    Serial.print(", Avg BPM=");
    Serial.print(beatAvg);
    Serial.println();

    if (irValue > 100000){        
      digitalWrite(LED_R, LED_ON);
    } else {
      digitalWrite(LED_R, LED_OFF);     
    }

    // Update LCD
    lcd.clear(); // Bersihkan layar LCD
    lcd.setCursor(0, 0);
    lcd.print("BPM: ");
    lcd.print(beatAvg);
    lcd.setCursor(0, 1);
    lcd.print("SPO2: ");
  }
  Serial.print("Hasil BPM Mode: ");
  Serial.println(beatAvg);
}

void runSPO2Mode() {
  Serial.println("Mulai SPO2 Mode");
  unsigned long startTime = millis();
  while (millis() - startTime < 16000) {
    bufferLength = 100; // buffer length of 100 stores 4 seconds of samples running at 25sps

    // read the first 100 samples, and determine the signal range
    for (byte i = 0 ; i < bufferLength ; i++) {
      while (particleSensor.available() == false) // do we have new data?
        particleSensor.check(); // Check the sensor for new data

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); // We're finished with this sample so move to next sample
    }

    // calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
    maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

    // Send data to Serial Monitor
    Serial.print(F("BPM="));
    Serial.print(heartRate);
    Serial.print(F(", ValidHR="));
    Serial.print(validHeartRate);
    Serial.print(F(", SPO2="));
    Serial.print(spo2, DEC);
    Serial.print(F(", SPO2Valid="));
    Serial.println(validSPO2, DEC);
    // Update LCD
    lcd.clear(); // Bersihkan layar LCD
    lcd.setCursor(0, 0);
    lcd.print("BPM: ");
    lcd.print(beatAvg);

    lcd.setCursor(0, 1);
    lcd.print("SPO2: "); 
    lcd.print(spo2); 
    delay(3000);
  }

  Serial.print("Hasil BPM Mode: ");
  Serial.println(beatAvg);
  Serial.print("Hasil SPO2 Mode: ");
  Serial.println(spo2);
}

void KalibrasiGLucosa() {
  // Rumus Prediksi GLucosa
  predictedGlucose = 16714.61 + (0.47 * beatAvg) - (351.045 * spo2) + (1.85 * (spo2 * spo2));
           
  Serial.print("Predicted Glucose= ");
  Serial.println(predictedGlucose);
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("PREDIKSI GLUKOSA ");
  lcd.setCursor(0, 1);
  lcd.print("  ");
  lcd.print(predictedGlucose);
  lcd.print("mg/dl");
  sendData();
  delay(10000);
  // Mengirim data setelah perhitungan prediksi glukosa selesai



  lcd.clear();
  beatsPerMinute = 0;
  beatAvg = 0;
  digitalWrite(LED_R, LED_OFF);
  
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("   PENGUKURAN");
  lcd.setCursor(0, 1);
  lcd.print("    SELESAI");
  
  delay(1000);
}

void sendData() {
  // Pastikan klien MQTT terhubung
  if (!client.connected()) {
    Serial.println("MQTT client not connected, attempting to reconnect...");
    reconnect();
  }

  // Membuat objek JSON untuk mengirim data
  StaticJsonDocument<200> jsonDoc;
  jsonDoc["id"] = lastReceivedID;
  jsonDoc["BPM"] = beatAvg;
  jsonDoc["SpO2"] = spo2;
  jsonDoc["PredictedGlucose"] = predictedGlucose;

  // Mengonversi objek JSON menjadi string
  char buffer[256];
  size_t n = serializeJson(jsonDoc, buffer);

  // Mencetak data yang akan dikirim ke Serial Monitor untuk debug
  Serial.print("Sending data: ");
  Serial.println(buffer);

  // Mengirim data ke broker MQTT dengan topik "postdataGluc"
  if (client.publish("postdataGluc", buffer, n)) {
    Serial.println("Data sent successfully");
  } else {
    Serial.println("Failed to send data");
  }
}


