// Define the platform (uncomment the appropriate line)
#define ESP32
//#define ESP8266

#if defined(ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#else
#error "This code is intended to run on ESP8266 or ESP32 platform."
#endif

#include <ArduinoJson.h>

#define DEBUG 1  // 1 ==> Enables Serial prints
#if DEBUG == 1
#define BAUD 9600  //9600 bit/sec
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#define debugF(x, ...) Serial.printf(x, __VA_ARGS__)
#else  //Replace with nothing
#define debug(x)
#define debugln(x)
#define debugF(x, ...)
#endif

char WIFI_SSID[] = "Wokwi-GUEST";  // WiFi SSID
char WIFI_PASSWORD[] = "";         // WiFi Password

const String TIME_API_URL = "https://worldtimeapi.org/api/timezone/";  // Base URL for the API
/*TIMEZONE WILL BE OBTAINED FROM WEATHER API WHEN SELECTING USER LOCATION*/
String TIMEZONE = "Africa/Tunis";  // Path for the specific timezone

/* ******** OLD API **********
// User defined location details (Latitude and Longitude can be obtained from Google Maps)
String USER_LATITUDE = "33.8815"; //Gabes LATITUDE
String USER_LONGITUDE = "10.0982"; //Gabes LONGITUDE
String WEATHER_API_URL = "https://api.open-meteo.com/v1/forecast?latitude=" + USER_LATITUDE + "&longitude=" + USER_LONGITUDE + "&hourly=temperature_2m,relative_humidity_2m,precipitation_probability,rain,wind_speed_10m&timezone=Europe%2FLondon&forecast_days=1";  // URL for the API
*/

// User defined location details
String USER_LOCATION = "Gabes";
String WEATHER_API_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" + USER_LOCATION + "?unitGroup=metric&elements=datetime%2Cname%2Clatitude%2Clongitude%2Ctempmax%2Ctempmin%2Ctemp%2Chumidity%2Cprecip%2Cprecipprob%2Cwindspeed&include=days%2Cfcst&key=GBJT2APDPGR428GZTEPCS4AU9&options=stnslevel1&contentType=json";  // URL for the API

// These variables need to be global to use them in the sendData() | !TODO : sendData()
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minute = 0;

// Extract Date : "2024-06-12"
char date_str[11];  // 10 characters for the date + null terminator

const char* timezone_weatherAPI = "Africa/Tunis";
const char* day_datetime = "2024-06-12";
int day_tempmax = 0;
int day_tempmin = 0;
int day_temp = 0;
int day_humidity = 0;
int day_precip = 0;
int day_precipprob = 0;
int day_windspeed = 0;

unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
unsigned long sec = 90;             //90 sec
unsigned long period = sec * 1000;  // update time&date every 90 sec

int prevHourValue = 0; // Initialize previous value to 0

// Function prototypes
void connectToWiFi();
bool fetchDateTime();
void handleFailedWiFiConnection();

void setup() {
  //Initialize serial and wait for port to open:
#if DEBUG == 1
  // Record the start time
  unsigned long startTime = millis();

  Serial.begin(BAUD);  // Start serial communication

  while (!Serial && (millis() - startTime < 10 * 1000)) {  // 10 sec to avoid infinite loop
    // Wait for serial port to connect.
  }

  if (!(millis() - startTime < 10 * 1000)) {
    // You can blink an LED when failed to open serial communication.
  }
#endif

  debugln(" .: ESP : TIME & WEATHER :. ");  // Print once

  connectToWiFi();  // Connect to the WiFi network

  if (fetchWeather()) {  // Fetch and print weather
    debugln("TIMEZONE fetched successfully from Weather API");
  } else {
    debugln("Failed to fetch TIMEZONE from Weather API");
  }

  startMillis = millis();  //initial start time
  period = 10;             //10 mili_Sec (first time getting time&date)
}

