#include <Wire.h>
#include <DHT.h>
#include "HX711.h"
#include <LCD_I2C.h>  // Include library LCD_I2C by BlackHack
#include <math.h>
#include <EEPROM.h>
#include "DFRobot_ENS160.h"  // Librería para ENS160
#include "Adafruit_AHTX0.h"  // Librería para AHT21

LCD_I2C lcd(0x27, 16, 2);  // Default address of most PCF8574 modules, change according
// Inicializar el sensor ENS160
DFRobot_ENS160_I2C ens160(&Wire, 0x53);  // Dirección I2C por defecto para ENS160 (0x53)

// Inicializar el sensor AHT21 (AHTX0)
Adafruit_AHTX0 aht;
#define useCO2 1

#define CALWEIGHT 1.00  // Set known value for calibration (In Kg 1.0 = 1000 gram)
#define LB2KG 0.45352   // Conversion factor of Lbs to Kg
#define DEFAULT_CALIFACTOR -7050
#define number_of_digits 2


unsigned long lcdUpdateTime = 5000;                          // Lcd update time for next sensor value
unsigned long lastLCDUpdateTime = millis() - lcdUpdateTime;  // variable to hold last lcd update time
// Variables to hold sensor data values
int hIn = 0;
float tIn = 0;  // or dhtIn.readTemperature(true) for Fahrenheit
int hOut = 0;
float tOut = 0;      // or dhtIn.readTemperature(true) for Fahrenheit
int soundValue = 0;  // Variable to hold sound sensor value
float peso = 0.0;    // variable to hold weight sensor
int co2Value = 0;    // read CO2 value
int tvocValue = 0;   // Read TVOC value


// Pins Definition
#define DHTPINin 2  // What digital pin we're connected to

const byte soundSensorPin = A0;  // Pin analógico del sensor de sonido

const byte DOUT_PIN = 4;     // Pin digital conectado a DOUT del módulo HX711
const byte SCK_PIN = 5;      // Pin digital conectado a SCK del módulo HX711
#define calibrationBtnPin 3  // Scale calibration button pin

HX711 scale;
long currentOffset;
float calibration_factor;


// Uncomment whatever type you're using!
#define DHTTYPEin DHT22  // DHT 22 for inside temperature

DHT dhtIn(DHTPINin, DHTTYPEin);

int displayCount = 0;  // variable to hold display count

void setup() {
  Serial.begin(9600);
  Wire.begin();  // Iniciar el bus I2C

  pinMode(soundSensorPin, INPUT);  // Set sound sensor pin as input

  dhtIn.begin();  // Initialize DHT in sensor

  lcd.begin();  // If you are using more I2C devices using the Wire library use lcd.begin(false)
                // this stop the library(LCD_I2C) from calling Wire.begin()
  lcd.backlight();

  pinMode(calibrationBtnPin, INPUT_PULLUP);



  // eeprom
  if (EEPROM.read(0x00) != 0x01) {
    Serial.println("NOT INIT !!!!");
    currentOffset = 0;
    calibration_factor = DEFAULT_CALIFACTOR;
    // show instructions
    displayMessage(0, 0, "MUST CALIBIRATE", HIGH);
    displayMessage(0, 1, "Press Calib BTn");
    lcd.clear();
    delay(2000);  // wait for 2 seconds
  } else {
    EEPROM.get(0x01, currentOffset);
    EEPROM.get(0x01 + sizeof(long), calibration_factor);
    Serial.println("currentOffset = " + String(currentOffset));
    Serial.println("calibration_factor = " + String(calibration_factor));
  }


  //scale
  scale.begin(DOUT_PIN, SCK_PIN);
  delay(100);
  Serial.println("calibration_factor = " + String(calibration_factor));
  scale.set_scale(calibration_factor / LB2KG);
  scale.set_offset(currentOffset);  // Apply saved offset value
  Serial.println("setup done ...");
  delay(1000);

  // Inicializar el sensor ENS160
  if (ens160.begin() != 0) {
    Serial.println("Error al iniciar el sensor ENS160. Verifica las conexiones.");
    displayMessage(0, 0, "ENS160 Error");
    delay(1000);
    // while (1);  // Detiene el programa si falla la inicialización
  }
  Serial.println("Sensor ENS160 iniciado correctamente.");

  // Inicializar el sensor AHT21
  if (!aht.begin()) {
    Serial.println("Error al iniciar el sensor AHT21. Verifica las conexiones.");
    displayMessage(0, 0, "AHT Error");
    delay(1000);
    // while (1);  // Detiene el programa si falla la inicialización
  }
  Serial.println("Sensor AHT21 iniciado correctamente.");
}

