#include <Arduino.h>
#include <WiFi.h>
#include <SPIFFS.h>
#include <FS.h>
#include <Preferences.h>
#include <ESP32Ping.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include "esp_system.h"
#include "esp_task_wdt.h"
#include <esp_timer.h>
#include <driver/adc.h>

#define MAX_COMMAND_LEN 128
#define MAX_HISTORY_LEN 10

Preferences preferences;
String currentDir = "/";
char commandHistory[MAX_HISTORY_LEN][MAX_COMMAND_LEN];
int historyIndex = 0;
int historyCount = 0;
char input[MAX_COMMAND_LEN];
size_t len = 0;

void setup();
void loop();
void displaySystemInfo();
void scanWiFiNetworks();
void connectToWiFi(const char* ssid, const char* password);
void disconnectWiFi();
void loadWiFiCredentials();
void saveWiFiCredentials(const char* ssid, const char* password);
void listFiles();
void changeDirectory(const char* dirName);
void showFileContents(const char* filename);
void createFile(const char* filename);
void createDirectory(const char* dirName);
void deleteFileOrDirectory(const char* name);
void renameFile(const char* oldName, const char* newName);
void moveFile(const char* src, const char* dest);
void displayHelp();
void pingCommand(const char* address, int count);
void ifconfigCommand();
void memoryInfoCommand();
void fetchmeCommand();
void rebootCommand();
void uptimeCommand();
void freeMemCommand();
void handleCommand(char* input);
void addToHistory(const char* command);
void showCommandHistory();
void completeCommand(char* input);
void handleHelpFlag(const char* command);

struct Command {
    const char* name;
    void (*func)(const char* arg1, const char* arg2);
    const char* help;
};

Command commands[] = {
    {"help", [](const char* arg1, const char* arg2) { displayHelp(); }, "Display help information"},
    {"ls", [](const char* arg1, const char* arg2) { listFiles(); }, "List files in a directory"},
    {"cd", [](const char* arg1, const char* arg2) { changeDirectory(arg1); }, "Change directory"},
    {"cat", [](const char* arg1, const char* arg2) { showFileContents(arg1); }, "Display file contents"},
    {"touch", [](const char* arg1, const char* arg2) { createFile(arg1); }, "Create a new file"},
    {"mkdir", [](const char* arg1, const char* arg2) { createDirectory(arg1); }, "Create a new directory"},
    {"rm", [](const char* arg1, const char* arg2) { deleteFileOrDirectory(arg1); }, "Remove file or directory"},
    {"rename", [](const char* arg1, const char* arg2) { renameFile(arg1, arg2); }, "Rename a file"},
    {"mv", [](const char* arg1, const char* arg2) { moveFile(arg1, arg2); }, "Move a file"},
    {"wifi_scan", [](const char* arg1, const char* arg2) { scanWiFiNetworks(); }, "Scan for WiFi networks"},
    {"wifi_connect", [](const char* arg1, const char* arg2) { connectToWiFi(arg1, arg2); }, "Connect to a WiFi network"},
    {"wifi_disconnect", [](const char* arg1, const char* arg2) { disconnectWiFi(); }, "Disconnect from WiFi"},
    {"ping", [](const char* arg1, const char* arg2) { pingCommand(arg1, atoi(arg2)); }, "Ping an IP address or domain"},
    {"ifconfig", [](const char* arg1, const char* arg2) { ifconfigCommand(); }, "Display network configuration"},
    {"meminfo", [](const char* arg1, const char* arg2) { memoryInfoCommand(); }, "Display memory information"},
    {"fetchme", [](const char* arg1, const char* arg2) { fetchmeCommand(); }, "Display system information like neofetch"},
    {"reboot", [](const char* arg1, const char* arg2) { rebootCommand(); }, "Reboot the system"},
    {"uptime", [](const char* arg1, const char* arg2) { uptimeCommand(); }, "Display system uptime"},
    {"free_mem", [](const char* arg1, const char* arg2) { freeMemCommand(); }, "Display free memory"}
};

void setup() {
    Serial.begin(115200);
    if (!SPIFFS.begin(true)) {
        Serial.println("SPIFFS Mount Failed");
        return;
    }
    preferences.begin("wifi", false);

    displaySystemInfo();
    loadWiFiCredentials();
    Serial.print("> ");
}

