/**************************************************************

   TinyGSM Getting Started guide:
     https://tiny.cc/tinygsm-readme

   NOTE:
   Some of the functions may be unavailable for your modem.
   Just comment them out.
   https://simcom.ee/documents/SIM7600C/SIM7500_SIM7600%20Series_AT%20Command%20Manual_V1.01.pdf
 **************************************************************/

#define TINY_GSM_MODEM_SIM7600

// Set serial for debug console (to the Serial Monitor, default speed 115200)
#define SerialMon Serial

// Set serial for AT commands (to the module)
// Use Hardware Serial on Mega, Leonardo, Micro
#define SerialAT Serial1

// See all AT commands, if wanted
#define DUMP_AT_COMMANDS

// Define the serial console for debug prints, if needed
#define TINY_GSM_DEBUG SerialMon

/*
   Tests enabled
*/
// #define TINY_GSM_TEST_GPRS          true
// #define TINY_GSM_TEST_TCP           true
//  #define TINY_GSM_TEST_CALL     true
//  #define TINY_GSM_TEST_SMS      true
//  #define TINY_GSM_TEST_USSD     true
//  #define TINY_GSM_TEST_TEMPERATURE   true
//  #define TINY_GSM_TEST_TIME          true
// #define TINY_GSM_TEST_GPS           true
//  powerdown modem after tests
// #define TINY_GSM_POWERDOWN          true
//  #define TEST_RING_RI_PIN            true

// set GSM PIN, if any
#define GSM_PIN ""

// Set phone numbers, if you want to test SMS and Calls
// #define SMS_TARGET  "+380xxxxxxxxx"
// #define CALL_TARGET "+380xxxxxxxxx"

#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 30          /* Time ESP32 will go to sleep (in seconds) */

#define UART_BAUD 115200

#define MODEM_TX 27
#define MODEM_RX 26
#define MODEM_PWRKEY 4
#define MODEM_DTR 32
#define MODEM_RI 33
#define MODEM_FLIGHT 25
#define MODEM_STATUS 34

#define SD_MISO 2
#define SD_MOSI 15
#define SD_SCLK 14
#define SD_CS 13

#define LED_PIN 12

// Default GPRS credentials
const char defaultAPN[] = "YourAPN";
// const char apn[] = "ibasis.iot";
const char defaultGprsUser[] = "YourGprsUser";
const char defaultGprsPass[] = "YourGprsPass";

// Default WiFi credentials
const char *defaultSSID = "AP_1_IOT";
const char *defaultPassword = "YOUR-WIFI-PASSWORD";

// Default Access Point credentials
const char *defaultAPSSID = "ESP32-AP";
const char *defaultAPPassword = "12345678";

// Server details to test TCP/SSL
const char testServer[] = "vsh.pp.ua";
const char resource[] = "/TinyGSM/logo.txt";

#include <SPI.h>
#include <FS.h>
#include <FFat.h>
#include <SD.h>
#include <Ticker.h>
#include <TinyGsmClient.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPWebFileManager.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>
// #include "utilities.h"

#ifdef DUMP_AT_COMMANDS
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, SerialMon);
TinyGsm modem(debugger);
#else
TinyGsm modem(SerialAT);
#endif

AsyncWebServer server(80);
ESPWebFileManager fileManager;
Preferences preferences;

