#include <Arduino.h>
#include <WiFi.h>
#include <SPI.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>

// Configurazione hardware
#define CS_PIN 5
#define DRDY_PIN 4

// Configurazione acquisizione
#define DEFAULT_SAMPLE_RATE 30000 // Hz

// Configurazione Wi-Fi
const char* WIFI_SSID = "WebPocket-E280";
const char* WIFI_PASSWORD = "dorabino.7468!";

// Gestione configurazione
struct Config {
    uint32_t sampleRate;
    uint8_t gain;
    bool filterEnabled;
    float threshold;
    bool streaming;
};

// Variabili globali
QueueHandle_t dataQueue;
QueueHandle_t eventQueue;
AsyncWebServer server(80);
AsyncWebSocket wsData("/data");
AsyncWebSocket wsEvents("/events");
Config globalConfig = {DEFAULT_SAMPLE_RATE, 1, false, 1000000, true};
float emaAlpha = 0.1; // Coefficiente EMA

// Gestione eventi WebSocket
void onEventsWebSocket(AsyncWebSocket *server, AsyncWebSocketClient *client, 
                      AwsEventType type, void *arg, uint8_t *data, size_t len) {
    if (type == WS_EVT_CONNECT) {
        Serial.printf("Client eventi %u connesso\n", client->id());
    } else if (type == WS_EVT_DISCONNECT) {
        Serial.printf("Client eventi %u disconnesso\n", client->id());
    } else if (type == WS_EVT_DATA) {
        String message = String((char*)data).substring(0, len);
        StaticJsonDocument<200> doc;
        DeserializationError error = deserializeJson(doc, message);
        
        if (!error) {
            if (doc.containsKey("samplerate")) {
                globalConfig.sampleRate = doc["samplerate"].as<int>();
                Serial.printf("Sample rate impostato a: %d\n", globalConfig.sampleRate);
            }
            
            if (doc.containsKey("alfaema")) {
                emaAlpha = doc["alfaema"].as<float>();
                Serial.printf("EMA alpha impostato a: %.2f\n", emaAlpha);
            }
        }
    }
}

/*
void IRAM_ATTR adcTask(void* pvParameters) {
    SPIClass spi(VSPI);
    spi.begin(18, 19, 23, CS_PIN);
    pinMode(CS_PIN, OUTPUT);
    pinMode(DRDY_PIN, INPUT);

    uint32_t lastSample = 0;
    uint32_t sampleInterval = 1000000 / globalConfig.sampleRate;
    float emaFilteredValue = 0.0;

    while (true) {
        if (!globalConfig.streaming) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
            continue;
        }

        if (digitalRead(DRDY_PIN) == LOW &&
            (micros() - lastSample) >= sampleInterval) {

            digitalWrite(CS_PIN, LOW);
            uint8_t data[3] = {0x01, 0x02, 0x03};
            digitalWrite(CS_PIN, HIGH);

            int32_t value = (data[0] << 16) | (data[1] << 8) | data[2];
            if (value & 0x800000) value -= 0x1000000;

            emaFilteredValue = emaAlpha * value + (1 - emaAlpha) * emaFilteredValue;

            xQueueSend(dataQueue, &emaFilteredValue, 0);
            lastSample = micros();
        }
    }
}
*/

void printWiFiDetails() {
    if(WiFi.status() == WL_CONNECTED) {
        Serial.println("\nWiFi Connection Details:");
        Serial.printf("SSID: %s\n", WiFi.SSID().c_str());
        Serial.printf("BSSID: %s\n", WiFi.BSSIDstr().c_str());
        Serial.printf("Channel: %d\n", WiFi.channel());
        Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
        Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
        Serial.printf("Subnet: %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: %s\n", WiFi.macAddress().c_str());
    }
}

