#include <Wire.h>
#include <SparkFunBME280.h>
#include <SparkFunCCS811.h>
#include <Stepper.h>
#include "esp_sleep.h"

// =====================================================
// CCS811 + BME280
// =====================================================
#define CCS811_ADDR 0x5B

CCS811 myCCS811(CCS811_ADDR);
BME280 myBME280;

// =====================================================
// Stepper Motor
// =====================================================
const int stepsPerRevolution = 2048;

// ULN2003 Pins
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 10

Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);

// 180 degrees
const int STEPS_180 = stepsPerRevolution / 2;

// =====================================================
// Air Quality Thresholds
// =====================================================
const int CO2_LIMIT = 1000;   // ppm
const int TVOC_LIMIT = 300;   // ppb

// =====================================================
// Sampling
// =====================================================
const int NUM_SAMPLES = 5;
const int SAMPLE_INTERVAL_MS = 1000;

// =====================================================
// Deep Sleep
// =====================================================
#define uS_TO_S_FACTOR 1000000ULL
#define SLEEP_TIME_SEC 300   // 5 minutes

// =====================================================
// Retained During Deep Sleep
// false = vent closed
// true  = vent open
// =====================================================
RTC_DATA_ATTR bool ventOpen = false;

// =====================================================
void rotate180()
{
  Serial.println("Rotating motor 180 degrees...");

  myStepper.setSpeed(8);
  myStepper.step(STEPS_180);

  delay(500);
}

// =====================================================
void goToSleep()
{
  Serial.println();
  Serial.println("Going to sleep for 5 minutes...");
  Serial.flush();

  esp_sleep_enable_timer_wakeup(
      SLEEP_TIME_SEC * uS_TO_S_FACTOR);

  esp_deep_sleep_start();
}

// =====================================================
bool initializeSensors()
{
  Wire.begin();

  Serial.println("Initializing BME280...");
  myBME280.beginI2C();

  Serial.println("Initializing CCS811...");

  CCS811Core::CCS811_Status_e status =
      myCCS811.beginWithStatus();

  Serial.print("CCS811 Status: ");
  Serial.println(myCCS811.statusString(status));

  if (status != CCS811Core::CCS811_Stat_SUCCESS)
  {
    Serial.println("CCS811 initialization failed!");
    return false;
  }

  return true;
}

// =====================================================
void setup()
{
  Serial.begin(115200);
  delay(1000);

  Serial.println();
  Serial.println("================================");
  Serial.println("Air Quality Vent Controller");
  Serial.println("================================");

  if (!initializeSensors())
  {
    goToSleep();
  }

  // -----------------------------------------
  // Stabilization delay
  // -----------------------------------------
  Serial.println();
  Serial.println("Waiting 10 seconds for sensors...");
  delay(10000);

  uint32_t co2Sum = 0;
  uint32_t tvocSum = 0;
  int validSamples = 0;

  Serial.println();
  Serial.println("Collecting samples...");

  for (int i = 0; i < NUM_SAMPLES; i++)
  {
    float tempC = myBME280.readTempC();
    float humidity = myBME280.readFloatHumidity();

    myCCS811.setEnvironmentalData(
        humidity,
        tempC);

    if (myCCS811.dataAvailable())
    {
      myCCS811.readAlgorithmResults();

      uint16_t co2 = myCCS811.getCO2();
      uint16_t tvoc = myCCS811.getTVOC();

      Serial.print("Sample ");
      Serial.print(i + 1);
      Serial.print(": CO2=");
      Serial.print(co2);
      Serial.print(" ppm, TVOC=");
      Serial.print(tvoc);
      Serial.println(" ppb");

      co2Sum += co2;
      tvocSum += tvoc;
      validSamples++;
    }
    else
    {
      Serial.print("Sample ");
      Serial.print(i + 1);
      Serial.println(": No data available");
    }

    delay(SAMPLE_INTERVAL_MS);
  }

  if (validSamples == 0)
  {
    Serial.println("No valid CCS811 readings.");
    goToSleep();
  }

  uint16_t avgCO2 = co2Sum / validSamples;
  uint16_t avgTVOC = tvocSum / validSamples;

  Serial.println();
  Serial.println("========== AVERAGE ==========");
  Serial.print("Average CO2  : ");
  Serial.print(avgCO2);
  Serial.println(" ppm");

  Serial.print("Average TVOC : ");
  Serial.print(avgTVOC);
  Serial.println(" ppb");

  bool airBad =
      (avgCO2 > CO2_LIMIT) ||
      (avgTVOC > TVOC_LIMIT);

  Serial.println();

  if (airBad)
  {
    Serial.println("Air quality = POOR");
  }
  else
  {
    Serial.println("Air quality = GOOD");
  }

  // -----------------------------------------
  // Open vent if air becomes bad
  // -----------------------------------------
  if (airBad && !ventOpen)
  {
    Serial.println("Vent is CLOSED.");
    Serial.println("Opening vent...");

    rotate180();

    ventOpen = true;

    Serial.println("Vent OPEN.");
  }

  // -----------------------------------------
  // Close vent if air becomes good
  // -----------------------------------------
  else if (!airBad && ventOpen)
  {
    Serial.println("Vent is OPEN.");
    Serial.println("Closing vent...");

    rotate180();

    ventOpen = false;

    Serial.println("Vent CLOSED.");
  }

  else
  {
    Serial.println("No motor action required.");
  }

  goToSleep();
}

void loop()
{
  // Never reached because ESP32 enters deep sleep
}