void connectModem()
{
  DBG("Initializing modem...");
  if (!modem.init())
  {
    DBG("Failed to restart modem, delaying 10s and retrying");
    return;
  }

  /*  Preferred mode selection : AT+CNMP
      2 – Automatic
      13 – GSM Only
      14 – WCDMA Only
      38 – LTE Only
      59 – TDS-CDMA Only
      9 – CDMA Only
      10 – EVDO Only
      19 – GSM+WCDMA Only
      22 – CDMA+EVDO Only
      48 – Any but LTE
      60 – GSM+TDSCDMA Only
      63 – GSM+WCDMA+TDSCDMA Only
      67 – CDMA+EVDO+GSM+WCDMA+TDSCDMA Only
      39 – GSM+WCDMA+LTE Only
      51 – GSM+LTE Only
      54 – WCDMA+LTE Only
*/
  String ret;
  //    do {
  //        ret = modem.setNetworkMode(2);
  //        delay(500);
  //    } while (ret != "OK");
  ret = modem.setNetworkMode(2);
  DBG("setNetworkMode:", ret);

  // https://github.com/vshymanskyy/TinyGSM/pull/405
  uint8_t mode = modem.getGNSSMode();
  DBG("GNSS Mode:", mode);

  /*
      CGNSSMODE: <gnss_mode>,<dpo_mode>
      This command is used to configure GPS, GLONASS, BEIDOU and QZSS support mode.
      gnss_mode:
          0 : GLONASS
          1 : BEIDOU
          2 : GALILEO
          3 : QZSS
      dpo_mode :
          0 disable
          1 enable
  */
  modem.setGNSSMode(1, 1);
  light_sleep(1);

  String name = modem.getModemName();
  DBG("Modem Name:", name);

  String modemInfo = modem.getModemInfo();
  DBG("Modem Info:", modemInfo);

  String manufacturer = modem.getModemManufacturer();
  DBG("Modem Manufacturer:", manufacturer);

  String hw_ver = modem.getModemModel();
  DBG("Modem Hardware Version:", hw_ver);

  String fv_ver = modem.getModemRevision();
  DBG("Modem Firware Version:", fv_ver);

  // Unlock your SIM card with a PIN if needed
  if (GSM_PIN && modem.getSimStatus() != 3)
  {
    modem.simUnlock(GSM_PIN);
  }

  DBG("Waiting for network...");
  if (!modem.waitForNetwork(600000L))
  {
    light_sleep(10);
    return;
  }

  if (modem.isNetworkConnected())
  {
    DBG("Network connected");
  }

  // Read saved GPRS credentials
  preferences.begin("gprs", true);
  String apn = preferences.getString("apn", defaultAPN);
  String gprsUser = preferences.getString("gprsUser", defaultGprsUser);
  String gprsPass = preferences.getString("gprsPass", defaultGprsPass);
  preferences.end();

  DBG("Connecting to", apn);
  if (!modem.gprsConnect(apn.c_str(), gprsUser.c_str(), gprsPass.c_str()))
  {
    light_sleep(10);
    return;
  }

  bool res = modem.isGprsConnected();
  DBG("GPRS status:", res ? "connected" : "not connected");

  String ccid = modem.getSimCCID();
  DBG("CCID:", ccid);

  String imei = modem.getIMEI();
  DBG("IMEI:", imei);

  String imsi = modem.getIMSI();
  DBG("IMSI:", imsi);

  String cop = modem.getOperator();
  DBG("Operator:", cop);

  IPAddress local = modem.localIP();
  DBG("Local IP:", local);

  int csq = modem.getSignalQuality();
  DBG("Signal quality:", csq);

  // Read saved GPS/GNSS/GLONASS
  DBG("Enabling GPS/GNSS/GLONASS");
  modem.enableGPS();
  light_sleep(2);
  // delay(15000L);

  float lat2 = 0;
  float lon2 = 0;
  float speed2 = 0;
  float alt2 = 0;
  int vsat2 = 0;
  int usat2 = 0;
  float accuracy2 = 0;
  int year2 = 0;
  int month2 = 0;
  int day2 = 0;
  int hour2 = 0;
  int min2 = 0;
  int sec2 = 0;
  DBG("Requesting current GPS/GNSS/GLONASS location");
  for (;;)
  {
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    if (modem.getGPS(&lat2, &lon2, &speed2, &alt2, &vsat2, &usat2, &accuracy2,
                     &year2, &month2, &day2, &hour2, &min2, &sec2))
    {
      DBG("Latitude:", String(lat2, 8), "\tLongitude:", String(lon2, 8));
      DBG("Speed:", speed2, "\tAltitude:", alt2);
      DBG("Visible Satellites:", vsat2, "\tUsed Satellites:", usat2);
      DBG("Accuracy:", accuracy2);
      DBG("Year:", year2, "\tMonth:", month2, "\tDay:", day2);
      DBG("Hour:", hour2, "\tMinute:", min2, "\tSecond:", sec2);
      break;
    }
    else
    {
      light_sleep(2);
    }
  }
  DBG("Retrieving GPS/GNSS/GLONASS location again as a string");
  String gps_raw = modem.getGPSraw();
  DBG("GPS/GNSS Based Location String:", gps_raw);
  // DBG("Disabling GPS");
  // modem.disableGPS();
}

