#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <DHT.h>
#include <libmaple/flash.h>
#include <RTClock.h>
#include <ArduinoJson.h>
#include <WebSocketsServer.h>
#include <algorithm>
#include <cmath>
#include "config.h"
#include "structures.h"

#define CS_PIN PA4

DHT dht(DHT_PIN, DHT22);
RTClock rt(RTCSEL_LSE);
EthernetUDP Udp;  // Istanza UDP per NTP


unsigned int localPort = 8888;  // Porta locale per UDP
const char* ntpServer = "pool.ntp.org";  // Server NTP
byte packetBuffer[NTP_PACKET_SIZE];
unsigned long lastNTPSync = 0;
const unsigned long NTP_SYNC_INTERVAL = 3600000; // Sincronizza l`ora ogni ora

Settings settings;
SensorData sensorData;
unsigned long previousMillis = 0;
const long interval = 500;

uint8_t getHours() {
    return (uint8_t)((rt.getTime()/3600)%24);
}

uint8_t getMinutes() {
    return (uint8_t)((rt.getTime()/60)%60);
}

const unsigned long SAVE_INTERVAL = 3600000; // 1 ora in millisecondi
bool settingsChanged = false;

void setupNTP() {
    Udp.begin(localPort);
}

void syncTime() {
    sendNTPpacket(ntpServer);
    delay(1000);

    if (Udp.parsePacket()) {
        Udp.read(packetBuffer, NTP_PACKET_SIZE);

        unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
        unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
        unsigned long secsSince1900 = highWord << 16 | lowWord;

        const unsigned long seventyYears = 2208988800UL;
        unsigned long epoch = secsSince1900 - seventyYears + (TIME_ZONE * TIME_ZONE_OFFSET);

        rt.setTime(epoch);
    }
}

void sendNTPpacket(const char* address) {
    memset(packetBuffer, 0, NTP_PACKET_SIZE);

    packetBuffer[0] = 0b11100011;
    packetBuffer[1] = 0;
    packetBuffer[2] = 6;
    packetBuffer[3] = 0xEC;

    Udp.beginPacket(address, 123);
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
}

void writeFlash(uint32_t addr, uint8_t* data, uint32_t count) {
    noInterrupts();
    uint32_t timeout = 50000;

    *FLASH_KEYR = FLASH_KEY1;
    *FLASH_KEYR = FLASH_KEY2;

    *FLASH_CR = 0x02;
    *FLASH_AR = addr;
    *FLASH_CR = 0x42;
    while((*FLASH_SR & 0x01) && --timeout);

    if (timeout == 0) {
        *FLASH_CR = 0x80;
        interrupts();
        return;
    }

    *FLASH_CR = 0x01;
    for(uint32_t i = 0; i < count; i += 2) {
        timeout = 50000;
        *(volatile uint16_t*)(addr + i) = *(uint16_t*)(data + i);
        while((*FLASH_SR & 0x01) && --timeout);

        if (timeout == 0) {
            break;
        }
    }

    *FLASH_CR = 0x80;
    interrupts();
}

void loadSettings() {
    memcpy(&settings, (void*)FLASH_START_ADDR, sizeof(Settings));

    if (settings.numero_magico != NUMERO_MAGICO) {
        initValori();
    }
}