void loop() {
    if (Serial.available() > 0) {
        char ch = Serial.read();
        if (ch == '\n' || ch == '\r') {
            input[len] = '\0';
            Serial.println();
            handleCommand(input);
            len = 0;
            Serial.print("> ");
        } else if (ch == '\t') {
            completeCommand(input);
        } else if (ch == 0x1B) { // Handle arrow keys
            if (Serial.available() > 0 && Serial.read() == '[') {
                char arrow = Serial.read();
                if (arrow == 'A') { // Up arrow
                    if (historyCount > 0) {
                        historyIndex = (historyIndex + historyCount - 1) % historyCount;
                        strcpy(input, commandHistory[historyIndex]);
                        len = strlen(input);
                        Serial.print("\r> ");
                        Serial.print(input);
                    }
                } else if (arrow == 'B') { // Down arrow
                    if (historyCount > 0) {
                        historyIndex = (historyIndex + 1) % historyCount;
                        strcpy(input, commandHistory[historyIndex]);
                        len = strlen(input);
                        Serial.print("\r> ");
                        Serial.print(input);
                    }
                }
            }
        } else if (len < MAX_COMMAND_LEN - 1) {
            input[len++] = ch;
            Serial.print(ch);
        }
    }
}

void displaySystemInfo() {
    Serial.println("\033[1;34mESP32 System Information:\033[0m");
    Serial.printf("  \033[1;32mSDK Version:\033[0m %s\n", ESP.getSdkVersion());
    Serial.printf("  \033[1;32mFree Heap:\033[0m %d bytes\n", ESP.getFreeHeap());
    Serial.printf("  \033[1;32mCPU Frequency:\033[0m %d MHz\n", ESP.getCpuFreqMHz());
    Serial.println("  \033[1;32mType 'help' to see the list of available commands.\033[0m");
    Serial.print("> ");
}

