#include <Arduino.h>
#include <Fastled.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <PubSubClient.h>
#include <MQUnifiedsensor.h>
#include "FirebaseESP32.h"
#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"
#define WIFI_SSID "coba"
#define WIFI_PASSWORD  "apaituitu"
#define MQTT_HOST "broker.emqx.io"
#define MQTT_PORT 1883
#define MQTT_PUB_TEMP "/SmartMirrorMirafySemester5"
#define MQTT_PUB_HUM "/SmartMirrorMirafySemester5"
#define API_KEY "AIzaSyCKgIPGxmkOqnsPX-3r4jnpY6o082KkCEY"
#define DATABASE_URL "https://smart-mirror-4f0dd-default-rtdb.asia-southeast1.firebasedatabase.app/"

#define NUM_LEDS 60
#define DATA_WS2815 17
#define DHTTYPE DHT11
#define DHTPIN 16
#define Board ("ESP-32")
#define Pin (32)
#define Type ("MQ-7")
#define Voltage_Resolution (3.3)
#define ADC_Bit_Resolution (12)
#define RatioMQ2CleanAir (9.83)


int redValue = 0;
int greenValue = 0;
int blueValue = 0;
int timer_set = 180;
bool trigger = true;
bool ledValue;
float temp;
float hum;
bool signupOK = false;
String message;
unsigned long previousMillis = 0;
unsigned long interval = 10000;
volatile bool dataChanged = false;
CRGBArray<NUM_LEDS> leds;
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;
FirebaseJson json;
FirebaseData data;
FirebaseData stream;
FirebaseJsonData jsonData;
DHT dht(DHTPIN, DHTTYPE);
WiFiClient espClient;
PubSubClient client(espClient);
MQUnifiedsensor MQ7(Board, Voltage_Resolution, ADC_Bit_Resolution, Pin, Type);

int convertPPMtoAQI(float ppm) {
  const int numberOfRanges = 6;
  float ppmValues[numberOfRanges + 1] = {0, 4.4, 9.4, 12.4, 15.4, 30.4, 500};
  int aqiRanges[numberOfRanges] = {0, 50, 100, 150, 200, 300};

  for (int i = 0; i < numberOfRanges; i++) {
    if (ppm <= ppmValues[i + 1]) {
      return aqiRanges[i] + ((aqiRanges[i + 1] - aqiRanges[i]) / (ppmValues[i + 1] - ppmValues[i])) * (ppm - ppmValues[i]);
    }
  }
  return 500;  // Default, if ppm is beyond the defined upper limit
}

float convertPPMtoMgPerM3(float ppm, float molecularWeight) {
  const float molarVolume = 24.45; // L/mol, constant for ideal gas at STP
  return (ppm * molecularWeight) / molarVolume;
}

void MQ7Init (){
  MQ7.setRegressionMethod(1); //_PPM =  a*ratio^b
  MQ7.setA(99.042);
  MQ7.setB(-1.518);
  MQ7.init();

  Serial.print("Calibrating please wait.");
  float calcR0 = 0;
  for (int i = 1; i <= 10; i++) {
    MQ7.update();
    calcR0 += MQ7.calibrate(RatioMQ2CleanAir);
    Serial.print(".");
  }
  MQ7.setR0(calcR0 / 10);
  Serial.println("  done!.");

//   if (isinf(calcR0)) {
//     Serial.println("Warning: Connection issue, R0 is infinite (Open circuit detected) please check your wiring and supply");
//     while (1);
//   }
//   if (calcR0 == 0) {
//     Serial.println("Warning: Connection issue found, R0 is zero (Analog pin shorts to ground) please check your wiring and supply");
//     while (1);
//   }
}

void callback(char *topic, byte *payload, unsigned int length) {
    Serial.print("Message arrived in topic: ");
    Serial.println(topic);
    Serial.print("Message:");
    for (int i = 0; i < length; i++) {
        Serial.print((char) payload[i]);
    }
    Serial.println();
    Serial.println("-----------------------");
}

void initWifi() {
  btStop();
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
  Serial.print("----------------------");
  delay(1000);
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("RRSI: ");
  Serial.println(WiFi.RSSI());
  }

void streamTimeoutCallback(bool timeout)
{
  if (timeout)
    Serial.println("stream timed out, resuming...\n");

  if (!stream.httpConnected())
    Serial_Printf("error code: %d, reason: %s\n\n", stream.httpCode(), stream.errorReason().c_str());
}