void initValori(){
    if (settings.numero_magico != NUMERO_MAGICO) {
        settings.numero_magico = NUMERO_MAGICO;

        // Luci
        settings.lightManualMode = true;
        settings.lightManualState = false;
        settings.lightTimer = {8, 0, 20, 0, false};  // 8:00-20:00

        // Aspiratore
        settings.aspiratorManualMode = true;
        settings.aspiratorManualState = false;
        settings.aspiratorTempMode = false;
        settings.aspiratorHumidityMode = false;
        settings.tempMaxAspirator = 25.0;
        settings.maxHumidity = 70.0;

        // Riscaldamento
        settings.heaterManualMode = true;
        settings.heaterManualState = false;
        settings.tempMinHeater = 15.0;
        settings.heaterTempMode = false;

        // Pompa 1
        settings.pump1ManualMode = true;
        settings.pump1ManualState = false;
        settings.pump1SoilMode = false;
        settings.pump1SoilThreshold = 30.0;
        for(int i = 0; i < MAX_TIMER_STEPS; i++) {
            settings.pump1Timer[i] = {8, 0, 8, 15, false};  // 8:00-8:15
        }

        // Pompa 2
        settings.pump2ManualMode = true;
        settings.pump2ManualState = false;
        settings.pump2SoilMode = false;
        settings.pump2SoilThreshold = 30.0;
        for(int i = 0; i < MAX_TIMER_STEPS; i++) {
            settings.pump2Timer[i] = {8, 0, 8, 15, false};  // 8:00-8:15
        }

        settingsChanged = true;
        saveSettings();
    }
}

void saveSettings() {
    if (!settingsChanged) return;
    writeFlash(FLASH_START_ADDR, (uint8_t*)&settings, sizeof(Settings));
    settingsChanged = false;
}

void readSensors() {
    sensorData.temperature = dht.readTemperature();
    sensorData.humidity = dht.readHumidity();
    uint16_t rawValue = analogRead(SOIL_SENSOR) >> 2;
    uint16_t invertedValue = 1023 - rawValue;
    sensorData.soilMoisture = constrain(map(invertedValue, 0, 800, 0, 100), 0, 100);

    if (std::isnan(sensorData.temperature) || std::isnan(sensorData.humidity)) {
        Serial.println("Errore lettura DHT22");
    }
}

bool isTimeBetween(int currentHour, int currentMin, int startHour, int startMin, int endHour, int endMin) {
    int current = currentHour * 60 + currentMin;
    int start = startHour * 60 + startMin;
    int end = endHour * 60 + endMin;

    if (start <= end) {
        return (current >= start && current < end);
    } else {
        return (current >= start || current < end);
    }
}

void controlLights() {
    bool lightState = false;
    uint8_t currentHour = getHours();
    uint8_t currentMin = getMinutes();

    if (settings.lightManualMode) {
        lightState = settings.lightManualState;
    } else {
        if (settings.lightTimer.active) {
            lightState = isTimeBetween(
                currentHour, currentMin,
                settings.lightTimer.startHour, settings.lightTimer.startMin,
                settings.lightTimer.endHour, settings.lightTimer.endMin
            );
        }
    }

    settings.currentLightState = lightState;
    digitalWrite(RELE_LUCI, lightState);
}

void controlPump(uint8_t pumpPin, bool manualMode, bool manualState,
                bool soilMode, int soilThreshold, TimerStep timerSteps[MAX_TIMER_STEPS]) {
    bool pumpState = false;
    uint8_t currentHour = getHours();
    uint8_t currentMin = getMinutes();

    if (manualMode) {
        pumpState = manualState;
    } else if (soilMode) {
        pumpState = (sensorData.soilMoisture < soilThreshold);
    } else {
        for (int i = 0; i < MAX_TIMER_STEPS; i++) {
            if (timerSteps[i].active) {
                if (isTimeBetween(
                    currentHour, currentMin,
                    timerSteps[i].startHour, timerSteps[i].startMin,
                    timerSteps[i].endHour, timerSteps[i].endMin
                )) {
                    pumpState = true;
                    break;
                }
            }
        }
    }

    if (pumpPin == RELE_POMPA1) {
        settings.currentPump1State = pumpState;
    } else if (pumpPin == RELE_POMPA2) {
        settings.currentPump2State = pumpState;
    }

    digitalWrite(pumpPin, pumpState);
}

