/* This is the Arduino Sketch for AMBI: The Ambient Environment Buddy
 * AMBI 1.0 senses temperature, pressure, humidity, air quality, and light intensity data
 * from sensors connected via I2C to an XIAO processor and displays the readings on an OLED display.
 * Then it interprets the comfort level of the data and displays on a Winky Eyelid display
 * It switches between the normal Winky display to numerical readings based on hand/body movement
*/

#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include "Air_Quality_Sensor.h"
#include <Adafruit_Sensor.h>

AirQualitySensor sensor(A0);
int airQuality = 0;

//The following are new in AMBI
  #include <Adafruit_BME280.h>
  #define BME280_ADDRESS (0x76) //Defines the I2C address for the BME280 sensor
  #define PIR_MOTION_SENSOR D1
  //redefining the GUS TOUCH_PIN to be dayLight_PIN
  #define daylight_PIN D2  

  Adafruit_BME280 bme; // I2C temp, humidity, pressure sensor

  const int pirPin = D1; 
  const int dayLight_PIN = D2;  //Grove light sensor pin
  String state;  //"state" is a string variable with values day/night

// This is the display for the specific display. This has to be changed if a different display or resolution is used
U8G2_SH1107_SEEED_128X128_1_HW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
 
// Blink parameters
unsigned long nextBlinkTime = 0;
bool isBlinking = true; //Start in the blinking state
unsigned long blinkInterval = 30000; // Interval between blinks (30 sec)
//CHANGED BLINK DURATION FROM 3000 TO 10000
const unsigned long blinkDuration = 100000;  // Duration of each blink (3 seconds)
const int blinkCount = 2;                 // Number of blinks per sequence
unsigned long lastBlinkTime = 0;
int currentBlink = 0;

/*----------------------------------Setup Routine---------------------------------*/
void setup() {
//This can be as low as 9600
Serial.begin(115200);
pinMode(dayLight_PIN, INPUT);  // Set the dayLight pin as INPUT (it is not an I2C sensor)
pinMode(pirPin, INPUT); // Set the PIR sensor pin as an input (it is not an I2C sensor)

Wire.begin(); // Initialize the I2C bus
// Set up LCD display
  u8g2.begin();
//Start of setup display during loading
  u8g2.setDrawColor(1);
  // u8g2.setFont(u8g2_font_helvB12_tr);
   u8g2.setFont(u8g2_font_fub14_tr); 

  /*-----------------------Show initial loading progress on LCD----------------------*/    
  int load = 0;
  while(load < 20)
  {
    u8g2.firstPage(); // Start the page drawing process
    do {
      u8g2.setCursor(0, 50);
      u8g2.println("AMBI");
      u8g2.setCursor(0, 70);
      u8g2.println("Energy Buddy");
      u8g2.setCursor(10, 90);   //was 10,90
      // Print dots dynamically within the page loop to update each frame
      for (int i = 0; i <= load; i++) {
        u8g2.print(".");
      }
    } while (u8g2.nextPage()); // Send the buffer to the display
    delay(500);
    load++;
  }
  u8g2.clearBuffer(); // Clear buffer after loading is complete
// ------------------------End Initial Setup Display---------------------------------

  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  if (sensor.init()) {
        Serial.println("Sensor ready.");
  } else {
        Serial.println("Sensor ERROR!");
    }
}
/* ---------------------------------END OF SETUP------------------------------------------------ */