void loop() {
  currentMillis = millis();                   //get the current "time" (actually the number of milliseconds since the program started)
  if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  {
    period = sec * 1000;
    if (fetchDateTime()) {  // Fetch and print the date and time
      debugln("DateTime fetched successfully");
    } else {
      debugln("Failed to fetch DateTime");
    }

    startMillis = currentMillis;  //IMPORTANT to save the start time.
  }

  // Check if the current hour is different from the previous hour
  if (hour != prevHourValue) {
    fetchWeather(); // Call the fetchWeather() function
    prevHourValue = hour; // Update the previous value
  }
}

void connectToWiFi() {
  // Record the start time
  unsigned long startTime = millis();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);  // Start WiFi connection

  debug("Connecting");
  while (WiFi.status() != WL_CONNECTED && (millis() - startTime < 60 * 1000)) {  // Wait for the connection & 60 sec timeout to avoid infinite loop
    delay(500);
    debug(".");
  }

  if (!(millis() - startTime < 60 * 1000)) {
    // You can blink an LED when failed to connect to wifi
    handleFailedWiFiConnection();
    return;
  }

  debugln("");
  debug("Connected to WiFi network with IP Address: ");
  debugln(WiFi.localIP());  // Print local IP address
}

void handleFailedWiFiConnection() {
  debugln("Failed to connect to WiFi after multiple attempts. Restarting...");
  ESP.restart();
}

bool fetchDateTime() {
  HTTPClient http;

#if defined(ESP8266)
  WiFiClient client;                            //ESP8266
  http.begin(client, TIME_API_URL + TIMEZONE);  //ESP8266
#endif

#if defined(ESP32)
  http.begin(TIME_API_URL + TIMEZONE);  //ESP32
#endif

  int httpResponseCode = http.GET();  // Send GET request

  if (httpResponseCode > 0) {                  // Check if the request was successful
    if (httpResponseCode == HTTP_CODE_OK) {    // Check if the response code is 200 (OK)
      String responseData = http.getString();  // Get the response data as a string

      StaticJsonDocument<64> doc;
      DeserializationError error = deserializeJson(doc, responseData);  // Parse JSON data

      if (error) {  // Check if parsing was successful
        debug("deserializeJson() failed: ");
        debugln(error.c_str());
        http.end();  // Close HTTP connection
        return false;
      }

      const char* datetime = doc["datetime"];  // Extract "datetime" field from JSON

      strncpy(date_str, datetime, 10);
      date_str[10] = '\0';  // Ensure null termination

      debug("Extracted date: ");
      debugln(date_str);

      year = atoi(datetime);         // Extract year
      month = atoi(datetime + 5);    // Extract month
      day = atoi(datetime + 8);      // Extract day
      hour = atoi(datetime + 11);    // Extract hour
      minute = atoi(datetime + 14);  // Extract minute

      debug("Year: ");
      debugln(year);
      debug("Month: ");
      debugln(month);
      debug("Day: ");
      debugln(day);
      debug("H: ");
      debugln(hour);
      debug("M: ");
      debugln(minute);

      http.end();   // Close HTTP connection
      return true;  // Return true if successful
    } else {
      debugF("[HTTP] GET... code: %d\n", httpResponseCode);  // Print the HTTP response code if not 200
    }
  } else {
    debugF("[HTTP] GET... failed, error: %s\n", http.errorToString(httpResponseCode).c_str());  // Print the error message if request failed
  }

  http.end();  // Close HTTP connection
  return false;
}