void controlEnvironment() {
    bool aspiratorState = false;
    bool heaterState = false;

    if (settings.aspiratorManualMode) {
        aspiratorState = settings.aspiratorManualState;
    } else {
        if (settings.aspiratorTempMode && sensorData.temperature > settings.tempMaxAspirator) {
            aspiratorState = true;
        }
        if (settings.aspiratorHumidityMode && sensorData.humidity > settings.maxHumidity) {
            aspiratorState = true;
        }
    }

    if (settings.heaterManualMode) {
        heaterState = settings.heaterManualState;
    } else {
        if (settings.heaterTempMode && sensorData.temperature < settings.tempMinHeater) {
            heaterState = true;
        }
    }

    settings.currentAspiratorState = aspiratorState;
    settings.currentHeaterState = heaterState;

    digitalWrite(RELE_ASPIRATORE, aspiratorState);
    digitalWrite(RELE_STUFA, heaterState);
}

void controlDevices() {
    controlLights();

    controlPump(RELE_POMPA1,
                settings.pump1ManualMode,
                settings.pump1ManualState,
                settings.pump1SoilMode,
                settings.pump1SoilThreshold,
                settings.pump1Timer);

    controlPump(RELE_POMPA2,
                settings.pump2ManualMode,
                settings.pump2ManualState,
                settings.pump2SoilMode,
                settings.pump2SoilThreshold,
                settings.pump2Timer);

    controlEnvironment();
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case WStype_DISCONNECTED:
            //Serial.println("WebSocket Disconnected");
            break;

        case WStype_CONNECTED:
            Serial.println("WebSocket Connected");
            break;

        case WStype_TEXT: {
            String message = (char*)payload;
            StaticJsonDocument<1024> doc;
            DeserializationError error = deserializeJson(doc, message);
            
            if (error) {
                Serial.println("Failed to parse JSON");
                return;
            }

            const char* token = doc["token"];
            if (!token || strcmp(token, STORED_TOKEN) != 0) {
                String errorResponse = "{\"id\":" + String(doc["id"].as<int>()) + ",\"error\":\"Invalid token\"}";
                webSocket.sendTXT(num, errorResponse);
                return;
            }

            const char* msgType = doc["type"];
            int messageId = doc["id"];

            if (strcmp(msgType, "ping") == 0) {
                String response = "{\"id\":" + String(messageId) + ",\"success\":true}";
                webSocket.sendTXT(num, response);
            }
            else if (strcmp(msgType, "getStatus") == 0) {
                sendStatusResponse(num, messageId);
            }
            else if (strcmp(msgType, "getSettings") == 0) {
                sendSettingsResponse(num, messageId);
            }
            else if (strcmp(msgType, "setOutput") == 0) {
                handleOutputRequest(doc["payload"], num, messageId);
            }
            else if (strcmp(msgType, "updateSettings") == 0) {
                parseSettings(doc["payload"]);
                String response = "{\"id\":" + String(messageId) + ",\"success\":true}";
                webSocket.sendTXT(num, response);
            }
            break;
        }
    }
}

void sendStatusResponse(uint8_t num, int messageId) {
    StaticJsonDocument<512> doc;
    doc["id"] = messageId;
    JsonObject data = doc.createNestedObject("data");
    
    JsonObject sensors = data.createNestedObject("sensors");
    sensors["temperature"] = sensorData.temperature;
    sensors["humidity"] = sensorData.humidity;
    sensors["soil"] = sensorData.soilMoisture;
    
    JsonObject states = data.createNestedObject("states");
    states["light"] = settings.currentLightState;
    states["pump1"] = settings.currentPump1State;
    states["pump2"] = settings.currentPump2State;
    states["aspirator"] = settings.currentAspiratorState;
    states["heater"] = settings.currentHeaterState;

    String response;
    serializeJson(doc, response);
    webSocket.sendTXT(num, response);
}

void sendSettingsResponse(uint8_t num, int messageId) {
    StaticJsonDocument<1024> doc;
    doc["id"] = messageId;
    JsonObject data = doc.createNestedObject("data");
    serializeSettings(data);
    
    String response;
    serializeJson(doc, response);
    webSocket.sendTXT(num, response);
}