/*----------------------------------Sensor Reading Loop-------------------------------------------*/
void loop() {
  /* This loop reads and updates sensor values every second. They are
     also printed to the Serial Monitor, but not the LCD display until the next 
     room movement detected by the PIR sensor
  */
  
//This is a heading for the serial monitor printing
  Serial.println(); //Spacer between reading cycles
  Serial.println("RealTime Sensor Values");  //Note these print to the serial display, not the OLED
  
//Temperature from BME280 sensor
  float tempC = bme.readTemperature(); //read temperture in C from BME280 sensor
  Serial.print("Temperature = "); // Test print to Seial monitor  of temperature and converted to F
  float temp =  ((tempC * 1.8) + 32.0);  //Convert from C to F temps
  Serial.print(temp);
  Serial.println(" F");

//Barometric pressure from BME280 sensor
  float pres_P = bme.readPressure();  //read barometric pressure in Pascals from BME280 sensor
  float pres = (pres_P * 0.000295299); //  in. of mercury
  Serial.print("Pressure = "); // Test print to Seial monitor
  Serial.print(pres);
  Serial.println(" inHg"); //traditionally in "inches of mercury (Hg)"

//Humidity from BME280 sensor
  float humidity = bme.readHumidity(); // get humidity in rH%
  Serial.print("Humidity = "); // Test print to Seial monitor
  Serial.print(humidity);
  Serial.println(" %");

//Air Quallity from AQ Sensor (note scale from 0=fresh, to 3=polluted)
int quality = sensor.slope();  //Air quality sensor
  if (quality == AirQualitySensor::FORCE_SIGNAL) {
      airQuality = 3; // very high pollution
  } else if (quality == AirQualitySensor::HIGH_POLLUTION) {
      airQuality = 2; // high pollution
  } else if (quality == AirQualitySensor::LOW_POLLUTION) {
      airQuality = 1; // moderate pollution
  } else if (quality == AirQualitySensor::FRESH_AIR) {
      airQuality = 0; // fresh air
  }
  Serial.print("Air: "); // Test print to Seial monitor
  Serial.print(airQuality);
  Serial.println();

//Room light (day/night) sensor
  // This sensor has an analog value of the light intensity from 0-4095 
  // Higher means brighter light.
  // Typically, day ~>3000, night ~< 700 
  int dayLight = analogRead(dayLight_PIN); 
  if (dayLight > 700) {
    state = "Day Time";
  } else {
    state = "Night Time";
  }
  Serial.print("Day Light "); // Test print to Seial monitor
  Serial.println(dayLight);
  Serial.print("State: ");
  Serial.println(state);

// Room motion sensed by PIR SENSOR
    //GUS originally used TOUCH sensor, but I found this unreliable through the plastic case
   //substituted movement for isTouched variable
   bool movement = digitalRead(pirPin);
   delay(100); 

/*-----------------------------------Display Mode Decision Based on Hand/Body Movement-------------------*/
// THIS IS THE KEY LOOP that shifts from normal Winky EYES to test sensor values
// This can be easilly triggered to show text by waving a hand infront of the sensor
  
  
  int comfortLevel = 0;
  if (movement) {
// If "movement" OLED display will have fully open eyes for 2 seconds
   updateDisplay(0);  //Friendly "I'm Here Eyes"
  delay(2000);
//Then display the numerical data on the OLED
    displaySensorData(temp, humidity, pres, airQuality, comfortLevel, dayLight);
  } else {
    //If no movement, call this function to calculate new comfort level from new sensor values & DISPLAY as EYEBALLS
    //In this initial version only the airQ, temp and humidity variables are considered
    int comfortLevel = calculateComfortLevel(airQuality, temp, humidity);

   // Display Eyeballs at with height set by new comfortLevel
    updateDisplay(comfortLevel);
  }
//THIS DELAY CONTROLS how often the sensing restarts and the Eyes blink (THE BLINK RATE)delay(3000);
delay(3000);   //This delay sets the overall refresh rate
}

/*-----------------------------------Sensor Reading Classification------------------------------------------*/
// Classify the comfort level for diffewrebt sensor values
// This takes the sensor numbers and classifies each type by a number from 0 to 4, 0 being most comfortable and 4 being least
int calculateComfortLevel(int air, float temp, float humid) {
  int airLevel = air;  //int values from 0 to 3
  int tempLevel = (temp >= 65 && temp <=75) ? 0 : (temp  > 75 && temp < 80) ? 1 : (temp < 55) ? 2 : 3;
  int humidLevel = (humid >= 0 && humid <= 40) ? 0 : (humid >= 40 && humid <= 50) ? 1 : (humid >= 50 && humid <= 80) ? 2 : 3;

// The max value is taken as the overall (worst/hightes) dominant comfort level number
  return max(max(airLevel, tempLevel), humidLevel);
}

/*--------------------------------THE MAIN EYEBALL DISPLAY LOOP----------------------------------*/
void updateDisplay(int comfortLevel) {

  Serial.print("Comfort Level ");  //NOTE-THIS IS ON SERIAL MONITOR
  Serial.print(comfortLevel);
  Serial.println();

//If isBlinking true it will blink, if false it will stop blinking
//isBlinking = true;
isBlinking = true;
  if (isBlinking){
        u8g2.firstPage();
        do {
          drawEyes(0, true);
        } while (u8g2.nextPage());     
} 

/*----------------------------EYELID Height Decision based on Comfort Level--------------------*/
//IF comfort level is 0 THEN eyelidHeight FULL HEIGHT (0), 30 HALF HEIGHT, 60 FULL BLINK
  int eyelidHeight = 0;
  if (comfortLevel == 0) {eyelidHeight = 0;}
  else
  if (comfortLevel == 1 or comfortLevel == 2) {eyelidHeight = 30;}
  else
  if (comfortLevel == 3) {eyelidHeight = 54;} // Almost closed

  u8g2.firstPage();
  do {
    drawEyes(eyelidHeight, false);
  } while (u8g2.nextPage());
}

