/*
Details at 
https://www.hackster.io/abouhatab/wokwi-hardware-simulation-of-space-station-tracker-5a9d7a
*/

#if defined(ARDUINO_ESP8266_WEMOS_D1MINI)
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <ESP8266HTTPClient.h>
#else
#include <WiFi.h>
#include <HTTPClient.h>
#endif

#include <ArduinoJson.h>
#include <time.h>

JsonDocument doc, filter;

struct SatelliteCatalogNumber
{
  const char *Name;
  const char *NORADID;
  const char *Color;
};
SatelliteCatalogNumber SCN[2] = {{"ISS", "25544", "2016"}, {"Tiangong", "48274", "63519"}};
const String URL[] = {"http://worldtimeapi.org/api/ip/8.8.8.8",
                      "http://ip-api.com/json/?fields=lat,lon",
                      "https://www.astroviewer.net/iss/ws/predictor.php?sat=",
                      "https://www.n2yo.com/sat/instant-tracking.php?d=1&s="};

time_t StringToDateTime(String D)
{
  struct tm DateTime = {D.substring(12, 14).toInt(), D.substring(10, 12).toInt(), D.substring(8, 10).toInt(),
                        D.substring(6, 8).toInt(), D.substring(4, 6).toInt(), D.substring(0, 4).toInt()};
  return mktime(&DateTime);
}

bool GetJson(String H)
{
  HTTPClient http;
  http.useHTTP10(true);
  WiFiClient client;
  WiFiClientSecure clientSecure;
  clientSecure.setInsecure();
  doc.clear();
  http.begin(H[4] == 's' ? clientSecure : client, H);
  // do {
  http.GET();
  H = http.getString();
  // } while (H == "");
  http.end();
  deserializeJson(doc, H[0] == '[' ? H.substring(1, H.length() - 1) : H, DeserializationOption::Filter(filter));
  // serializeJsonPretty(doc, Serial);
  return !doc.isNull();
}

String DrawCir(uint W, const char *C, float Lat, float Lon)
{
  return "cirs " + String((Lon + 180.0) / 360.0 * 480.0, 0) + "," + String((-1.0 * Lat + 90.0) / 180.0 * 240.0, 0) + "," + String(W / 2) + "," + C + "\xFF\xFF\xFF";
}

void setup()
{
  Serial.begin(115200);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  while (!Serial)
    yield();
  Serial.print("\xFF\xFF\xFFt0.txt=\"WiFi:WeMos/Vive La Resistance, http://192.168.4.1\"\xFF\xFF\xFF");
#if defined(ARDUINO_ESP8266_WEMOS_D1MINI)
  WiFiManager wm;
  wm.autoConnect("WeMos", "Vive La Resistance");
#else
  WiFi.begin("Wokwi-GUEST", "", 6);
  while (WiFi.status() != WL_CONNECTED);
#endif
  Serial.print("\xFF\xFF\xFFt0.txt=\"Connected to WiFi\"\xFF\xFF\xFF");
  // URL[0]
  filter["day_of_year"] = true;
  filter["utc_datetime"] = true;
  filter["datetime"] = true;
  // URL[1]
  filter["lat"] = true;
  filter["lon"] = true;
  // URL[2]
  filter["passes"][0]["begin"] = true;
  filter["passes"][0]["mag"] = true;
  // URL[3]
  filter["pos"][0]["d"] = true;
}

void loop()
{
  char NextPass[64], CurrentDateTime[20];
  float Lat, Lon;
  String S = "vis 255,0\xFF\xFF\xFFvis t0,1\xFF\xFF\xFF";
  if (GetJson(URL[0]))
  {
    // Calculate and draw Sun Position
    float Days = doc["utc_datetime"].as<String>().substring(11, 13).toFloat();
    Days += doc["utc_datetime"].as<String>().substring(14, 16).toFloat() / 60.0;
    Days /= 24.0;
    Days += doc["day_of_year"].as<float>() - 1.0;
    Lon = (PI * (1 - 2 * (Days - floor(Days)))) * 180.0 / PI;
    Lat = ((23.5 * PI / 180) * sin(PI * 2 / 365.25 * (Days - ((31 + 28.25 + 21) * 1.0)))) * 180.0 / PI;
    S += DrawCir(12, "65504", Lat, Lon) + DrawCir(8, "63488", Lat, Lon);

    // Save current time to be used to calculate next pass Time Remaining
    sprintf(CurrentDateTime, doc["datetime"].as<String>().substring(0, 4).c_str());
    strcat(CurrentDateTime, doc["datetime"].as<String>().substring(5, 7).c_str());
    strcat(CurrentDateTime, doc["datetime"].as<String>().substring(8, 10).c_str());
    strcat(CurrentDateTime, doc["datetime"].as<String>().substring(11, 13).c_str());
    strcat(CurrentDateTime, doc["datetime"].as<String>().substring(14, 16).c_str());
    strcat(CurrentDateTime, doc["datetime"].as<String>().substring(17, 19).c_str());

    // Draw my GPS coordinates
    if (GetJson(URL[1]))
    {
      Lat = doc["lat"].as<float>();
      Lon = doc["lon"].as<float>();
      S += "fill " + String(((Lon + 180.0) / 360.0 * 480.0) - 4, 0) + "," + String(((-1.0 * Lat + 90.0) / 180.0 * 240.0) - 4, 0) + ",9,9,0\xFF\xFF\xFF";
      S += DrawCir(7, "65504", Lat, Lon) + DrawCir(5, "31", Lat, Lon);

      // Next pass Time Remaining text using current time and my GPS coordinates
      S += "t0.txt=\"";
      for (SatelliteCatalogNumber scn : SCN)
      {
        if (GetJson(URL[2] + scn.NORADID + "&lon=" + Lon + "&lat=" + Lat))
          for (JsonObject pass : (JsonArray)doc["passes"].as<JsonArray>())
          {
            time_t RemTime = StringToDateTime(pass["begin"].as<String>()) - StringToDateTime(CurrentDateTime);
            if (RemTime > 0)
            {
              if (RemTime < 120)
                // Beep
                for (int i = 0; i < 500; i++)
                {
                  digitalWrite(5, digitalRead(4));
                  digitalWrite(4, !digitalRead(4));
                  delayMicroseconds(400);
                }
              sprintf(NextPass, "%s (%.1f): %02d:%02d:%02d  ",
                      scn.Name,
                      pass["mag"].as<float>(),
                      (int)(((RemTime / (24 * 60 * 60)) * 24) + (RemTime % (24 * 60 * 60)) / (60 * 60)),
                      (int)((RemTime % (60 * 60)) / 60),
                      (int)(RemTime % 60));
              S += String(NextPass);
              break;
            }
          }
      }
      S += "\"\xFF\xFF\xFF";
    }
  }

  // Draw space stations
  for (SatelliteCatalogNumber scn : SCN)
    if (GetJson(URL[3] + scn.NORADID))
    {
      sscanf(doc["pos"][0]["d"].as<const char *>(), "%f|%f", &Lat, &Lon);
      S += DrawCir(15, scn.Color, Lat, Lon);
    }
  Serial.print(S);
}