void connectToWiFi()
{
  // Read saved WiFi credentials
  preferences.begin("wifi", true);
  String ssid = preferences.getString("ssid", "");
  String password = preferences.getString("password", "");
  preferences.end();

  if (ssid != "")
  {
    // Connect to WiFi with saved credentials
    WiFi.begin(ssid.c_str(), password.c_str());
    Serial.printf("Connecting to WiFi SSID: %s\n", ssid.c_str());
  }
  else
  {
    // Use default credentials if no saved credentials are found
    WiFi.begin(defaultSSID, defaultPassword);
    Serial.println("No saved WiFi credentials found, using default credentials");
    Serial.printf("Connecting to WiFi SSID: %s\n", defaultSSID);
  }

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.print(".");
  }

  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println("\nConnected to WiFi");
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());
  }
  else
  {
    Serial.println("\nFailed to connect to WiFi");
  }
}

void setupAccessPoint()
{
  // Read saved AP credentials
  preferences.begin("ap", true);
  String apSSID = preferences.getString("apSSID", defaultAPSSID);
  String apPassword = preferences.getString("apPassword", defaultAPPassword);
  preferences.end();

  Serial.println("Setting up Access Point...");

  bool result = WiFi.softAP(apSSID.c_str(), apPassword.c_str());
  if (result)
  {
    Serial.println("Access Point started successfully!");
    Serial.print("AP IP Address: ");
    Serial.println(WiFi.softAPIP());
  }
  else
  {
    Serial.println("Failed to start Access Point.");
  }
}