void serializeSettings(JsonObject& data) {
    // Light settings
    JsonObject light = data.createNestedObject("light");
    light["manualMode"] = settings.lightManualMode;
    light["manualState"] = settings.lightManualState;
    JsonObject lightTimer = light.createNestedObject("timer");
    lightTimer["startHour"] = settings.lightTimer.startHour;
    lightTimer["startMin"] = settings.lightTimer.startMin;
    lightTimer["endHour"] = settings.lightTimer.endHour;
    lightTimer["endMin"] = settings.lightTimer.endMin;
    lightTimer["active"] = settings.lightTimer.active;

    // Pump1 settings
    JsonObject pump1 = data.createNestedObject("pump1");
    pump1["manualMode"] = settings.pump1ManualMode;
    pump1["manualState"] = settings.pump1ManualState;
    pump1["soilMode"] = settings.pump1SoilMode;
    pump1["soilThreshold"] = settings.pump1SoilThreshold;
    JsonArray pump1Timers = pump1.createNestedArray("timerSteps");
    for(int i = 0; i < MAX_TIMER_STEPS; i++) {
        if(settings.pump1Timer[i].active) {
            JsonObject timer = pump1Timers.createNestedObject();
            timer["startHour"] = settings.pump1Timer[i].startHour;
            timer["startMin"] = settings.pump1Timer[i].startMin;
            timer["endHour"] = settings.pump1Timer[i].endHour;
            timer["endMin"] = settings.pump1Timer[i].endMin;
            timer["active"] = settings.pump1Timer[i].active;
        }
    }

    // Pump2 settings
    JsonObject pump2 = data.createNestedObject("pump2");
    pump2["manualMode"] = settings.pump2ManualMode;
    pump2["manualState"] = settings.pump2ManualState;
    pump2["soilMode"] = settings.pump2SoilMode;
    pump2["soilThreshold"] = settings.pump2SoilThreshold;
    JsonArray pump2Timers = pump2.createNestedArray("timerSteps");
    for(int i = 0; i < MAX_TIMER_STEPS; i++) {
        if(settings.pump2Timer[i].active) {
            JsonObject timer = pump2Timers.createNestedObject();
            timer["startHour"] = settings.pump2Timer[i].startHour;
            timer["startMin"] = settings.pump2Timer[i].startMin;
            timer["endHour"] = settings.pump2Timer[i].endHour;
            timer["endMin"] = settings.pump2Timer[i].endMin;
            timer["active"] = settings.pump2Timer[i].active;
        }
    }

    // Aspirator settings
    JsonObject aspirator = data.createNestedObject("aspirator");
    aspirator["manualMode"] = settings.aspiratorManualMode;
    aspirator["manualState"] = settings.aspiratorManualState;
    aspirator["tempControl"] = settings.aspiratorTempMode;
    aspirator["humidityControl"] = settings.aspiratorHumidityMode;
    aspirator["maxTemp"] = settings.tempMaxAspirator;
    aspirator["maxHumidity"] = settings.maxHumidity;

    // Heater settings
    JsonObject heater = data.createNestedObject("heater");
    heater["manualMode"] = settings.heaterManualMode;
    heater["manualState"] = settings.heaterManualState;
    heater["tempControl"] = settings.heaterTempMode;
    heater["minTemp"] = settings.tempMinHeater;
}

