
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <ArduinoJson.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <WiFiClientSecure.h>
#include <WiFi.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);

const char *ssid     = "aBcAdfh"; //inserire nome rete WiFi
const char *password = "wyZ23tX"; //inserire password rete WiFi
const String apiKey = "d5g8d4c3380283728b5d22f5d5df8bd9";//inserire codice API OWM
const String location = "Milano,it";// inserire nome località e stato
const char *clientAdress = "api.openweathermap.org";

int strMinTemp, strMaxTemp, strCurTemp, strFeelTemp, strSunrise, strSunset;
DynamicJsonDocument jsonDoc(2000);
int utcOffsetInSeconds = 3600;

String daysOfTheWeek[7] = {"DOM", "LUN", "MAR", "MER", "GIO", "VEN", "SAB"};
time_t tmLastFullMoon;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", utcOffsetInSeconds);

bool firstrun = true;
bool bUpdateDisplay = true;

void setup() {
  Serial.begin(115200);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
  {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  WiFi.begin(ssid, password);
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }

  tmLastFullMoon = tmConvert(2020, 10, 31, 15, 51, 29);
  Serial.println("");
  Serial.println("Last moon: " + String(tmLastFullMoon));
  Serial.println("Connected to Wifi with IP: " + WiFi.localIP().toString());
  timeClient.begin();
}

void loop() {

  WeatherUpdate();
  TimeUpdate();
  DisplayUpdate();
  if (firstrun)
    firstrun = false;
}

void TimeUpdate()
{
  static bool bUpdateDone;
  if ((firstrun || (minute() % 2) == 0) && !bUpdateDone)
  {
    bool bUpdate = timeClient.update();
    setTime(timeClient.getEpochTime());
    Serial.print("Update time: ");
    if (bUpdate) {
      Serial.println("Success");
    }
    else {
      Serial.println("Failed");
    }
    bUpdateDone = true;
  }
  if ((minute() % 2) != 0)
    bUpdateDone = false;
}

void WeatherUpdate()
{
  static bool bUpdateDone;
  if ((firstrun || (minute() % 5) == 0) && !bUpdateDone)
  {
    jsonDoc.clear();
    String strRequestData = RequestWeather();
    Serial.println("Received data: " + strRequestData);

    if (strRequestData != "")
    {
      DeserializationError error = deserializeJson(jsonDoc, strRequestData);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
        return;
      }

      strMinTemp = RoundTemp(jsonDoc["main"]["temp_min"].as<double>());
      strMaxTemp = RoundTemp(jsonDoc["main"]["temp_max"].as<double>());
      strCurTemp = RoundTemp(jsonDoc["main"]["temp"].as<double>());
      strFeelTemp = RoundTemp(jsonDoc["main"]["feels_like"].as<double>());
      strSunrise  = jsonDoc["sys"]["sunrise"].as<int>();
      strSunset   = jsonDoc["sys"]["sunset"].as<int>();
      static long timeZone = jsonDoc["timezone"];

      Serial.println("Min Temp:  " + strMinTemp);
      Serial.println("Max Temp:  " + strMaxTemp);
      Serial.println("Cur Temp:  " + strCurTemp);
      Serial.println("Feel Temp: " + strFeelTemp);

      if (timeZone != utcOffsetInSeconds)
      {
        utcOffsetInSeconds = timeZone;
        timeClient.setTimeOffset(utcOffsetInSeconds);
      }
    }
    bUpdateDone = true;
    bUpdateDisplay = true;
  }
  if ((minute() % 5) != 0)
    bUpdateDone = false;
}

String RequestWeather()
{
  WiFiClientSecure client;
  if (!client.connect(clientAdress, 443)) {
    Serial.println("Failed to connect");
    return "";
  }

  String path = "/data/2.5/weather?q=" + location + "&lang=it&units=metric&appid=" + apiKey;

  client.print(
    "GET " + path + " HTTP/1.1\r\n" +
    "Host: " + clientAdress + "\r\n" +
    "Connection: close\r\n" +
    "Pragma: no-cache\r\n" +
    "Cache-Control: no-cache\r\n" +
    "User-Agent: ESP32\r\n" +
    "Accept: text/html,application/json\r\n\r\n");

  uint64_t startMillis = millis();
  while (client.available() == 0) {
    if (millis() - startMillis > 2000) {
      Serial.println("Client timeout");
      client.stop();
      return "";
    }
  }

  String resHeader = "", resBody = "";
  bool receivingHeader = true;
  while (client.available()) {
    String line = client.readStringUntil('\r');
    if (line.length() == 1 && resBody.length() == 0) {
      receivingHeader = false;
      continue;
    }
    if (receivingHeader) {
      resHeader += line;
    }
    else {
      resBody += line;
    }
  }

  client.stop();
  return resBody;
}