void scanWiFiNetworks() {
    Serial.println("\033[1;33mScanning for WiFi networks...\033[1;0m");

    const char spinner[] = {'|', '/', '-', '\\'};
    int spinnerIndex = 0;
    for (int i = 0; i < 30; ++i) {
        Serial.print(spinner[spinnerIndex]);
        delay(100);
        Serial.print("\b");
        spinnerIndex = (spinnerIndex + 1) % 4;
    }
    Serial.println();

    int numNetworks = WiFi.scanNetworks();
    for (int i = 0; i < numNetworks; ++i) {
        Serial.printf("%d: %s (%d dBm)\n", i + 1, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
    }

    if (numNetworks == 0) {
        Serial.println("No networks found.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    Serial.println("\nType the number of the network you want to connect to:");
    while (Serial.available() == 0) {
        delay(10);
    }
    int selectedNetwork = Serial.parseInt();
    Serial.read(); // Clear the newline character from the buffer

    if (selectedNetwork < 1 || selectedNetwork > numNetworks) {
        Serial.println("Invalid selection.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    String selectedSSID = WiFi.SSID(selectedNetwork - 1);
    String savedPassword = preferences.getString(selectedSSID.c_str(), "");

    if (savedPassword.length() > 0) {
        Serial.println("Trying saved credentials...");
        connectToWiFi(selectedSSID.c_str(), savedPassword.c_str());
    } else {
        bool connected = false;
        while (!connected) {
            Serial.println("Enter the password for the network:");
            while (Serial.available() == 0) {
                delay(10);
            }
            String password = Serial.readStringUntil('\n');
            password.trim();

            connectToWiFi(selectedSSID.c_str(), password.c_str());
            if (WiFi.status() == WL_CONNECTED) {
                Serial.println("Connected successfully!");
                saveWiFiCredentials(selectedSSID.c_str(), password.c_str());
                connected = true;
            } else {
                Serial.println("Failed to connect. Please check the password and try again.");
                Serial.println("Would you like to try again? (\033[1;92mY\033[1;0m/\033[1;91mN\033[1;0m):");
                while (Serial.available() == 0) {
                    delay(10);
                }
                String retry = Serial.readStringUntil('\n');
                retry.trim();
                retry.toLowerCase();

                if (retry != "y" && retry != "yes") {
                    connected = true;  // Exit the loop if user does not want to try again
                }
            }
        }
    }
    Serial.println();
    Serial.print("> ");
}

void connectToWiFi(const char* ssid, const char* password) {
    WiFi.begin(ssid, password);
    Serial.print("Connecting to WiFi...");

    unsigned long startAttemptTime = millis();
    const unsigned long timeout = 10000; // 10 seconds timeout

    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < timeout) {
        Serial.print(".");
        delay(500);
    }

    if (WiFi.status() == WL_CONNECTED) {
        Serial.println("Connected!");
    } else {
        Serial.println("Failed to connect within the timeout period.");
        if (preferences.getString(ssid, "").length() > 0) {
            Serial.println("Saved credentials may be outdated or incorrect.");
        }
    }

    Serial.println();
    Serial.print("> ");
}

void disconnectWiFi() {
    WiFi.disconnect();
    Serial.println("Disconnected from WiFi.");
    Serial.println();
    Serial.print("> ");
}

void loadWiFiCredentials() {
    String ssid = preferences.getString("ssid", "");
    String password = preferences.getString("password", "");
    if (ssid.length() > 0 && password.length() > 0) {
        WiFi.begin(ssid.c_str(), password.c_str());
    }
}

void saveWiFiCredentials(const char* ssid, const char* password) {
    preferences.putString("ssid", ssid);
    preferences.putString("password", password);
}

void listFiles() {
    File root = SPIFFS.open(currentDir);
    if (!root) {
        Serial.println("Failed to open directory.");
        Serial.println();
        Serial.print("> ");
        return;
    }
    if (!root.isDirectory()) {
        Serial.println("Not a directory.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    File file = root.openNextFile();
    while (file) {
        String type = file.isDirectory() ? "DIR" : "FILE";
        Serial.printf("%-32s %4s %8d bytes\n", file.name(), type.c_str(), file.size());
        file = root.openNextFile();
    }
    Serial.println();
    Serial.print("> ");
}

void changeDirectory(const char* dirName) {
    if (dirName == NULL) {
        Serial.println("Directory name not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    if (strcmp(dirName, "..") == 0) {
        int lastSlash = currentDir.lastIndexOf('/');
        if (lastSlash != -1) {
            currentDir = currentDir.substring(0, lastSlash);
            if (currentDir.length() == 0) {
                currentDir = "/";
            }
        }
    } else {
        if (!currentDir.endsWith("/")) {
            currentDir += "/";
        }
        currentDir += dirName;
    }
    Serial.println();
    Serial.print("> ");
}

void showFileContents(const char* filename) {
    String filePath = filename;
    if (currentDir != "/") {
        filePath = currentDir + "/" + filename;
    }

    File file = SPIFFS.open(filePath, FILE_READ);
    if (!file) {
        Serial.println("File not found.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    while (file.available()) {
        Serial.write(file.read());
    }
    file.close();
    Serial.println();
    Serial.print("> ");
}

void createFile(const char* filename) {
    if (filename == NULL) {
        Serial.println("File name not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    String filePath = filename;
    if (!filePath.startsWith("/")) {
        filePath = currentDir + "/" + filePath;
    }

    File file = SPIFFS.open(filePath, FILE_WRITE);
    if (!file) {
        Serial.println("Failed to create file.");
        Serial.println();
        Serial.print("> ");
        return;
    }
    file.close();
    Serial.println("File created.");
    Serial.println();
    Serial.print("> ");
}

void createDirectory(const char* dirName) {
    if (dirName == NULL) {
        Serial.println("Directory name not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    String dirPath = dirName;
    if (!dirPath.startsWith("/")) {
        dirPath = currentDir + "/" + dirPath;
    }

    if (SPIFFS.mkdir(dirPath)) {
        Serial.println("Directory created.");
    } else {
        Serial.println("Failed to create directory.");
    }
    Serial.println();
    Serial.print("> ");
}

void deleteFileOrDirectory(const char* name) {
    if (name == NULL) {
        Serial.println("Name not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    String path = name;
    if (currentDir != "/") {
        path = currentDir + "/" + name;
    }

    if (SPIFFS.remove(path)) {
        Serial.println("File/directory deleted.");
    } else {
        Serial.println("Failed to delete file/directory.");
    }
    Serial.println();
    Serial.print("> ");
}

void renameFile(const char* oldName, const char* newName) {
    if (oldName == NULL || newName == NULL) {
        Serial.println("Old name or new name not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    String oldPath = oldName;
    String newPath = newName;
    if (currentDir != "/") {
        oldPath = currentDir + "/" + oldName;
        newPath = currentDir + "/" + newName;
    }

    if (SPIFFS.rename(oldPath, newPath)) {
        Serial.println("File renamed.");
    } else {
        Serial.println("Failed to rename file.");
    }
    Serial.println();
    Serial.print("> ");
}

void moveFile(const char* src, const char* dest) {
    String srcPath = src;
    String destPath = dest;
    if (currentDir != "/") {
        srcPath = currentDir + "/" + src;
        destPath = currentDir + "/" + dest;
    }

    if (SPIFFS.rename(srcPath, destPath)) {
        Serial.println("File moved.");
    } else {
        Serial.println("Failed to move file.");
    }
    Serial.println();
    Serial.print("> ");
}

void displayHelp() {
    Serial.println("Available commands:");
    for (auto& command : commands) {
        Serial.printf("%s - %s\n", command.name, command.help);
    }
    Serial.println();
    Serial.print("> ");
}

void pingCommand(const char* address, int count) {
    if (address == NULL) {
        Serial.println("Address not provided.");
        Serial.println();
        Serial.print("> ");
        return;
    }

    if (count <= 0) {
        count = 5;
    }

    Serial.printf("Pinging %s with %d packets:\n", address, count);
    for (int i = 0; i < count; i++) {
        if (Ping.ping(address)) {
            Serial.printf("Ping %d successful\n", i + 1);
        } else {
            Serial.printf("Ping %d failed\n", i + 1);
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    Serial.println();
    Serial.print("> ");
}

void ifconfigCommand() {
    Serial.printf("IP Address: %s\n", WiFi.localIP().toString().c_str());
    Serial.printf("Subnet Mask: %s\n", WiFi.subnetMask().toString().c_str());
    Serial.printf("Gateway: %s\n", WiFi.gatewayIP().toString().c_str());
    Serial.printf("DNS: %s\n", WiFi.dnsIP().toString().c_str());
    Serial.printf("MAC Address: %s\n", WiFi.macAddress().c_str());
    Serial.println();
    Serial.print("> ");
}

void memoryInfoCommand() {
    Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
    Serial.println();
    Serial.print("> ");
}

void fetchmeCommand() {
    const char* logo[] = {
        "  ___                _   _      ___  ___ ",
        " / __|_ __  __ _ _ _| |_| |___ / _ \\/ __|",
        " \\__ \\ '_ \\/ _` | '_| / / / -_) (_) \\__ \\",
        " |___/ .__/\\__,_|_| |_\\_\\_\\___|\\___/|___/",
        "     |_|                                 ",
        "                                         ",
        "       ESP32 build Alpha 0.1.0-r1        ",
        "       By angeldev0                      "
    };

    int64_t uptimeSeconds = esp_timer_get_time() / 1000000;
    int days = uptimeSeconds / 86400;
    int hours = (uptimeSeconds % 86400) / 3600;
    int minutes = (uptimeSeconds % 3600) / 60;
    int seconds = uptimeSeconds % 60;

    const char* info[] = {
        "CPU: %s",
        "Number of CPU cores: %d",
        "SDK Version: %s",
        "Free Heap: %d bytes",
        "CPU Frequency: %d MHz",
        "IP Address: %s",
        "MAC Address: %s",
        "Uptime: %02d/%02d/%02d/%02d (dd/hh/mm/ss)",
        "Flash Chip Size: %d bytes",
        "Sketch Size: %d bytes",
        "Free Sketch Space: %d bytes",
        "Chip Revision: %d",
        "Temperature: %.2f °C",
        "Voltage: %.2f V"
    };

    const char* cpuModel = ESP.getChipModel();
    int cores = ESP.getChipCores();
    const char* sdkVersion = ESP.getSdkVersion();
    int freeHeap = ESP.getFreeHeap();
    int cpuFreqMHz = ESP.getCpuFreqMHz();
    String ipAddress = WiFi.localIP().toString();
    String macAddress = WiFi.macAddress();
    int flashChipSize = ESP.getFlashChipSize();
    int sketchSize = ESP.getSketchSize();
    int freeSketchSpace = ESP.getFreeSketchSpace();
    int chipRevision = ESP.getChipRevision();

    float temperature = 0.0;
    float voltage = 0.0;

    String formattedInfo[] = {
        String(cpuModel),
        String(cores),
        String(sdkVersion),
        String(freeHeap),
        String(cpuFreqMHz),
        ipAddress,
        macAddress,
        String(days) + "/" + String(hours) + "/" + String(minutes) + "/" + String(seconds),
        String(flashChipSize),
        String(sketchSize),
        String(freeSketchSpace),
        String(chipRevision),
        String(temperature, 2),
        String(voltage, 2)
    };

    int logoHeight = sizeof(logo) / sizeof(logo[0]);
    int infoHeight = sizeof(info) / sizeof(info[0]);
    int maxHeight = max(logoHeight, infoHeight);

    for (int i = 0; i < maxHeight; ++i) {
        if (i < logoHeight) {
            Serial.print(logo[i]);
        } else {
            Serial.print("                                         ");
        }
        Serial.print(" | ");

        if (i < infoHeight) {
            Serial.println(formattedInfo[i]);
        } else {
            Serial.println();
        }
    }

    Serial.println();
    Serial.print("> ");
}

void rebootCommand() {
    Serial.println("Rebooting...");
    ESP.restart();
}

void uptimeCommand() {
    Serial.printf("Uptime: %lld seconds\n", esp_timer_get_time() / 1000000);
    Serial.println();
    Serial.print("> ");
}

void freeMemCommand() {
    Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
    Serial.println();
    Serial.print("> ");
}

void handleCommand(char* input) {
    if (strlen(input) == 0) return;

    addToHistory(input);

    char* arg1 = strchr(input, ' ');
    if (arg1 != NULL) {
        *arg1 = '\0';
        arg1++;
    }

    char* arg2 = NULL;
    if (arg1 != NULL) {
        arg2 = strchr(arg1, ' ');
        if (arg2 != NULL) {
            *arg2 = '\0';
            arg2++;
        }
    }

    for (auto& command : commands) {
        if (strcmp(input, command.name) == 0) {
            command.func(arg1, arg2);
            return;
        }
    }

    Serial.println("Command not found. Type 'help' to see available commands.");
    Serial.print("> ");
}

void addToHistory(const char* command) {
    if (historyCount < MAX_HISTORY_LEN) {
        strncpy(commandHistory[historyCount], command, MAX_COMMAND_LEN - 1);
        commandHistory[historyCount][MAX_COMMAND_LEN - 1] = '\0';
        historyCount++;
    } else {
        for (int i = 1; i < MAX_HISTORY_LEN; i++) {
            strncpy(commandHistory[i - 1], commandHistory[i], MAX_COMMAND_LEN - 1);
            commandHistory[i - 1][MAX_COMMAND_LEN - 1] = '\0';
        }
        strncpy(commandHistory[MAX_HISTORY_LEN - 1], command, MAX_COMMAND_LEN - 1);
        commandHistory[MAX_HISTORY_LEN - 1][MAX_COMMAND_LEN - 1] = '\0';
    }
    historyIndex = historyCount;
}

void showCommandHistory() {
    Serial.println("Command History:");
    for (int i = 0; i < historyCount; i++) {
        Serial.printf("%d: %s\n", i + 1, commandHistory[i]);
    }
    Serial.print("> ");
}

void completeCommand(char* input) {
    int len = strlen(input);
    for (auto& command : commands) {
        if (strncmp(input, command.name, len) == 0) {
            strcpy(input, command.name);
            Serial.print("\r> ");
            Serial.print(input);
            return;
        }
    }
}

void handleHelpFlag(const char* command) {
    if (command != NULL && strcmp(command, "--help") == 0) {
        displayHelp();
    }
}