void drawEyes(int eyelidHeight, bool isBlinking) {
  const int leftEyeX = 32;
  const int rightEyeX = 96;
  const int eyeY = 64;
  const int eyeRadius = 30;
  const int eyeVerticalRadius = eyeRadius * 1.4; // Make eyes longer vertically
  const int lineWidth = 30;  // Width of the blink line
  const int lineThickness = 4;  // Number of horizontal lines to draw for thickness

  u8g2.setDrawColor(0);
  u8g2.drawBox(0, 0, 128, 128);

  // eyes
  u8g2.setDrawColor(1);

  if (isBlinking) {
    // Draw horizontal lines (multiple for thickness)
    for(int i = 0; i < lineThickness; i++) {
      // Left eye line
      u8g2.drawHLine(leftEyeX - lineWidth/2, eyeY + eyeVerticalRadius - lineThickness/2 + i, lineWidth);
      // Right eye line
      u8g2.drawHLine(rightEyeX - lineWidth/2, eyeY + eyeVerticalRadius - lineThickness/2 + i, lineWidth);
    }
  }

  else{
    // Draw left eye as a filled ellipse, longer vertically
    u8g2.drawFilledEllipse(leftEyeX, eyeY, eyeRadius, eyeVerticalRadius);
    // Draw right eye as a filled ellipse, longer vertically
    u8g2.drawFilledEllipse(rightEyeX, eyeY, eyeRadius, eyeVerticalRadius);

    // pupils
    u8g2.setDrawColor(0);
    // Draw left pupil slightly to the right of the center for cartoonish effect
    u8g2.drawDisc(leftEyeX + 8, eyeY, 8);
    // Draw right pupil slightly to the left of the center for cartoonish effect
    u8g2.drawDisc(rightEyeX - 8, eyeY, 8);

  // eyelids
  //This is where the eyelid covering is determined
    u8g2.setDrawColor(0);
    // Top of the eyelid is based on the vertical radius
    int eyeTop = eyeY - eyeVerticalRadius;

    // to test, make eyelidHeight = 0 for full height
    u8g2.drawBox(0, eyeTop, 128, eyelidHeight);
  }
}

/*------------------------------Text version of Sensor Dada Display on OLED----------------------------*/
// When "movement" the Eyeball display switches to this display of the sensor data on the OLED display
void displaySensorData(float temp, float humid, float pres, int airQuality, int comfortLevel, int dayLight) {
  u8g2.firstPage();
  do {
    u8g2.setDrawColor(1);
    u8g2.setFont(u8g2_font_helvB10_tr);  
    // changed 15 to 0
    u8g2.setCursor(10, 15);
    u8g2.print("Temp: ");
        // u8g2.setCursor(10, 35);
    u8g2.print(temp);
    u8g2.print(" F");

//Change 55 to 25
    u8g2.setCursor(10, 40);
    u8g2.print("Hum: ");
    u8g2.print(humid);
    u8g2.print(" %");

    u8g2.setCursor(10, 65);
    u8g2.print("Baro: ");
    u8g2.print(pres);
    u8g2.print(" inHg");

    String airQ = (airQuality == 0) ? "Clear" : (airQuality == 1) ? "Low Pollution" : (airQuality == 2) ? "Hi Pollution" : "Ext Polluted";
    u8g2.setCursor(10, 85);
    u8g2.print("Air: ");
    u8g2.print(airQ);

    String comfortQ = (comfortLevel == 0) ? "Very" : (comfortLevel == 1) ? "Comfortable" : (comfortLevel == 2) ? "Moderate" : "Uncomfortable";
    u8g2.setCursor(10, 105);
    u8g2.print("Comfort: ");
    u8g2.println(comfortQ);

    //State values are day or night 
    u8g2.setCursor(10, 125);
    u8g2.print("State: ");
    u8g2.print(state);

  } while (u8g2.nextPage());
}