void setupWebServer()
{
  // Serve HTML file
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            {
    // Read saved WiFi credentials
        preferences.begin("wifi", true);
        String ssid = preferences.getString("ssid", defaultSSID);
        String password = preferences.getString("password", defaultPassword);
        preferences.end();

        // Read saved AP credentials
        preferences.begin("ap", true);
        String apSSID = preferences.getString("apSSID", defaultAPSSID);
        String apPassword = preferences.getString("apPassword", defaultAPPassword);
        preferences.end();

        // Read saved GPRS credentials
        preferences.begin("gprs", true);
        String apn = preferences.getString("apn", defaultAPN);
        String gprsUser = preferences.getString("gprsUser", defaultGprsUser);
        String gprsPass = preferences.getString("gprsPass", defaultGprsPass);
        preferences.end();

        // Read saved GSM PIN
        preferences.begin("gsm", true);
        String gsmPin = preferences.getString("gsmPin", GSM_PIN);
        preferences.end();

        // Get IP addresses
        String wifiIP = WiFi.isConnected() ? WiFi.localIP().toString() : "Not connected";
        String apIP = WiFi.softAPIP().toString();
        String gprsIP = modem.localIP().toString();

        // Get MAC addresses
        String wifiMAC = WiFi.macAddress();
        String apMAC = WiFi.softAPmacAddress();
        String gprsMAC = modem.getIMEI(); // Using IMEI as a unique identifier

        // Get GPRS status and other details
        bool gprsStatus = modem.isGprsConnected();
        String gprsStatusStr = gprsStatus ? "connected" : "not connected";
        String ccid = modem.getSimCCID();
        String imei = modem.getIMEI();
        String imsi = modem.getIMSI();
        String cop = modem.getOperator();
        int csq = modem.getSignalQuality();
        String signalQuality = String(csq);

        // Get GPS data
        float lat2 = 0;
        float lon2 = 0;
        float speed2 = 0;
        float alt2 = 0;
        int vsat2 = 0;
        int usat2 = 0;
        float accuracy2 = 0;
        int year2 = 0;
        int month2 = 0;
        int day2 = 0;
        int hour2 = 0;
        int min2 = 0;
        int sec2 = 0;
        String latitude, longitude, altitude, speed, visibleSatellites, usedSatellites, accuracy, date, time;
        if (modem.getGPS(&lat2, &lon2, &speed2, &alt2, &vsat2, &usat2, &accuracy2, &year2, &month2, &day2, &hour2, &min2, &sec2)) {
            latitude = String(lat2, 8);
            longitude = String(lon2, 8);
            altitude = String(alt2);
            speed = String(speed2);
            visibleSatellites = String(vsat2);
            usedSatellites = String(usat2);
            accuracy = String(accuracy2);
            date = String(year2) + "-" + String(month2) + "-" + String(day2);
            time = String(hour2) + ":" + String(min2) + ":" + String(sec2);
        } else {
            latitude = "N/A";
            longitude = "N/A";
            altitude = "N/A";
            speed = "N/A";
            visibleSatellites = "N/A";
            usedSatellites = "N/A";
            accuracy = "N/A";
            date = "N/A";
            time = "N/A";
        }

        // Read the HTML file from the filesystem
        File file = FFat.open("/web_admin.html", "r");
        if (!file) {
            request->send(500, "text/plain", "Failed to open web_admin.html");
            return;
        }

        // Read the file content into a string
        String html = file.readString();
        file.close();

        // Replace placeholders with actual values
        html.replace("{{WiFiSSID}}", ssid);
        html.replace("{{WiFiPassword}}", password);
        html.replace("{{WiFiIP}}", wifiIP);
        html.replace("{{WiFiMAC}}", wifiMAC);
        html.replace("{{APSSID}}", apSSID);
        html.replace("{{APPassword}}", apPassword);
        html.replace("{{APIP}}", apIP);
        html.replace("{{APMAC}}", apMAC);
        html.replace("{{APN}}", apn);
        html.replace("{{GPRSUser}}", gprsUser);
        html.replace("{{GPRSPass}}", gprsPass);
        html.replace("{{GPRSIP}}", gprsIP);
        html.replace("{{GPRSMAC}}", gprsMAC);
        html.replace("{{GPRSStatus}}", gprsStatusStr);
        html.replace("{{CCID}}", ccid);
        html.replace("{{IMEI}}", imei);
        html.replace("{{IMSI}}", imsi);
        html.replace("{{Operator}}", cop);
        html.replace("{{SignalQuality}}", signalQuality);
        html.replace("{{GSMPIN}}", gsmPin);
        html.replace("{{Latitude}}", latitude);
        html.replace("{{Longitude}}", longitude);
        html.replace("{{Altitude}}", altitude);
        html.replace("{{Speed}}", speed);
        html.replace("{{VisibleSatellites}}", visibleSatellites);
        html.replace("{{UsedSatellites}}", usedSatellites);
        html.replace("{{Accuracy}}", accuracy);
        html.replace("{{Date}}", date);
        html.replace("{{Time}}", time);

        // Send the modified HTML content
        request->send(200, "text/html", html); });

  // Handle WiFi configuration form submission
  server.on("/setWiFi", HTTP_POST, [](AsyncWebServerRequest *request)
            {
        String ssid = request->getParam("ssid", true)->value();
        String password = request->getParam("password", true)->value();
    
        // Save the credentials to a secure location
        preferences.begin("wifi", false);
        preferences.putString("ssid", ssid);
        preferences.putString("password", password);
        preferences.end();
    
        Serial.printf("New WiFi SSID: %s, Password: %s\n", ssid.c_str(), password.c_str());

        // Restart WiFi with new credentials
        WiFi.disconnect();
        WiFi.begin(ssid.c_str(), password.c_str());

        // Wait for connection
        while (WiFi.status() != WL_CONNECTED) {
            delay(1000);
            Serial.print(".");
        }

        if (WiFi.status() == WL_CONNECTED) {
            Serial.println("\nConnected to WiFi");
            Serial.print("IP Address: ");
            Serial.println(WiFi.localIP());
        } else {
            Serial.println("\nFailed to connect to WiFi");
        }
    
        // Send a response with a popup alert
        String response = "<html><body><script>alert('WiFi credentials updated successfully.'); window.location.href = '/';</script></body></html>";
        request->send(200, "text/html", response); });

  // Handle Access Point configuration form submission
  server.on("/setAP", HTTP_POST, [](AsyncWebServerRequest *request)
            {
        String apSSID = request->getParam("apSSID", true)->value();
        String apPassword = request->getParam("apPassword", true)->value();
    
        // Save the credentials to a secure location
        preferences.begin("ap", false);
        preferences.putString("apSSID", apSSID);
        preferences.putString("apPassword", apPassword);
        preferences.end();
    
        Serial.printf("New AP SSID: %s, Password: %s\n", apSSID.c_str(), apPassword.c_str());

        // Restart Access Point with new credentials
        WiFi.softAPdisconnect(true);
        bool result = WiFi.softAP(apSSID.c_str(), apPassword.c_str());
        if (result) {
            Serial.println("Access Point started successfully!");
            Serial.print("AP IP Address: ");
            Serial.println(WiFi.softAPIP());
        } else {
            Serial.println("Failed to start Access Point.");
        }
    
        // Send a response with a popup alert
        String response = "<html><body><script>alert('Access Point credentials updated successfully.'); window.location.href = '/';</script></body></html>";
        request->send(200, "text/html", response); });

  // Handle GPRS configuration form submission
  server.on("/setGPRS", HTTP_POST, [](AsyncWebServerRequest *request)
            {
        String apn = request->getParam("apn", true)->value();
        String gprsUser = request->getParam("gprsUser", true)->value();
        String gprsPass = request->getParam("gprsPass", true)->value();
    
        // Save the credentials to a secure location
        preferences.begin("gprs", false);
        preferences.putString("apn", apn);
        preferences.putString("gprsUser", gprsUser);
        preferences.putString("gprsPass", gprsPass);
        preferences.end();
    
        Serial.printf("New GPRS APN: %s, User: %s, Pass: %s\n", apn.c_str(), gprsUser.c_str(), gprsPass.c_str());

        // Restart GPRS with new credentials
        modem.gprsDisconnect();
        delay(1000); // Wait for disconnection
        if (modem.gprsConnect(apn.c_str(), gprsUser.c_str(), gprsPass.c_str())) {
            Serial.println("GPRS reconnected successfully!");
        } else {
            Serial.println("Failed to reconnect GPRS.");
        }

        // Send a response with a popup alert
        String response = "<html><body><script>alert('GPRS credentials updated successfully.'); window.location.href = '/';</script></body></html>";
        request->send(200, "text/html", response); });

  // Handle GSM PIN configuration form submission
  server.on("/setGSM", HTTP_POST, [](AsyncWebServerRequest *request)
            {
        String gsmPin = request->getParam("gsmPin", true)->value();

        // Save the GSM PIN to a secure location
        preferences.begin("gsm", false);
        preferences.putString("gsmPin", gsmPin);
        preferences.end();

        Serial.printf("New GSM PIN: %s\n", gsmPin.c_str());

        // Send a response with a popup alert
        String response = "<html><body><script>alert('GSM PIN updated successfully.'); window.location.href = '/';</script></body></html>";
        request->send(200, "text/html", response); });

  // Start server
  server.begin();
  Serial.println("Web server started.");
}