void handleOutputRequest(JsonObject payload, uint8_t num, int messageId) {
    const char* type = payload["type"];
    bool value = payload["value"].as<bool>();
    bool success = false;

    if (strcmp(type, "light") == 0 && settings.lightManualMode) {
        settings.lightManualState = value;
        settings.currentLightState = value;
        success = true;
    }
    else if (strcmp(type, "pump1") == 0 && settings.pump1ManualMode) {
        settings.pump1ManualState = value;
        settings.currentPump1State = value;
        success = true;
    }
    else if (strcmp(type, "pump2") == 0 && settings.pump2ManualMode) {
        settings.pump2ManualState = value;
        settings.currentPump2State = value;
        success = true;
    }
    else if (strcmp(type, "aspirator") == 0 && settings.aspiratorManualMode) {
        settings.aspiratorManualState = value;
        settings.currentAspiratorState = value;
        success = true;
    }
    else if (strcmp(type, "heater") == 0 && settings.heaterManualMode) {
        settings.heaterManualState = value;
        settings.currentHeaterState = value;
        success = true;
    }

    String response = "{\"id\":" + String(messageId) + ",\"success\":" + (success ? "true" : "false") + "}";
    webSocket.sendTXT(num, response);
}

void parseAspiratorSettings(JsonObject& settingsObj) {
    if (settingsObj.containsKey("manualMode")) {
        settings.aspiratorManualMode = settingsObj["manualMode"];
    }
    if (settingsObj.containsKey("manualState")) {
        settings.aspiratorManualState = settingsObj["manualState"];
    }
    if (settingsObj.containsKey("tempControl")) {
        settings.aspiratorTempMode = settingsObj["tempControl"];
    }
    if (settingsObj.containsKey("humidityControl")) {
        settings.aspiratorHumidityMode = settingsObj["humidityControl"];
    }
    if (settingsObj.containsKey("maxTemp")) {
        settings.tempMaxAspirator = settingsObj["maxTemp"];
    }
    if (settingsObj.containsKey("maxHumidity")) {
        settings.maxHumidity = settingsObj["maxHumidity"];
    }
}

void parseHeaterSettings(JsonObject& settingsObj) {
    if (settingsObj.containsKey("manualMode")) {
        settings.heaterManualMode = settingsObj["manualMode"];
    }
    if (settingsObj.containsKey("manualState")) {
        settings.heaterManualState = settingsObj["manualState"];
    }
    if (settingsObj.containsKey("tempControl")) {
        settings.heaterTempMode = settingsObj["tempControl"];
    }
    if (settingsObj.containsKey("minTemp")) {
        settings.tempMinHeater = settingsObj["minTemp"];
    }
}