void initFirebase() {
  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;
  if (Firebase.signUp(&config, &auth, "", "")){
    Serial.println("ok");
    Serial.println("--------------------------------------------");
    signupOK = true;
  }
  else  {
  Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }
  config.token_status_callback = tokenStatusCallback; 
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
  }

void WS2815() {
  if (ledValue == 0) {
    FastLED.setBrightness(0);
    FastLED.show();
  }
  else {
    FastLED.setBrightness(100);} 

  for (int i=0; i < NUM_LEDS; i++) {
      leds[i] = CRGB (greenValue, redValue, blueValue);}
      FastLED.show();
  }

void onDataColorChanged(StreamData data) {
    json.setJsonData(data.to<String>());
    if (json.get(jsonData, "b"))
    {
        int newBlue = jsonData.to<int>();
        blueValue = newBlue;
        Serial.print("| Blue: ");
        Serial.print(newBlue);
    }

    if (json.get(jsonData, "g"))
    {
        int newGreen = jsonData.to<int>();
        greenValue = newGreen;
        Serial.print(" | Green: ");
        Serial.print(newGreen);
    }

    if (json.get(jsonData, "r"))
    {
        int newRed = jsonData.to<int>();
        redValue = newRed;
        Serial.print(" | Red: ");
        Serial.print(newRed);
    }

    if (json.get(jsonData, "ledIsOn"))
    {
        bool newLedIsOn = jsonData.to<bool>(); {
          ledValue = newLedIsOn;
          Serial.print(" | Led is on: ");
          Serial.print(newLedIsOn);
          Serial.println(" |");
        }
    }
    WS2815();
    delay(50);
}

void initMqtt() {
  client.setServer(MQTT_HOST, MQTT_PORT);
  if (!client.connected()) {
    String client_id = "esp32-client-";
    client_id += String(WiFi.macAddress());
    Serial.printf("The client %s connects to the public MQTT broker\n", client_id.c_str());
    if (client.connect(client_id.c_str())) {
      Serial.println("Connected to MQTT broker");
      client.publish(MQTT_PUB_HUM, "Hi, I'm ESP32 ^^");
    } else {
      Serial.println("Failed to connect to MQTT broker");
    }
  }
}

void setup() {
    Serial.begin(9600);
    dht.begin();
    initWifi();
    initMqtt();
    initFirebase();
    MQ7Init();
    FastLED.addLeds<WS2812B, DATA_WS2815, RGB>(leds, NUM_LEDS);
    FastLED.setBrightness(100);
    Firebase.beginStream(stream, "/led/color");
    Firebase.setStreamCallback(stream, onDataColorChanged, streamTimeoutCallback);
}

void loop() {
  if (millis() - previousMillis >= interval) {
  previousMillis = millis();
  MQ7.update();
  float ppmValue = MQ7.readSensor();
  int aqiValue = convertPPMtoAQI(ppmValue);
  float mgPerM3Value = convertPPMtoMgPerM3(ppmValue, 28.01);

    hum = dht.readHumidity();
    hum = round(hum * 100) / 100;
    temp = dht.readTemperature();
    temp = round(temp * 100) / 100;
    message = "Temperature: " + String(temp) + " °C"+ "<br>Humidity: " + String(hum) + " RH" + "<br>PPM Value: " + String(ppmValue) + "<br>AQI Value: " + String(aqiValue) + "<br>CO Concentration (mg/m³): " + String(mgPerM3Value);
    //client.publish(MQTT_PUB_TEMP, String(temp, hum).c_str());
    client.publish(MQTT_PUB_HUM, message.c_str());

    Serial.print("| Temperature: ");
    Serial.print(temp);
    Serial.print(" °C  ");
    Serial.print("|   Humidity: ");
    Serial.print(hum);
    Serial.println("  RH  |");

  Serial.print("| PPM Value: ");
  Serial.print(ppmValue);
  Serial.print(" | AQI Value: ");
  Serial.print(aqiValue);
  Serial.print(" | CO Concentration (mg/m³): ");
  Serial.print(mgPerM3Value);
  Serial.println(" |");
    // Check if any reads failed and exit early (to try again).
    if (isnan(temp) || isnan(hum)) {
      Serial.println(F("Failed to read from DHT sensor!"));
      return;
    }}

if (Firebase.ready()){
    if ((WiFi.status() != WL_CONNECTED) && (millis() - previousMillis >=interval)) {
    Serial.print(millis());
    Serial.println("Reconnecting to WiFi...");
    WiFi.disconnect();
    WiFi.reconnect();
    previousMillis = millis();
    }
}

}