int RoundTemp(double dTemp)
{
  return int(dTemp + 0.5);
}

void DisplayUpdate()
{
  static int iLastMinute;
  if (iLastMinute != minute() || firstrun || bUpdateDisplay) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println(String(daysOfTheWeek[weekday() - 1]) + " " + GetDigits(day()) +
                    String(".") + GetDigits(month()) + String(".") + year() +
                    "  " + GetDigits(hour()) + String(":") + GetDigits(minute()));
    display.println(String("MINIMA  -----   ") + strMinTemp  + String(" C"));
    display.println(String("MASSIMA -----   ") + strMaxTemp  + String(" C"));
    display.println(String("ATTUALE -----   ") + strCurTemp  + String(" C"));
    display.println(String("PERCEPITA ---   ") + strFeelTemp + String(" C"));
    display.cp437(true);
    display.write(int16_t(30));
    display.println(" SOLE ------  " + GetTimeAsString(strSunrise, utcOffsetInSeconds));
    display.write(int16_t(31));
    display.println(" SOLE ------  " + GetTimeAsString(strSunset, utcOffsetInSeconds));
    display.display();
    display.setCursor(19, 57);
    display.println(GetMoonPhase());
    display.display();

    Serial.println("---------------------");
    Serial.println("EpochTime: " + String(timeClient.getEpochTime()));
    Serial.println(String(daysOfTheWeek[weekday() - 1]) + String(". ") +
                   GetDigits(day()) + String(".") + GetDigits(month()) +
                   String(".") + year());
    Serial.println(GetDigits(hour()) + String(":") + GetDigits(minute()));
    Serial.println(String("MINIMA:-------------     ") + strMinTemp);
    Serial.println(String("MASSIMA:------------     ") + strMaxTemp);
    Serial.println(String("ATTUALE:------------     ") + strCurTemp);
    Serial.println(String("PERCEPITA:----------     ") + strFeelTemp);
    Serial.println(String("SOLE SORGE:---------     ") + GetTimeAsString(strSunrise, utcOffsetInSeconds));
    Serial.println(String("SOLE TRAMONTA:------     ") + GetTimeAsString(strSunset, utcOffsetInSeconds));
    Serial.println(String("FASE LUNARE:--------     ") + GetMoonPhase());
    iLastMinute = minute();
    bUpdateDisplay = false;
  }
}

String GetDigits(int iValue)
{
  String rValue = "";
  if (iValue < 10)
    rValue += "0";
  rValue += iValue;
  return rValue;
}

time_t tmConvert(int iYear, byte byMonth, byte byDay, byte byHour, byte byMinute, byte bySecond)
{
  tmElements_t tmSet;
  tmSet.Year = iYear - 1970;
  tmSet.Month = byMonth;
  tmSet.Day = byDay;
  tmSet.Hour = byHour;
  tmSet.Minute = byMinute;
  tmSet.Second = bySecond;
  return makeTime(tmSet);
}

String GetTimeAsString(int iValue,  int iOffset)
{
  if (iValue > 0) {
    iValue += iOffset;
    return GetDigits(hour(iValue)) + ":" + GetDigits(minute(iValue));
  }
  return "";
}

double MoonUpdate()
{
  double dDaySinceLastFullmoon = (now() / 86400 - tmLastFullMoon / 86400) / 29.53;
  int iToHundret = (dDaySinceLastFullmoon - int(dDaySinceLastFullmoon)) * 100;
  return (double) iToHundret / 100;
}

String GetMoonPhase()
{
  static double dMoonphase = MoonUpdate();
  Serial.println("Risultato Calcolato Fase Lunare: " + String(dMoonphase, 3));
  if (dMoonphase == 0.0)
    dMoonphase = 1.00;

  if (dMoonphase < double(0.25))
  {
    return "LUNA CALANTE";
  }
  else if (dMoonphase == 0.25)
  {
    return "LUNA CRESCENTE";
  }
  else if (0.25 < dMoonphase && dMoonphase < 0.50)
  {
    return "FALCE CALANTE";
  }
  else if (dMoonphase == 0.50)
  {
    return "LUNA NUOVA";
  }
  else if (0.50 < dMoonphase && dMoonphase < 0.75)
  {
    return "FALCE CRESCENTE";
  }
  else if (dMoonphase == 0.75)
  {
    return "MEZZA CRESCENTE";
  }
  else if (0.75 < dMoonphase && dMoonphase < 1.00)
  {
    return "LUNA CRESCENTE";
  }
  else if (dMoonphase == 1)
  {
    return "LUNA PIENA";
  }
}