void handleSettingsRequest() {
    StaticJsonDocument<1024> doc;
    JsonObject root = doc.to<JsonObject>();

    // Luci
    JsonObject light = root.createNestedObject("light");
    light["manualMode"] = settings.lightManualMode;
    light["manualState"] = settings.lightManualState;
    JsonObject lightTimer = light.createNestedObject("timer");
    lightTimer["startHour"] = settings.lightTimer.startHour;
    lightTimer["startMin"] = settings.lightTimer.startMin;
    lightTimer["endHour"] = settings.lightTimer.endHour;
    lightTimer["endMin"] = settings.lightTimer.endMin;
    lightTimer["active"] = settings.lightTimer.active;

    // Pompa 1
    JsonObject pump1 = root.createNestedObject("pump1");
    pump1["manualMode"] = settings.pump1ManualMode;
    pump1["manualState"] = settings.pump1ManualState;
    pump1["soilMode"] = settings.pump1SoilMode;
    pump1["soilThreshold"] = settings.pump1SoilThreshold;
    JsonArray pump1Steps = pump1.createNestedArray("timerSteps");

    for(int i = 0; i < MAX_TIMER_STEPS; i++) {
        if(settings.pump1Timer[i].active) {
            JsonObject step = pump1Steps.createNestedObject();
            step["startHour"] = settings.pump1Timer[i].startHour;
            step["startMin"] = settings.pump1Timer[i].startMin;
            step["endHour"] = settings.pump1Timer[i].endHour;
            step["endMin"] = settings.pump1Timer[i].endMin;
            step["active"] = settings.pump1Timer[i].active;
        }
    }

    // Pompa 2
    JsonObject pump2 = root.createNestedObject("pump2");
    pump2["manualMode"] = settings.pump2ManualMode;
    pump2["manualState"] = settings.pump2ManualState;
    pump2["soilMode"] = settings.pump2SoilMode;
    pump2["soilThreshold"] = settings.pump2SoilThreshold;
    JsonArray pump2Steps = pump2.createNestedArray("timerSteps");

    for(int i = 0; i < MAX_TIMER_STEPS; i++) {
        if(settings.pump2Timer[i].active) {
            JsonObject step = pump2Steps.createNestedObject();
            step["startHour"] = settings.pump2Timer[i].startHour;
            step["startMin"] = settings.pump2Timer[i].startMin;
            step["endHour"] = settings.pump2Timer[i].endHour;
            step["endMin"] = settings.pump2Timer[i].endMin;
            step["active"] = settings.pump2Timer[i].active;
        }
    }

    // Aspiratore
    JsonObject aspirator = root.createNestedObject("aspirator");
    aspirator["manualMode"] = settings.aspiratorManualMode;
    aspirator["manualState"] = settings.aspiratorManualState;
    aspirator["tempControl"] = settings.aspiratorTempMode;
    aspirator["humidityControl"] = settings.aspiratorHumidityMode;
    aspirator["maxTemp"] = settings.tempMaxAspirator;
    aspirator["maxHumidity"] = settings.maxHumidity;

    // Riscaldamento
    JsonObject heater = root.createNestedObject("heater");
    heater["manualMode"] = settings.heaterManualMode;
    heater["manualState"] = settings.heaterManualState;
    heater["tempControl"] = settings.heaterTempMode;
    heater["minTemp"] = settings.tempMinHeater;

    serializeJsonPretty(root, Serial);

    String response;
    serializeJson(root, response);
    webSocket.broadcastTXT(response);
}

void parseSettings(const String& jsonString) {
    StaticJsonDocument<1024> doc;
    DeserializationError error = deserializeJson(doc, jsonString);

    if (error) {
        return;
    }

    const char* type = doc["type"];
    JsonObject settingsObj = doc["settings"];

    if (settingsObj.containsKey("settings")) {
        settingsObj = settingsObj["settings"];
    }

    if (!type || settingsObj.isNull()) {
        return;
    }

    if (strcmp(type, "light") == 0) {
        parseLightSettings(settingsObj);
    }
    else if (strcmp(type, "pump1") == 0) {
        parsePump1Settings(settingsObj);
    }
    else if (strcmp(type, "pump2") == 0) {
        parsePump2Settings(settingsObj);
    }
    else if (strcmp(type, "aspirator") == 0) {
        parseAspiratorSettings(settingsObj);
    }
    else if (strcmp(type, "heater") == 0) {
        parseHeaterSettings(settingsObj);
    }

    settingsChanged = true;
    saveSettings();
}

void parseLightSettings(JsonObject& settingsObj) {
    if (settingsObj.containsKey("manualMode")) {
        settings.lightManualMode = settingsObj["manualMode"].as<bool>();
    }
    if (settingsObj.containsKey("manualState")) {
        settings.lightManualState = settingsObj["manualState"].as<bool>();
    }

    if (settingsObj.containsKey("timer")) {
        JsonObject timer = settingsObj["timer"];
        settings.lightTimer.startHour = timer["startHour"] | settings.lightTimer.startHour;
        settings.lightTimer.startMin = timer["startMin"] | settings.lightTimer.startMin;
        settings.lightTimer.endHour = timer["endHour"] | settings.lightTimer.endHour;
        settings.lightTimer.endMin = timer["endMin"] | settings.lightTimer.endMin;
        settings.lightTimer.active = timer["active"] | settings.lightTimer.active;
    }
}