void loop() {
  // if calibrationBtnPin is pressed (LOW) start calibiration
  if (digitalRead(calibrationBtnPin) == LOW) {
    performCalibration();
  }
  if ((millis() - lastLCDUpdateTime) > lcdUpdateTime) {
    lastLCDUpdateTime = millis();  // Note current lcd update time
    switch (displayCount) {
      case 0:
        displayHumidityTemp();  // Display humidity and temperature values
        break;
      case 1:
        displaySoundWight();  // Display sound and weight value
        break;
      case 2:
        readCO2Sensor();  // Display CO2 sensor value
        break;
      default:
        break;
    }
    displayCount++;  // Increase counter
    displayCount = (displayCount > 2) ? 0 : displayCount;
  }
}
void readCO2Sensor() {
  // Read CO2 sensor data
  // ens160.update();
  co2Value = ens160.getECO2();   // read CO2 value
  tvocValue = ens160.getTVOC();  // Read TVOC value

  Serial.print("CO2: ");
  Serial.print(co2Value);
  Serial.print("ppm, TVOC: ");
  Serial.print(tvocValue);
  Serial.println("ppb");

  displayMessage(0, 0, "CO2 Value:", HIGH);
  displayMessage(10, 0, co2Value);
  lcd.print(" ppm ");
  displayMessage(0, 1, "TVOC Value:", LOW);
  displayMessage(12, 1, tOut);
  lcd.print(" ppb  ");
}
void displaySoundWight() {
  // Read sound sensor and Weight sensor
  // Lectura del sensor de sonido
  int soundValue = analogRead(soundSensorPin);
  Serial.print("Nivel de sonido: ");
  Serial.println(soundValue);

  // Lectura de peso
  // get data
  double data = abs(scale.get_units());
  // issue with abs missing at the 4th digit after the dot (bug!!)
  if (0.0000 - data > 0.0001)
    data = 0.00;  //reset to zero


  // serial
  Serial.print(data, number_of_digits);
  Serial.println(" Kg");

  displayMessage(0, 0, "Sound:", HIGH);
  displayMessage(6, 0, soundValue);
  displayMessage(0, 1, "Weight:", LOW);

  lcd.setCursor(8, 1);
  lcd.print(data, number_of_digits);
}
void displayHumidityTemp() {
  // Read inside dht sensor and display on lcd
  hIn = dhtIn.readHumidity();
  tIn = dhtIn.readTemperature();  // or dhtIn.readTemperature(true) for Fahrenheit
  if (isnan(hIn) || isnan(tIn)) {
    Serial.println("Failed to read from Inside DHT sensor!");
    hIn = 0;
    tIn = 0;
  }
  displayMessage(0, 0, "Humidity In:", HIGH);
  displayMessage(12, 0, hIn);
  displayMessage(15, 0, "% ", LOW);
  displayMessage(0, 1, "Temp In:", LOW);
  displayMessage(9, 1, tIn);
  displayMessage(15, 1, "C ", LOW);

  lcd.print(" *C  ");
  delay(lcdUpdateTime);

  // Obtener temperatura y humedad del AHT21
  sensors_event_t humidity, temp;
  aht.getEvent(&humidity, &temp);  // Obtener datos de temperatura y humedad

  // display outside temperature
  hOut = humidity.relative_humidity;
  tOut = temp.temperature;  // or dhtIn.readTemperature(true) for Fahrenheit

  Serial.print("Inside Temp:");
  Serial.print(tIn);
  Serial.print(" | Humidity:");
  Serial.print(hIn);
  Serial.print(" | Outside Temp:");
  Serial.print(tOut);
  Serial.print(" | Humidity:");
  Serial.println(hOut);

  if (isnan(hOut) || isnan(tOut)) {
    Serial.println("Failed to read from Outside DHT sensor!");
    hOut = 0;
    tOut = 0;
  }
  displayMessage(0, 0, "Humidity Out:", HIGH);
  displayMessage(12, 0, hOut);
  displayMessage(15, 0, "% ", LOW);
  displayMessage(0, 1, "Temp Out:", LOW);
  displayMessage(9, 1, tOut);
  displayMessage(15, 1, "C", LOW);
}
void displayMessage(int x, int y, const String& msg, bool clearScreen) {
  // This function will receive cursor x, y position and message to display and a flag to clear previous display or not
  if (clearScreen) {  // If clear screen flag is high clear lcd
    lcd.clear();
    delay(500);  // Wait for 500 seconds
  }
  lcd.setCursor(x, y);  // Set cursor at x, y position
  lcd.print(msg);       // Print msg on lcd
}

