/*
  Example #6: BME280 Data Logging to Google Sheets
  
  This example logs temperature, humidity, and pressure data from a BME280 sensor
  to Google Sheets using the GoogleSheetClient wrapper class.
  
  Adapted from Rui Santos example at 
  https://RandomNerdTutorials.com/esp32-datalogging-google-sheets/
*/

#include <Arduino.h>
#include <WiFi.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "time.h"
#include "google-sheets.h"  // Include our wrapper class instead of ESP_Google_Sheet_Client directly

#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

// Google Project ID
#define PROJECT_ID "REPLACE_WITH_YOUR_PROJECT_ID"

// Service Account's client email
#define CLIENT_EMAIL "REPLACE_WITH_YOUR_CLIENT_EMAIL"

// Service Account's private key
const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nREPLACE_WITH_YOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----\n";

// The ID of the spreadsheet where you'll publish the data
const char spreadsheetId[] = "YOUR_SPREADSHEET_ID";

// Timer variables
unsigned long lastTime = 0;  
unsigned long timerDelay = 30000;  // Send data every 30 seconds

// Create an instance of our GoogleSheetClient wrapper
GoogleSheetClient sheets;

// Token Callback function
void tokenStatusCallback(TokenInfo info);

// BME280 I2C
Adafruit_BME280 bme;
// Variables to hold sensor readings
float temp;
float hum;
float pres;

// NTP server to request epoch time
const char* ntpServer = "pool.ntp.org";

// Variable to save current epoch time
unsigned long epochTime; 

// Function that gets current epoch time
unsigned long getTime() {
  time_t now;
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    //Serial.println("Failed to obtain time");
    return(0);
  }
  time(&now);
  return now;
}

void setup(){
    Serial.begin(115200);
    Serial.println();
    Serial.println("BME280 Google Sheets Logger using GoogleSheetClient wrapper");
    Serial.println();

    //Configure time
    configTime(0, 0, ntpServer);

    // Initialize BME280 sensor 
    if (!bme.begin(0x76)) {
      Serial.println("Could not find a valid BME280 sensor, check wiring!");
      while (1);
    }
    Serial.println("BME280 sensor initialized successfully");

    // Connect to Wi-Fi
    WiFi.setAutoReconnect(true);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  
    Serial.print("Connecting to Wi-Fi");
    while (WiFi.status() != WL_CONNECTED) {
      Serial.print(".");
      delay(1000);
    }
    Serial.println();
    Serial.print("Connected with IP: ");
    Serial.println(WiFi.localIP());
    Serial.println();

    // Set the callback for Google API access token generation status (for debug only)
    sheets.setTokenCallback(tokenStatusCallback);

    // Set the seconds to refresh the auth token before expire (60 to 3540, default is 300 seconds)
    sheets.setPrerefreshSeconds(10 * 60);

    // Begin the access token generation for Google API authentication using our wrapper
    if (sheets.begin(CLIENT_EMAIL, PROJECT_ID, PRIVATE_KEY)) {
        Serial.println("Google Sheets client initialized successfully!");
    } else {
        Serial.println("Failed to initialize Google Sheets client");
    }
}

void loop(){
    // Process Google Sheets client using our wrapper
    sheets.loop();

    // Check if client is ready and it's time to send data
    if (sheets.ready() && millis() - lastTime > timerDelay){
        lastTime = millis();

        Serial.println("\nAppend spreadsheet values...");
        Serial.println("----------------------------");

        // Get new BME280 sensor readings
        temp = bme.readTemperature();
        //temp = 1.8*bme.readTemperature() + 32;  // Uncomment for Fahrenheit
        hum = bme.readHumidity();
        pres = bme.readPressure()/100.0F;
        
        // Get timestamp
        epochTime = getTime();
        
        // Display readings for debugging
        Serial.println("Temperature: " + String(temp) + " °C");
        Serial.println("Humidity: " + String(hum) + " %");
        Serial.println("Pressure: " + String(pres) + " hPa");
        Serial.println("Timestamp: " + String(epochTime));

        // Create value range for appending
        FirebaseJson valueRange;
        valueRange.add("majorDimension", "COLUMNS");
        valueRange.set("values/[0]/[0]", epochTime);
        valueRange.set("values/[1]/[0]", temp);
        valueRange.set("values/[2]/[0]", hum);
        valueRange.set("values/[3]/[0]", pres);

        // Append values to the spreadsheet using our wrapper
        bool success = sheets.appendValues(spreadsheetId, "Sheet1!A1", &valueRange);
        
        if (success){
            Serial.println("Data successfully appended to spreadsheet!");
            
            // Print response details
            FirebaseJson* response = sheets.getResponse();
            if (response) {
                response->toString(Serial, true);
            }
            
            valueRange.clear();
        }
        else{
            Serial.println("Error appending data: " + sheets.errorReason());
        }
        
        Serial.println();
        Serial.println("Free heap memory: " + String(ESP.getFreeHeap()));
    }
}

// Callback function for token status (for debug only)
void tokenStatusCallback(TokenInfo info){
    if (info.status == token_status_error){
        Serial.printf("Token info: type = %s, status = %s\n", GSheet.getTokenType(info).c_str(), GSheet.getTokenStatus(info).c_str());
        Serial.printf("Token error: %s\n", GSheet.getTokenError(info).c_str());
    }
    else{
        Serial.printf("Token info: type = %s, status = %s\n", GSheet.getTokenType(info).c_str(), GSheet.getTokenStatus(info).c_str());
    }
}