void printFailReason(wifi_err_reason_t reason) {
    switch(reason) {
        case WIFI_REASON_AUTH_EXPIRE:
            Serial.println("Auth expire");
            break;
        case WIFI_REASON_AUTH_LEAVE:
            Serial.println("Auth leave");
            break;
        case WIFI_REASON_ASSOC_EXPIRE:
            Serial.println("Association expire");
            break;
        case WIFI_REASON_ASSOC_TOOMANY:
            Serial.println("Too many associations");
            break;
        case WIFI_REASON_NOT_AUTHED:
            Serial.println("Not authenticated");
            break;
        case WIFI_REASON_NOT_ASSOCED:
            Serial.println("Not associated");
            break;
        case WIFI_REASON_ASSOC_LEAVE:
            Serial.println("Association leave");
            break;
        case WIFI_REASON_HANDSHAKE_TIMEOUT:
            Serial.println("Handshake timeout");
            break;
        default:
            Serial.printf("Unknown reason: %d\n", reason);
            break;
    }
}


void printHeapInfo() {
    Serial.printf("Free Heap: %d bytes\n", ESP.getFreeHeap());
    Serial.printf("Min Free Heap: %d bytes\n", ESP.getMinFreeHeap());
    Serial.printf("Max Alloc Heap: %d bytes\n", ESP.getMaxAllocHeap());
}

void adcTask(void* pvParameters) {
    uint32_t lastSample = 0;
    uint32_t sampleInterval = 1000000 / globalConfig.sampleRate;

    while (true) {
        if (!globalConfig.streaming) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
            continue;
        }

        if ((micros() - lastSample) >= sampleInterval) {
            uint32_t timestamp = micros();
            float sample = random(0, 1000);  // Campioni casuali (da 0 a 1000)

            // Pacchetto JSON con campione e timestamp
            char message[128];
            snprintf(message, sizeof(message), "{\"timestamp\": %u, \"sample\": %.2f}", timestamp, sample);

            // Invia al server WebSocket
            xQueueSend(dataQueue, &message, 0);
            lastSample = timestamp;
        }
    }
}

// Task gestione dati WebSocket (Core 1)
void webSocketDataTask(void* pvParameters) {
    float value;
    char buffer[32];
    
    while(true) {
        if(xQueueReceive(dataQueue, &value, portMAX_DELAY) == pdTRUE) {
            if(wsData.count() > 0) {  // Se ci sono client connessi
                snprintf(buffer, sizeof(buffer), "%.2f", value);
                wsData.textAll(buffer);
            }
        }
    }
}

void setup() {
    Serial.begin(9600);
    
    // Abilita log verbose
    esp_log_level_set("wifi", ESP_LOG_VERBOSE);
       
    // Configura WiFi
    WiFi.mode(WIFI_STA);
    Serial.print("Connecting to WiFi");
     WiFi.begin("Wokwi-GUEST", "", 6);
    
    // Loop connessione con timeout
    uint8_t attempts = 0;
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    
    printWiFiDetails();

    printHeapInfo();
    // Aspetta un attimo per stabilizzare la connessione
    delay(1000);

    // Crea le code
    dataQueue = xQueueCreate(1024, sizeof(float));
    eventQueue = xQueueCreate(32, sizeof(char*));

    // Configurazione WebSocket
    wsEvents.onEvent(onEventsWebSocket);
    server.addHandler(&wsEvents);
    server.addHandler(&wsData);
    server.begin();

    // Avvia i task sui core specifici
     // WebSocket sul Core 0 dove già gira il networking
    xTaskCreatePinnedToCore(webSocketDataTask, "WS Data Task", 8192, NULL, 1, NULL, 0);
    Serial.println("\nTask websocket creato");
    delay(2000);
    // ADC sul Core 1 dove ha più risorse dedicate
    //xTaskCreatePinnedToCore(adcTask, "ADC Task", 16384, NULL, 5, NULL, 1);
    Serial.println("\nTask ADC creato");

    Serial.println("Sistema inizializzato");
}

void loop() {
    delay(1);  // Previene watchdog reset
}
esp:0
esp:2
esp:4
esp:5
esp:12
esp:13
esp:14
esp:15
esp:16
esp:17
esp:18
esp:19
esp:21
esp:22
esp:23
esp:25
esp:26
esp:27
esp:32
esp:33
esp:34
esp:35
esp:3V3
esp:EN
esp:VP
esp:VN
esp:GND.1
esp:D2
esp:D3
esp:CMD
esp:5V
esp:GND.2
esp:TX
esp:RX
esp:GND.3
esp:D1
esp:D0
esp:CLK