void performCalibration() {

  // show instructions
  displayMessage(0, 0, "Clear Scale", HIGH);
  displayMessage(0, 1, "Press Cal Button", LOW);
  // wait till person leaves the calibrationBtnPin
  while (!digitalRead(calibrationBtnPin))
    ;
  //short delay
  delay(500);

  // set tare and save value
  scale.tare();
  currentOffset = scale.get_offset();
  Serial.println(currentOffset);

  // show on lcd
  lcd.clear();
  lcd.print("Place ");
  lcd.print(CALWEIGHT);
  lcd.print(" Kg");
  displayMessage(0, 1, "Press Cal Button", LOW);
  //wait for calibrationBtnPin press
  while (digitalRead(calibrationBtnPin))
    ;
  delay(500);
  displayMessage(0, 0, "Calibration Start");
  displayMessage(2, 1, "Wait");
  Serial.println("calibirte");
  // calibiation
  boolean done = false;
  uint8_t flipDirCount = 0;
  int8_t direction = 1;
  uint8_t dirScale = 100;
  double data = abs(scale.get_units());
  double prevData = data;
  char runningSign[] = { '-', '\\', '|', '/' };
  uint8_t runningSignIdx = 0;
  while (!done) {
    // get data
    data = abs(scale.get_units());
    Serial.println("data = " + String(data, 2));
    Serial.println("abs = " + String(abs(data - CALWEIGHT), 4));
    Serial.println("calibration_factor = " + String(calibration_factor));
    // if not match
    if (abs(data - CALWEIGHT) >= 0.01) {
      if (abs(data - CALWEIGHT) < abs(prevData - CALWEIGHT) && direction != 1 && data < CALWEIGHT) {
        direction = 1;
        flipDirCount++;
      } else if (abs(data - CALWEIGHT) >= abs(prevData - CALWEIGHT) && direction != -1 && data > CALWEIGHT) {
        direction = -1;
        flipDirCount++;
      }

      if (flipDirCount > 2) {
        if (dirScale != 1) {
          dirScale = dirScale / 10;
          flipDirCount = 0;
          Serial.println("dirScale = " + String(dirScale));
        }
      }
      // set new factor
      calibration_factor += direction * dirScale;
      scale.set_scale(calibration_factor / LB2KG);
      // show still running
      lcd.setCursor(15, 1);
      lcd.print(runningSign[runningSignIdx]);
      runningSignIdx = (runningSignIdx + 1) % 4;
      //short delay
      delay(5);
      // keep old data
      prevData = data;
    }
    // if match
    else {
      Serial.println("NEW currentOffset = " + String(currentOffset));
      Serial.println("NEW calibration_factor = " + String(calibration_factor));
      EEPROM.put(0x00, 0x01);  // set init
      EEPROM.put(0x01, currentOffset);
      EEPROM.put(0x01 + sizeof(long), calibration_factor);
      done = true;
      lcd.clear();
    }
    displayMessage(0, 0, "Calibration Done", HIGH);
    delay(2000);  // wait for 2 second
    lcd.clear();  // clear display

  }  // end while

  scale.set_offset(currentOffset);
  displayCount = 0;
  lastLCDUpdateTime = lastLCDUpdateTime - lcdUpdateTime;
  delay(50);
}
void displayMessage(int x, int y, int value) {  // This function will receive cursor x, y position and integer value to display
  lcd.setCursor(x, y);                          // Set cursor at x, y position
  lcd.print(value);                             // Print msg on lcd
  lcd.print("  ");                              // Remove any extra value
}

void displayMessage(int x, int y, float value) {  // This function will receive cursor x, y position and integer value to display
  lcd.setCursor(x, y);                            // Set cursor at x, y position
  lcd.print(value);                               // Print msg on lcd
  lcd.print("  ");                                // Remove any extra value
}