bool fetchWeather() {
  HTTPClient http;

#if defined(ESP8266)
  WiFiClient client;                    //ESP8266
  http.begin(client, WEATHER_API_URL);  //ESP8266
#endif

#if defined(ESP32)
  http.begin(WEATHER_API_URL);  //ESP32
#endif

  int httpResponseCode = http.GET();  // Send GET request

  if (httpResponseCode > 0) {                  // Check if the request was successful
    if (httpResponseCode == HTTP_CODE_OK) {    // Check if the response code is 200 (OK)
      String responseData = http.getString();  // Get the response data as a string

      DynamicJsonDocument doc(3072);

      DeserializationError error = deserializeJson(doc, responseData);

      if (error) {
        Serial.print("deserializeJson() failed: ");
        Serial.println(error.c_str());
        http.en// Define the platform (uncomment the appropriate line)
#define ESP32
//#define ESP8266

#if defined(ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#else
#error "This code is intended to run on ESP8266 or ESP32 platform."
#endif

#include <ArduinoJson.h>

#define DEBUG 1  // 1 ==> Enables Serial prints
#if DEBUG == 1
#define BAUD 9600  //9600 bit/sec
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#define debugF(x, ...) Serial.printf(x, __VA_ARGS__)
#else  //Replace with nothing
#define debug(x)
#define debugln(x)
#define debugF(x, ...)
#endif

char WIFI_SSID[] = "Wokwi-GUEST";  // WiFi SSID
char WIFI_PASSWORD[] = "";         // WiFi Password

const String TIME_API_URL = "https://worldtimeapi.org/api/timezone/";  // Base URL for the API
/*TIMEZONE WILL BE OBTAINED FROM WEATHER API WHEN SELECTING USER LOCATION*/
String TIMEZONE = "Africa/Tunis";  // Path for the specific timezone

/* ******** OLD API **********
// User defined location details (Latitude and Longitude can be obtained from Google Maps)
String USER_LATITUDE = "33.8815"; //Gabes LATITUDE
String USER_LONGITUDE = "10.0982"; //Gabes LONGITUDE
String WEATHER_API_URL = "https://api.open-meteo.com/v1/forecast?latitude=" + USER_LATITUDE + "&longitude=" + USER_LONGITUDE + "&hourly=temperature_2m,relative_humidity_2m,precipitation_probability,rain,wind_speed_10m&timezone=Europe%2FLondon&forecast_days=1";  // URL for the API
*/

// User defined location details
String USER_LOCATION = "Gabes";
String WEATHER_API_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/" + USER_LOCATION + "?unitGroup=metric&elements=datetime%2Cname%2Clatitude%2Clongitude%2Ctempmax%2Ctempmin%2Ctemp%2Chumidity%2Cprecip%2Cprecipprob%2Cwindspeed&include=days%2Cfcst&key=GBJT2APDPGR428GZTEPCS4AU9&options=stnslevel1&contentType=json";  // URL for the API

// These variables need to be global to use them in the sendData() | !TODO : sendData()
int year = 0;
int month = 0;
int day = 0;
int hour = 0;
int minute = 0;

// Extract Date : "2024-06-12"
char date_str[11];  // 10 characters for the date + null terminator

const char* timezone_weatherAPI = "Africa/Tunis";
const char* day_datetime = "2024-06-12";
int day_tempmax = 0;
int day_tempmin = 0;
int day_temp = 0;
int day_humidity = 0;
int day_precip = 0;
int day_precipprob = 0;
int day_windspeed = 0;

unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
unsigned long sec = 90;             //90 sec
unsigned long period = sec * 1000;  // update time&date every 90 sec

int prevHourValue = 0; // Initialize previous value to 0

// Function prototypes
void connectToWiFi();
bool fetchDateTime();
void handleFailedWiFiConnection();

void setup() {
  //Initialize serial and wait for port to open:
#if DEBUG == 1
  // Record the start time
  unsigned long startTime = millis();

  Serial.begin(BAUD);  // Start serial communication

  while (!Serial && (millis() - startTime < 10 * 1000)) {  // 10 sec to avoid infinite loop
    // Wait for serial port to connect.
  }

  if (!(millis() - startTime < 10 * 1000)) {
    // You can blink an LED when failed to open serial communication.
  }
#endif

  debugln(" .: ESP : TIME & WEATHER :. ");  // Print once

  connectToWiFi();  // Connect to the WiFi network

  if (fetchWeather()) {  // Fetch and print weather
    debugln("TIMEZONE fetched successfully from Weather API");
  } else {
    debugln("Failed to fetch TIMEZONE from Weather API");
  }

  startMillis = millis();  //initial start time
  period = 10;             //10 mili_Sec (first time getting time&date)
}

void loop() {
  currentMillis = millis();                   //get the current "time" (actually the number of milliseconds since the program started)
  if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  {
    period = sec * 1000;
    if (fetchDateTime()) {  // Fetch and print the date and time
      debugln("DateTime fetched successfully");
    } else {
      debugln("Failed to fetch DateTime");
    }

    startMillis = currentMillis;  //IMPORTANT to save the start time.
  }

  // Check if the current hour is different from the previous hour
  if (hour != prevHourValue) {
    fetchWeather(); // Call the fetchWeather() function
    prevHourValue = hour; // Update the previous value
  }
}

void connectToWiFi() {
  // Record the start time
  unsigned long startTime = millis();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);  // Start WiFi connection

  debug("Connecting");
  while (WiFi.status() != WL_CONNECTED && (millis() - startTime < 60 * 1000)) {  // Wait for the connection & 60 sec timeout to avoid infinite loop
    delay(500);
    debug(".");
  }

  if (!(millis() - startTime < 60 * 1000)) {
    // You can blink an LED when failed to connect to wifi
    handleFailedWiFiConnection();
    //return;
  }

  debugln("");
  debug("Connected to WiFi network with IP Address: ");
  debugln(WiFi.localIP());  // Print local IP address
}

void handleFailedWiFiConnection() {
  debugln("Failed to connect to WiFi after multiple attempts. Restarting...");
  ESP.restart();
}

bool fetchDateTime() {
  HTTPClient http;

#if defined(ESP8266)
  WiFiClient client;                            //ESP8266
  http.begin(client, TIME_API_URL + TIMEZONE);  //ESP8266
#endif

#if defined(ESP32)
  http.begin(TIME_API_URL + TIMEZONE);  //ESP32
#endif

  int httpResponseCode = http.GET();  // Send GET request

  if (httpResponseCode > 0) {                  // Check if the request was successful
    if (httpResponseCode == HTTP_CODE_OK) {    // Check if the response code is 200 (OK)
      String responseData = http.getString();  // Get the response data as a string

      StaticJsonDocument<64> doc;
      DeserializationError error = deserializeJson(doc, responseData);  // Parse JSON data

      if (error) {  // Check if parsing was successful
        debug("deserializeJson() failed: ");
        debugln(error.c_str());
        http.end();  // Close HTTP connection
        return false;
      }

      const char* datetime = doc["datetime"];  // Extract "datetime" field from JSON

      strncpy(date_str, datetime, 10);
      date_str[10] = '\0';  // Ensure null termination

      debug("Extracted date: ");
      debugln(date_str);

      year = atoi(datetime);         // Extract year
      month = atoi(datetime + 5);    // Extract month
      day = atoi(datetime + 8);      // Extract day
      hour = atoi(datetime + 11);    // Extract hour
      minute = atoi(datetime + 14);  // Extract minute

      debug("Year: ");
      debugln(year);
      debug("Month: ");
      debugln(month);
      debug("Day: ");
      debugln(day);
      debug("H: ");
      debugln(hour);
      debug("M: ");
      debugln(minute);

      http.end();   // Close HTTP connection
      return true;  // Return true if successful
    } else {
      debugF("[HTTP] GET... code: %d\n", httpResponseCode);  // Print the HTTP response code if not 200
    }
  } else {
    debugF("[HTTP] GET... failed, error: %s\n", http.errorToString(httpResponseCode).c_str());  // Print the error message if request failed
  }

  http.end();  // Close HTTP connection
  return false;
}

bool fetchWeather() {
  HTTPClient http;

#if defined(ESP8266)
  WiFiClient client;                    //ESP8266
  http.begin(client, WEATHER_API_URL);  //ESP8266
#endif

#if defined(ESP32)
  http.begin(WEATHER_API_URL);  //ESP32 
#endif

  int httpResponseCode = http.GET();  // Send GET request

  if (httpResponseCode > 0) {                  // Check if the request was successful
    if (httpResponseCode == HTTP_CODE_OK) {    // Check if the response code is 200 (OK)
      String responseData = http.getString();  // Get the response data as a string

      DynamicJsonDocument doc(3072);

      DeserializationError error = deserializeJson(doc, responseData);

      if (error) {
        Serial.print("deserializeJson() failed: ");
        Serial.println(error.c_str());
        http.end();  // Close HTTP connection
        return false;
      }

      int queryCost = doc["queryCost"];                      // 1
      float latitude = doc["latitude"];                      // 33.8813
      float longitude = doc["longitude"];                    // 10.0981
      const char* resolvedAddress = doc["resolvedAddress"];  // "قابس, تونس"
      const char* address = doc["address"];                  // "Gabes"
      timezone_weatherAPI = doc["timezone"];                 // "Africa/Tunis"
      int tzoffset = doc["tzoffset"];                        // 1

      for (JsonObject day : doc["days"].as<JsonArray>()) {

        day_datetime = day["datetime"];      // "2024-06-12", "2024-06-13", "2024-06-14", "2024-06-15", ...
        day_tempmax = day["tempmax"];        // 27.6, 27.9, 27.1, 33.7, 30.5, 30.8, 31.7, 31.3, 32.7, 28.7, 28.8, ...
        day_tempmin = day["tempmin"];        // 23.8, 23.3, 22.8, 21.5, 24.6, 23.6, 24.6, 25.2, 25.3, 24.3, 23.5, ...
        day_temp = day["temp"];              // 25.6, 25.1, 24.8, 27.6, 27.5, 27.1, 28.4, 28.2, 29.4, 27.1, 26, 27, ...
        day_humidity = day["humidity"];      // 72.4, 62.4, 57.9, 47.7, 53.2, 55.5, 52.2, 55.7, 48.5, 60.4, ...
        day_precip = day["precip"];          // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        day_precipprob = day["precipprob"];  // 16.1, 16.1, 3.2, 0, 3.2, 0, 0, 3.2, 3.2, 0, 0, 0, 3.2, 3.2, ...
        day_windspeed = day["windspeed"];    // 22, 28.4, 23.4, 19.1, 22.3, 23.8, 20.9, 23.4, 30.6, 30.2, ...

        /* Print all week weather
        debugln("-----------------------------");
        debugln("DateTime: " + String(day_datetime));
        debugF("Temperature Max: %d°C\n", day_tempmax);
        debugF("Temperature Min: %d°C\n", day_tempmin);
        debugF("Temperature: %d°C\n", day_temp);
        debugF("Humidity: %d%%\n", day_humidity);
        debugF("Precipitation: %d mm\n", day_precip);
        debugF("Precipitation Probability: %d%%\n", day_precipprob);
        debugF("Wind Speed: %d m/s\n", day_windspeed);
        debugln("-----------------------------");
        */
        if (String(day_datetime) == String(date_str)) {  // Print todays weather only
          debugln("-----------------------------");
          debugln("DateTime: " + String(day_datetime));
          debugF("Temperature Max: %d°C\n", day_tempmax);
          debugF("Temperature Min: %d°C\n", day_tempmin);
          debugF("Temperature: %d°C\n", day_temp);
          debugF("Humidity: %d%%\n", day_humidity);
          debugF("Precipitation: %d mm\n", day_precip);
          debugF("Precipitation Probability: %d%%\n", day_precipprob);
          debugF("Wind Speed: %d m/s\n", day_windspeed);
          debugln("-----------------------------");
        } else { 
          debug("TimeZone : ");
          debugln(timezone_weatherAPI);
          debugln("-----------------------------");
          TIMEZONE = timezone_weatherAPI;
          http.end();   // Close HTTP connection
          return true;  //prevent printing multiple times
        }
      }
    } else {
      debugF("[HTTP] GET... code: %d\n", httpResponseCode);  // Print the HTTP response code if not 200
    }
  } else {
    debugF("[HTTP] GET... failed, error: %s\n", http.errorToString(httpResponseCode).c_str());  // Print the error message if request failed
  }

  http.end();  // Close HTTP connection
  return false;
}
d();  // Close HTTP connection
        return false;
      }

      int queryCost = doc["queryCost"];                      // 1
      float latitude = doc["latitude"];                      // 33.8813
      float longitude = doc["longitude"];                    // 10.0981
      const char* resolvedAddress = doc["resolvedAddress"];  // "قابس, تونس"
      const char* address = doc["address"];                  // "Gabes"
      timezone_weatherAPI = doc["timezone"];                 // "Africa/Tunis"
      int tzoffset = doc["tzoffset"];                        // 1

      for (JsonObject day : doc["days"].as<JsonArray>()) {

        day_datetime = day["datetime"];      // "2024-06-12", "2024-06-13", "2024-06-14", "2024-06-15", ...
        day_tempmax = day["tempmax"];        // 27.6, 27.9, 27.1, 33.7, 30.5, 30.8, 31.7, 31.3, 32.7, 28.7, 28.8, ...
        day_tempmin = day["tempmin"];        // 23.8, 23.3, 22.8, 21.5, 24.6, 23.6, 24.6, 25.2, 25.3, 24.3, 23.5, ...
        day_temp = day["temp"];              // 25.6, 25.1, 24.8, 27.6, 27.5, 27.1, 28.4, 28.2, 29.4, 27.1, 26, 27, ...
        day_humidity = day["humidity"];      // 72.4, 62.4, 57.9, 47.7, 53.2, 55.5, 52.2, 55.7, 48.5, 60.4, ...
        day_precip = day["precip"];          // 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        day_precipprob = day["precipprob"];  // 16.1, 16.1, 3.2, 0, 3.2, 0, 0, 3.2, 3.2, 0, 0, 0, 3.2, 3.2, ...
        day_windspeed = day["windspeed"];    // 22, 28.4, 23.4, 19.1, 22.3, 23.8, 20.9, 23.4, 30.6, 30.2, ...

        /* Print all week weather
        debugln("-----------------------------");
        debugln("DateTime: " + String(day_datetime));
        debugF("Temperature Max: %d°C\n", day_tempmax);
        debugF("Temperature Min: %d°C\n", day_tempmin);
        debugF("Temperature: %d°C\n", day_temp);
        debugF("Humidity: %d%%\n", day_humidity);
        debugF("Precipitation: %d mm\n", day_precip);
        debugF("Precipitation Probability: %d%%\n", day_precipprob);
        debugF("Wind Speed: %d m/s\n", day_windspeed);
        debugln("-----------------------------");
        */
        if (String(day_datetime) == String(date_str)) {  // Print todays weather only
          debugln("-----------------------------");
          debugln("DateTime: " + String(day_datetime));
          debugF("Temperature Max: %d°C\n", day_tempmax);
          debugF("Temperature Min: %d°C\n", day_tempmin);
          debugF("Temperature: %d°C\n", day_temp);
          debugF("Humidity: %d%%\n", day_humidity);
          debugF("Precipitation: %d mm\n", day_precip);
          debugF("Precipitation Probability: %d%%\n", day_precipprob);
          debugF("Wind Speed: %d m/s\n", day_windspeed);
          debugln("-----------------------------");
        } else { 
          debug("TimeZone : ");
          debugln(timezone_weatherAPI);
          debugln("-----------------------------");
          TIMEZONE = timezone_weatherAPI;
          http.end();   // Close HTTP connection
          return true;  //prevent printing multiple times
        }
      }
    } else {
      debugF("[HTTP] GET... code: %d\n", httpResponseCode);  // Print the HTTP response code if not 200
    }
  } else {
    debugF("[HTTP] GET... failed, error: %s\n", http.errorToString(httpResponseCode).c_str());  // Print the error message if request failed
  }

  http.end();  // Close HTTP connection
  return false;
}