void parsePumpSettings(JsonObject& settingsObj, bool& manualMode, bool& manualState,
                      bool& soilMode, float& soilThreshold, TimerStep* timerSteps) {

    if (settingsObj.containsKey("manualMode")) {
        manualMode = settingsObj["manualMode"].as<bool>();
    }
    if (settingsObj.containsKey("manualState")) {
        manualState = settingsObj["manualState"].as<bool>();
    }
    if (settingsObj.containsKey("soilMode")) {
        soilMode = settingsObj["soilMode"].as<bool>();
    }
    if (settingsObj.containsKey("soilThreshold")) {
        soilThreshold = settingsObj["soilThreshold"].as<int>();
    }

    if (settingsObj.containsKey("timerSteps")) {
        JsonArray steps = settingsObj["timerSteps"];

        for(int i = 0; i < MAX_TIMER_STEPS; i++) {
            timerSteps[i].active = false;
        }

        int numSteps = std::min((int)steps.size(), MAX_TIMER_STEPS);
        for(int i = 0; i < numSteps; i++) {
            JsonObject step = steps[i];
            timerSteps[i].startHour = step["startHour"] | timerSteps[i].startHour;
            timerSteps[i].startMin = step["startMin"] | timerSteps[i].startMin;
            timerSteps[i].endHour = step["endHour"] | timerSteps[i].endHour;
            timerSteps[i].endMin = step["endMin"] | timerSteps[i].endMin;
            timerSteps[i].active = step["active"] | timerSteps[i].active;
        }
    }
}

void parsePump1Settings(JsonObject& settingsObj) {
    parsePumpSettings(settingsObj,
                     settings.pump1ManualMode,
                     settings.pump1ManualState,
                     settings.pump1SoilMode,
                     settings.pump1SoilThreshold,
                     settings.pump1Timer);
}

void parsePump2Settings(JsonObject& settingsObj) {
    parsePumpSettings(settingsObj,
                     settings.pump2ManualMode,
                     settings.pump2ManualState,
                     settings.pump2SoilMode,
                     settings.pump2SoilThreshold,
                     settings.pump2Timer);
}


void setup() {
    Serial.begin(SERIAL_SPEED);
    loadSettings();

    pinMode(RELE_LUCI, OUTPUT);
    pinMode(RELE_ASPIRATORE, OUTPUT);
    pinMode(RELE_STUFA, OUTPUT);
    pinMode(RELE_POMPA1, OUTPUT);
    pinMode(RELE_POMPA2, OUTPUT);
    pinMode(SOIL_SENSOR, INPUT_ANALOG);

    digitalWrite(RELE_LUCI, LOW);
    digitalWrite(RELE_ASPIRATORE, LOW);
    digitalWrite(RELE_STUFA, LOW);
    digitalWrite(RELE_POMPA1, LOW);
    digitalWrite(RELE_POMPA2, LOW);

    SPI.begin();
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);

    pinMode(CS_PIN, OUTPUT);
    digitalWrite(CS_PIN, HIGH);
    Ethernet.init(CS_PIN);

    Ethernet.begin(mac, ip, dns, gw, sn);

    dht.begin();

    setupNTP();
    syncTime();

    webSocket.begin();
    webSocket.onEvent(webSocketEvent);
}

void loop() {
    static unsigned long lastSensorRead = 0;
    static unsigned long lastSaveCheck = 0;

    webSocket.loop();

    if (millis() - lastSensorRead >= 1000) {
        readSensors();
        lastSensorRead = millis();
    }

    if (millis() - lastSaveCheck >= SAVE_INTERVAL) {
        if (settingsChanged) {
            saveSettings();
        }
        lastSaveCheck = millis();
    }

    unsigned long currentMillis = millis();

    if (currentMillis - previousMillis >= interval) {
        previousMillis = currentMillis;
        controlDevices();
    }

    if (currentMillis - lastNTPSync >= NTP_SYNC_INTERVAL) {
        lastNTPSync = currentMillis;
        syncTime();
    }
}