void gsmLocationFunctions()
{
  float gsm_latitude = 0;
  float gsm_longitude = 0;
  float gsm_accuracy = 0;
  int gsm_year = 0;
  int gsm_month = 0;
  int gsm_day = 0;
  int gsm_hour = 0;
  int gsm_minute = 0;
  int gsm_second = 0;
  for (int8_t i = 15; i; i--)
  {
    DBG("Requesting current GSM location");
    if (modem.getGsmLocation(&gsm_latitude, &gsm_longitude, &gsm_accuracy,
                             &gsm_year, &gsm_month, &gsm_day, &gsm_hour,
                             &gsm_minute, &gsm_second))
    {
      DBG("Latitude:", String(gsm_latitude, 8),
          "\tLongitude:", String(gsm_longitude, 8));
      DBG("Accuracy:", gsm_accuracy);
      DBG("Year:", gsm_year, "\tMonth:", gsm_month, "\tDay:", gsm_day);
      DBG("Hour:", gsm_hour, "\tMinute:", gsm_minute, "\tSecond:", gsm_second);
      break;
    }
    else
    {
      DBG("Couldn't get GSM location, retrying in 15s.");
      delay(15000L);
    }
  }
  DBG("Retrieving GSM location again as a string");
  String location = modem.getGsmLocation();
  DBG("GSM Based Location String:", location);
}

