/*
  FILE: ATdebug_WiFi_AP_WebAdmin.ino
  AUTHOR: Kaibin (Modified by OpenAI)
  PURPOSE: Test functionality with WiFi, AP, and Web Admin Panel
*/

#define TINY_GSM_MODEM_SIM7600
#define TINY_GSM_RX_BUFFER 1024 // Set RX buffer to 1Kb
#define SerialAT Serial1

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

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

#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


// Your GPRS credentials, if any
const char apn[]  = "YOUR-APN";     //SET TO YOUR APN
const char gprsUser[] = "";
const char gprsPass[] = "";

// Default WiFi credentials
const char* defaultSSID = "AP_1_IOT";      // Replace with your WiFi SSID
const char* defaultPassword = "YOUR-WIFI-PASSWORD";  // Replace with your WiFi password

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

#include <TinyGsmClient.h>
#include <TinyGPS++.h>
#include <SPI.h>
#include <SD.h>
#include <FS.h>
#include <FFat.h>
#include <Ticker.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPWebFileManager.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>


#ifdef DUMP_AT_COMMANDS  // if enabled it requires the streamDebugger lib
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, Serial);
TinyGsm modem(debugger);
#else
TinyGsm modem(SerialAT);
#endif

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

int counter, lastIndex, numberOfPieces = 24;
String pieces[24], input;

bool reply = false;

void modem_on() {
    /*
    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);

    int i = 40;
    Serial.print(F("\r\n# Startup #\r\n"));
    Serial.print(F("# Sending \"AT\" to Modem. Waiting for Response\r\n# "));
    while (i) {
        SerialAT.println(F("AT"));

        // Show the User: we are doing something.
        Serial.print(F("."));
        delay(500);

        // Did the Modem send something?
        if (SerialAT.available()) {
            String r = SerialAT.readString();
            Serial.print("\r\n# Response:\r\n" + r);
            if ( r.indexOf("OK") >= 0 ) {
                reply = true;
                break;
            } else {
                Serial.print(F("\r\n# "));
            }
        }

        // Did the User try to send something? Maybe he did not receive the first messages yet. Inform the User what is happening
        if (Serial.available() && !reply) {
            Serial.read();
            Serial.print(F("\r\n# Modem is not yet online."));
            Serial.print(F("\r\n# Sending \"AT\" to Modem. Waiting for Response\r\n# "));
        }

        // On the 5th try: Inform the User what is happening
        if (i == 35) {
            Serial.print(F("\r\n# Modem did not yet answer. Probably Power loss?\r\n"));
            Serial.print(F("# Sending \"AT\" to Modem. Waiting for Response\r\n# "));
        }
        delay(500);
        i--;
    }
    Serial.println(F("#\r\n"));
}

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();

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

        // Get MAC addresses
        String wifiMAC = WiFi.macAddress();
        String apMAC = WiFi.softAPmacAddress();

        // Get GPS data
        String latitude = gps.location.isValid() ? String(gps.location.lat(), 6) : "N/A";
        String longitude = gps.location.isValid() ? String(gps.location.lng(), 6) : "N/A";
        String altitude = gps.altitude.isValid() ? String(gps.altitude.meters()) : "N/A";
        String speed = gps.speed.isValid() ? String(gps.speed.kmph()) : "N/A";
        String satellites = gps.satellites.isValid() ? String(gps.satellites.value()) : "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("{{Latitude}}", latitude);
        html.replace("{{Longitude}}", longitude);
        html.replace("{{Altitude}}", altitude);
        html.replace("{{Speed}}", speed);
        html.replace("{{Satellites}}", satellites);

        // 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);
    });

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

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 setup() {
    Serial.begin(115200); // Set console baud rate
    SerialAT.begin(115200, SERIAL_8N1, MODEM_RX, MODEM_TX);
    delay(100);

    fatfs ();
    modem_on();
    connectToWiFi();
    setupAccessPoint();
    setupWebServer();

    if (reply) {
        Serial.println(F("***********************************************************"));
        Serial.println(F(" You can now send AT commands"));
        Serial.println(F(" Enter \"AT\" (without quotes), and you should see \"OK\""));
        Serial.println(F(" If it doesn't work, select \"Both NL & CR\" in Serial Monitor"));
        Serial.println(F(" DISCLAIMER: Entering AT commands without knowing what they do"));
        Serial.println(F(" can have undesired consiquinces..."));
        Serial.println(F("***********************************************************\n"));

        // Uncomment to read received SMS
        //SerialAT.println("AT+CMGL=\"ALL\"");
    } else {
        Serial.println(F("***********************************************************"));
        Serial.println(F(" Failed to connect to the modem! Check the baud and try again."));
        Serial.println(F("***********************************************************\n"));
    }

    // 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 loop() {
    if (SerialAT.available()) {
        Serial.write(SerialAT.read());
    }
    if (Serial.available()) {
        SerialAT.write(Serial.read());
    }
    // Read data from the GPS module
    while (SerialAT.available() > 0) {
        gps.encode(SerialAT.read());
    }
    // Print GPS data to the serial monitor
    if (gps.location.isUpdated()) {
        Serial.print("Latitude: ");
        Serial.println(gps.location.lat(), 6);
        Serial.print("Longitude: ");
        Serial.println(gps.location.lng(), 6);
        Serial.print("Altitude: ");
        Serial.println(gps.altitude.meters());
        Serial.print("Speed: ");
        Serial.println(gps.speed.kmph());
        Serial.print("Satellites: ");
        Serial.println(gps.satellites.value());
    }
    delay(1000);
}