void networkTimeFunctions()
{
  DBG("Asking modem to sync with NTP");
  modem.NTPServerSync("pool.ntp.org", 20);

  int ntp_year = 0;
  int ntp_month = 0;
  int ntp_day = 0;
  int ntp_hour = 0;
  int ntp_min = 0;
  int ntp_sec = 0;
  float ntp_timezone = 0;
  for (int8_t i = 5; i; i--)
  {
    DBG("Requesting current network time");
    if (modem.getNetworkTime(&ntp_year, &ntp_month, &ntp_day, &ntp_hour,
                             &ntp_min, &ntp_sec, &ntp_timezone))
    {
      DBG("Year:", ntp_year, "\tMonth:", ntp_month, "\tDay:", ntp_day);
      DBG("Hour:", ntp_hour, "\tMinute:", ntp_min, "\tSecond:", ntp_sec);
      DBG("Timezone:", ntp_timezone);
      break;
    }
    else
    {
      DBG("Couldn't get network time, retrying in 15s.");
      delay(15000L);
    }
  }
  DBG("Retrieving time again as a string");
  String time = modem.getGSMDateTime(DATE_FULL);
  DBG("Current Network Time:", time);
}

void temperatureFunctions()
{
  float temp = modem.getTemperature();
  DBG("Chip temperature:", temp);
}

void batteryFunctions()
{
  int8_t chargeState = -99;
  int8_t chargePercent = -99;
  int16_t milliVolts = -9999;
  modem.getBattStats(chargeState, chargePercent, milliVolts);
  DBG("Battery charge state:", chargeState);
  DBG("Battery charge 'percent':", chargePercent);
  DBG("Battery voltage:", milliVolts / 1000.0F);
}

void fatfs()
{
  if (!FFat.begin(true))
  { // Format on fail: 'true' forces formatting if mounting fails
    Serial.println("Failed to initialize eMMC storage (FFat). Trying to format...");
    if (!FFat.format())
    {
      Serial.println("FFat format failed. Check partition table and storage.");
      return; // Halt setup if FFat fails
    }
    if (!FFat.begin())
    {
      Serial.println("Failed to mount FFat after formatting.");
      return;
    }
  }
  Serial.println("FFat initialized successfully.");
}

void sdcard()
{
  // Initialize SDCard
  SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS))
  {
    Serial.println("SDCard MOUNT FAIL");
  }
  else
  {
    uint32_t cardSize = SD.cardSize() / (1024 * 1024);
    String str = "SDCard Size: " + String(cardSize) + "MB";
    Serial.println(str);
  }
}

void setup()
{
  // Set console baud rate
  SerialMon.begin(115200);
  delay(10);

  // Set GSM module baud rate
  SerialAT.begin(UART_BAUD, SERIAL_8N1, MODEM_RX, MODEM_TX);

  /*
    The indicator light of the board can be controlled
  */
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  /*
    MODEM_PWRKEY IO:4 The power-on signal of the modulator must be given to it,
    otherwise the modulator will not reply when the command is sent
  */
  pinMode(MODEM_PWRKEY, OUTPUT);
  digitalWrite(MODEM_PWRKEY, HIGH);
  delay(300); // Need delay
  digitalWrite(MODEM_PWRKEY, LOW);

  /*
    MODEM_FLIGHT IO:25 Modulator flight mode control,
    need to enable modulator, this pin must be set to high
  */
  pinMode(MODEM_FLIGHT, OUTPUT);
  digitalWrite(MODEM_FLIGHT, HIGH);

  fatfs();
  sdcard();
  connectModem();
  connectToWiFi();
  setupAccessPoint();
  setupWebServer();
  gsmLocationFunctions();
  networkTimeFunctions();
  temperatureFunctions();
  batteryFunctions();

  // Uncomment below will perform loopback test
  //     while (1) {
  //         while (SerialMon.available()) {
  //             SerialAT.write(SerialMon.read());
  //         }
  //         while (SerialAT.available()) {
  //             SerialMon.write(SerialAT.read());
  //         }
  //     }

  // Read saved GPRS credentials
  preferences.begin("gprs", true);
  String apn = preferences.getString("apn", defaultAPN);
  String gprsUser = preferences.getString("gprsUser", defaultGprsUser);
  String gprsPass = preferences.getString("gprsPass", defaultGprsPass);
  preferences.end();

  // Read saved GSM PIN
  preferences.begin("gsm", true);
  String gsmPin = preferences.getString("gsmPin", GSM_PIN);
  preferences.end();

  // Initialize FATFS (Change to other types as needed, Valid types: FS_SD_CARD, FS_SPIFFS, FS_LITTLEFS, FS_FATFS )
  if (!fileManager.initFileSystem(ESPWebFileManager::FS_FATFS, true))
  {
    DEBUG_SERIAL.println("Failed to initialize file system");
  }

  fileManager.setServer(&server);
  server.begin();
  DEBUG_SERIAL.println("Web server started");
}

void light_sleep(uint32_t sec)
{
  esp_sleep_enable_timer_wakeup(sec * 1000000ULL);
  esp_light_sleep_start();
}

void loop()
{
  //   bool res ;

  // Restart takes quite some time
  // To skip it, call init() instead of restart()
  //   DBG("Initializing modem...");
  //   if (!modem.init()) {
  //     DBG("Failed to restart modem, delaying 10s and retrying");
  //     return;
  //   }
  //
  //    // Restart takes quite some time
  //    // To skip it, call init() instead of restart()
  //    DBG("Initializing modem...");
  //    if (!modem.restart()) {
  //        DBG("Failed to restart modem, delaying 10s and retrying");
  //        // restart autobaud in case GSM just rebooted
  //        return;
  //    }

  // #if TINY_GSM_TEST_USSD && defined TINY_GSM_MODEM_HAS_SMS
  //   String ussd_balance = modem.sendUSSD("*111#");
  //   DBG("Balance (USSD):", ussd_balance);

  //   String ussd_phone_num = modem.sendUSSD("*161#");
  //   DBG("Phone number (USSD):", ussd_phone_num);
  // #endif

  // #if TINY_GSM_TEST_TCP && defined TINY_GSM_MODEM_HAS_TCP
  //   TinyGsmClient client(modem, 0);
  //   const int port = 80;
  //   DBG("Connecting to ", testServer);
  //   if (!client.connect(testServer, port)) {
  //     DBG("... failed");
  //   } else {
  //     // Make a HTTP GET request:
  //     client.print(String("GET ") + resource + " HTTP/1.0\r\n");
  //     client.print(String("Host: ") + testServer + "\r\n");
  //     client.print("Connection: close\r\n\r\n");

  //     // Wait for data to arrive
  //     uint32_t start = millis();
  //     while (client.connected() && !client.available() &&
  //            millis() - start < 30000L) {
  //       delay(100);
  //     };

  //     // Read data
  //     start = millis();
  //     while (client.connected() && millis() - start < 5000L) {
  //       while (client.available()) {
  //         SerialMon.write(client.read());
  //         start = millis();
  //       }
  //     }
  //     client.stop();
  //   }
  // #endif

  // #if TINY_GSM_TEST_CALL && defined(CALL_TARGET)

  //   DBG("Calling:", CALL_TARGET);
  //   SerialAT.println("ATD"CALL_TARGET";");
  //   modem.waitResponse();
  //   light_sleep(20);
  // #endif

  // #if TINY_GSM_TEST_TIME && defined TINY_GSM_MODEM_HAS_TIME
  //   int year3 = 0;
  //   int month3 = 0;
  //   int day3 = 0;
  //   int hour3 = 0;
  //   int min3 = 0;
  //   int sec3 = 0;
  //   float timezone = 0;
  //   for (int8_t i = 5; i; i--) {
  //     DBG("Requesting current network time");
  //     if (modem.getNetworkTime(&year3, &month3, &day3, &hour3, &min3, &sec3,
  //                              &timezone)) {
  //       DBG("Year:", year3, "\tMonth:", month3, "\tDay:", day3);
  //       DBG("Hour:", hour3, "\tMinute:", min3, "\tSecond:", sec3);
  //       DBG("Timezone:", timezone);
  //       break;
  //     } else {
  //       DBG("Couldn't get network time, retrying in 15s.");
  //       light_sleep(15);
  //     }
  //   }
  //   DBG("Retrieving time again as a string");
  //   String time = modem.getGSMDateTime(DATE_FULL);
  //   DBG("Current Network Time:", time);
  // #endif

  // #if TINY_GSM_TEST_GPRS
  //   modem.gprsDisconnect();
  //   light_sleep(5);
  //   if (!modem.isGprsConnected()) {
  //     DBG("GPRS disconnected");
  //   } else {
  //     DBG("GPRS disconnect: Failed.");
  //   }
  // #endif

  // #if TINY_GSM_TEST_TEMPERATURE && defined TINY_GSM_MODEM_HAS_TEMPERATURE
  //   float temp = modem.getTemperature();
  //   DBG("Chip temperature:", temp);
  // #endif

  // #ifdef TEST_RING_RI_PIN
  // #ifdef MODEM_RI
  //   //Set RI Pin input
  //   pinMode(MODEM_RI, INPUT);

  //   Serial.println("Wait for call in");
  //   //When is no calling ,RI pin is high level
  //   while (digitalRead(MODEM_RI)) {
  //     Serial.print('.');
  //     delay(500);
  //   }
  //   Serial.println("call in ");

  //   //Wait for 5 seconds to connect the call
  //   delay(5000);

  //   //Accept call
  //   SerialAT.println("ATA");

  //   // Hang up after 20 seconds of talk time
  //   delay(20000);

  //   SerialAT.println("ATH");

  // #endif  //MODEM_RI
  // #endif  //TEST_RING_RI_PIN

  // #ifdef MODEM_DTR1

  //   modem.sleepEnable();

  //   delay(100);

  //   // test modem response , res == 0 , modem is sleep
  //   res = modem.testAT();
  //   Serial.print(" Test AT result -> ");
  //   Serial.println(res);

  //   delay(1000);

  //   Serial.println("Use DTR Pin Wakeup");
  //   pinMode(MODEM_DTR, OUTPUT);
  //   //Set DTR Pin low , wakeup modem .
  //   digitalWrite(MODEM_DTR, LOW);

  //   // test modem response , res == 1 , modem is wakeup
  //   res = modem.testAT();
  //   Serial.print(" Test AT result -> ");
  //   Serial.println(res);

  // #endif

  // #if TINY_GSM_POWERDOWN
  //   // Try to power-off (modem may decide to restart automatically)
  //   // To turn off modem completely, please use Reset/Enable pins
  //   modem.poweroff();
  //   DBG("Poweroff.");
  // #endif

  //   SerialMon.printf("End of tests. Enable deep sleep , Will wake up in %d seconds", TIME_TO_SLEEP);

  //   // Wait for modem to power off
  //   light_sleep(5);

  //   esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  //   delay(200);
  //   esp_deep_sleep_start();

  while (1)
    ;
}