/*
 ESP8266WiFiGeneric.cpp - WiFi library for esp8266

 Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
 This file is part of the esp8266 core for Arduino environment.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

 Reworked on 28 Dec 2015 by Markus Sattler

 */

#include "WiFi.h"
#include "WiFiGeneric.h"
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED

extern "C" {
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>

#include <esp_err.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_mac.h>
#include <esp_netif.h>
#if SOC_WIFI_SUPPORTED
#include <esp_phy.h>
#endif
#include "lwip/ip_addr.h"
#include "lwip/opt.h"
#include "lwip/err.h"
#include "lwip/dns.h"
#include "lwip/netif.h"
#include "dhcpserver/dhcpserver.h"
#include "dhcpserver/dhcpserver_options.h"

}  //extern "C"

#include "esp32-hal.h"
#include <vector>
#include "sdkconfig.h"

ESP_EVENT_DEFINE_BASE(ARDUINO_EVENTS);

static esp_netif_t *esp_netifs[ESP_IF_MAX] = {NULL, NULL, NULL};

esp_netif_t *get_esp_interface_netif(esp_interface_t interface) {
  if (interface < ESP_IF_MAX) {
    return esp_netifs[interface];
  }
  return NULL;
}

static void _arduino_event_cb(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
  (void)arg;
  arduino_event_t arduino_event;
  arduino_event.event_id = ARDUINO_EVENT_MAX;

  /*
	 * SCAN
	 * */
  if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
    wifi_event_sta_scan_done_t *event = (wifi_event_sta_scan_done_t *)event_data;
    log_v("SCAN Done: ID: %u, Status: %u, Results: %u", event->scan_id, event->status, event->number);
#endif
    arduino_event.event_id = ARDUINO_EVENT_WIFI_SCAN_DONE;
    memcpy(&arduino_event.event_info.wifi_scan_done, event_data, sizeof(wifi_event_sta_scan_done_t));

    /*
	 * WPS
	 * */
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_SUCCESS) {
    arduino_event.event_id = ARDUINO_EVENT_WPS_ER_SUCCESS;
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_FAILED) {
    arduino_event.event_id = ARDUINO_EVENT_WPS_ER_FAILED;
    memcpy(&arduino_event.event_info.wps_fail_reason, event_data, sizeof(wifi_event_sta_wps_fail_reason_t));
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_TIMEOUT) {
    arduino_event.event_id = ARDUINO_EVENT_WPS_ER_TIMEOUT;
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PIN) {
    arduino_event.event_id = ARDUINO_EVENT_WPS_ER_PIN;
    memcpy(&arduino_event.event_info.wps_er_pin, event_data, sizeof(wifi_event_sta_wps_er_pin_t));
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_WPS_ER_PBC_OVERLAP) {
    arduino_event.event_id = ARDUINO_EVENT_WPS_ER_PBC_OVERLAP;

    /*
	 * FTM
	 * */
  } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_FTM_REPORT) {
    arduino_event.event_id = ARDUINO_EVENT_WIFI_FTM_REPORT;
    memcpy(&arduino_event.event_info.wifi_ftm_report, event_data, sizeof(wifi_event_ftm_report_t));

#if !CONFIG_ESP_WIFI_REMOTE_ENABLED
    /*
	 * SMART CONFIG
	 * */
  } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
    log_v("SC Scan Done");
    arduino_event.event_id = ARDUINO_EVENT_SC_SCAN_DONE;
  } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
    log_v("SC Found Channel");
    arduino_event.event_id = ARDUINO_EVENT_SC_FOUND_CHANNEL;
  } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
    smartconfig_event_got_ssid_pswd_t *event = (smartconfig_event_got_ssid_pswd_t *)event_data;
    log_v("SC: SSID: %s, Password: %s", (const char *)event->ssid, (const char *)event->password);
#endif
    arduino_event.event_id = ARDUINO_EVENT_SC_GOT_SSID_PSWD;
    memcpy(&arduino_event.event_info.sc_got_ssid_pswd, event_data, sizeof(smartconfig_event_got_ssid_pswd_t));

  } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
    log_v("SC Send Ack Done");
    arduino_event.event_id = ARDUINO_EVENT_SC_SEND_ACK_DONE;

#if CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI
    /*
	 * Provisioning
	 * */
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_INIT) {
    log_v("Provisioning Initialized!");
    arduino_event.event_id = ARDUINO_EVENT_PROV_INIT;
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_DEINIT) {
    log_v("Provisioning Uninitialized!");
    arduino_event.event_id = ARDUINO_EVENT_PROV_DEINIT;
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_START) {
    log_v("Provisioning Start!");
    arduino_event.event_id = ARDUINO_EVENT_PROV_START;
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_END) {
    log_v("Provisioning End!");
    network_prov_mgr_deinit();
    arduino_event.event_id = ARDUINO_EVENT_PROV_END;
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_WIFI_CRED_RECV) {
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
    wifi_sta_config_t *event = (wifi_sta_config_t *)event_data;
    log_v("Provisioned Credentials: SSID: %s, Password: %s", (const char *)event->ssid, (const char *)event->password);
#endif
    arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_RECV;
    memcpy(&arduino_event.event_info.prov_cred_recv, event_data, sizeof(wifi_sta_config_t));
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_WIFI_CRED_FAIL) {
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR
    network_prov_wifi_sta_fail_reason_t *reason = (network_prov_wifi_sta_fail_reason_t *)event_data;
    log_e("Provisioning Failed: Reason : %s", (*reason == NETWORK_PROV_WIFI_STA_AUTH_ERROR) ? "Authentication Failed" : "AP Not Found");
#endif
    arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_FAIL;
    memcpy(&arduino_event.event_info.prov_fail_reason, event_data, sizeof(network_prov_wifi_sta_fail_reason_t));
  } else if (event_base == NETWORK_PROV_EVENT && event_id == NETWORK_PROV_WIFI_CRED_SUCCESS) {
    log_v("Provisioning Success!");
    arduino_event.event_id = ARDUINO_EVENT_PROV_CRED_SUCCESS;
#endif
#endif
  }

  if (arduino_event.event_id < ARDUINO_EVENT_MAX) {
    Network.postEvent(&arduino_event);
  }
}

static bool initWiFiEvents() {
  if (esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, NULL, NULL)) {
    log_e("event_handler_instance_register for WIFI_EVENT Failed!");
    return false;
  }

#if !CONFIG_ESP_WIFI_REMOTE_ENABLED
  if (esp_event_handler_instance_register(SC_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, NULL, NULL)) {
    log_e("event_handler_instance_register for SC_EVENT Failed!");
    return false;
  }

#if CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI
  if (esp_event_handler_instance_register(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb, NULL, NULL)) {
    log_e("event_handler_instance_register for NETWORK_PROV_EVENT Failed!");
    return false;
  }
#endif
#endif

  return true;
}

static bool deinitWiFiEvents() {
  if (esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb)) {
    log_e("esp_event_handler_unregister for WIFI_EVENT Failed!");
    return false;
  }

#if !CONFIG_ESP_WIFI_REMOTE_ENABLED
  if (esp_event_handler_unregister(SC_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb)) {
    log_e("esp_event_handler_unregister for SC_EVENT Failed!");
    return false;
  }

#if CONFIG_NETWORK_PROV_NETWORK_TYPE_WIFI
  if (esp_event_handler_unregister(NETWORK_PROV_EVENT, ESP_EVENT_ANY_ID, &_arduino_event_cb)) {
    log_e("esp_event_handler_unregister for NETWORK_PROV_EVENT Failed!");
    return false;
  }
#endif
#endif

  return true;
}

/*
 * WiFi INIT
 * */

static bool lowLevelInitDone = false;
bool WiFiGenericClass::_wifiUseStaticBuffers = false;

bool WiFiGenericClass::useStaticBuffers() {
  return _wifiUseStaticBuffers;
}

void WiFiGenericClass::useStaticBuffers(bool bufferMode) {
  if (lowLevelInitDone) {
    log_w("WiFi already started. Call WiFi.mode(WIFI_MODE_NULL) before setting Static Buffer Mode.");
  }
  _wifiUseStaticBuffers = bufferMode;
}

// Temporary fix to ensure that CDC+JTAG stay on on ESP32-C3
#if CONFIG_IDF_TARGET_ESP32C3
extern "C" void phy_bbpll_en_usb(bool en);
#endif

#if CONFIG_ESP_WIFI_REMOTE_ENABLED

bool WiFiGenericClass::setPins(int8_t clk, int8_t cmd, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst) {
  return hostedSetPins(clk, cmd, d0, d1, d2, d3, rst);
}

#endif

bool wifiLowLevelInit(bool persistent) {
  if (!lowLevelInitDone) {
    lowLevelInitDone = true;
#if CONFIG_ESP_WIFI_REMOTE_ENABLED
    if (!hostedInitWiFi()) {
      lowLevelInitDone = false;
      return lowLevelInitDone;
    }
#endif
    if (!Network.begin()) {
      lowLevelInitDone = false;
      return lowLevelInitDone;
    }

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

#if CONFIG_ESP_WIFI_REMOTE_ENABLED
    // required for proper work when esp-hosted is used.
    cfg.nvs_enable = false;
    persistent = false;
#endif

    if (!WiFiGenericClass::useStaticBuffers()) {
      cfg.static_tx_buf_num = 0;
      cfg.dynamic_tx_buf_num = 32;
      cfg.tx_buf_type = 1;
      cfg.cache_tx_buf_num = 4;  // can't be zero!
      cfg.static_rx_buf_num = 4;
      cfg.dynamic_rx_buf_num = 32;
    }

    esp_err_t err = esp_wifi_init(&cfg);
    if (err) {
      log_e("esp_wifi_init 0x%x: %s", err, esp_err_to_name(err));
      lowLevelInitDone = false;
      return lowLevelInitDone;
    }
// Temporary fix to ensure that CDC+JTAG stay on on ESP32-C3
#if CONFIG_IDF_TARGET_ESP32C3
    phy_bbpll_en_usb(true);
#endif
    if (!persistent) {
      lowLevelInitDone = esp_wifi_set_storage(WIFI_STORAGE_RAM) == ESP_OK;
    }
    if (lowLevelInitDone) {
      initWiFiEvents();
      if (esp_netifs[ESP_IF_WIFI_AP] == NULL) {
        esp_netifs[ESP_IF_WIFI_AP] = esp_netif_create_default_wifi_ap();
      }
      if (esp_netifs[ESP_IF_WIFI_STA] == NULL) {
        esp_netifs[ESP_IF_WIFI_STA] = esp_netif_create_default_wifi_sta();
      }

      arduino_event_t arduino_event;
      arduino_event.event_id = ARDUINO_EVENT_WIFI_READY;
      Network.postEvent(&arduino_event);
    }
  }
  return lowLevelInitDone;
}

static bool wifiLowLevelDeinit() {
  if (lowLevelInitDone) {
    lowLevelInitDone = false;
    deinitWiFiEvents();
    if (esp_netifs[ESP_IF_WIFI_AP] != NULL) {
      esp_netif_destroy_default_wifi(esp_netifs[ESP_IF_WIFI_AP]);
      esp_netifs[ESP_IF_WIFI_AP] = NULL;
    }
    if (esp_netifs[ESP_IF_WIFI_STA] != NULL) {
      esp_netif_destroy_default_wifi(esp_netifs[ESP_IF_WIFI_STA]);
      esp_netifs[ESP_IF_WIFI_STA] = NULL;
    }
    lowLevelInitDone = !(esp_wifi_deinit() == ESP_OK);
    if (!lowLevelInitDone) {
      arduino_event_t arduino_event;
      arduino_event.event_id = ARDUINO_EVENT_WIFI_OFF;
      Network.postEvent(&arduino_event);
#if CONFIG_ESP_WIFI_REMOTE_ENABLED
      hostedDeinitWiFi();
#endif
    }
  }
  return !lowLevelInitDone;
}

static bool _esp_wifi_started = false;

static bool espWiFiStart() {
  if (_esp_wifi_started) {
    return true;
  }
  _esp_wifi_started = true;
  esp_err_t err = esp_wifi_start();
  if (err != ESP_OK) {
    _esp_wifi_started = false;
    log_e("esp_wifi_start 0x%x: %s", err, esp_err_to_name(err));
    return _esp_wifi_started;
  }
#if SOC_WIFI_SUPPORT_5G
  log_v("Setting Band Mode to AUTO");
  esp_wifi_set_band_mode(WIFI_BAND_MODE_AUTO);
#endif
  return _esp_wifi_started;
}

static bool espWiFiStop() {
  esp_err_t err;
  if (!_esp_wifi_started) {
    return true;
  }
  _esp_wifi_started = false;
  err = esp_wifi_stop();
  if (err) {
    log_e("Could not stop WiFi! 0x%x: %s", err, esp_err_to_name(err));
    _esp_wifi_started = true;
    return false;
  }
  return wifiLowLevelDeinit();
}

// -----------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------- Generic WiFi function -----------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------

bool WiFiGenericClass::_persistent = true;
bool WiFiGenericClass::_long_range = false;
#if CONFIG_IDF_TARGET_ESP32S2
wifi_ps_type_t WiFiGenericClass::_sleepEnabled = WIFI_PS_NONE;
#else
wifi_ps_type_t WiFiGenericClass::_sleepEnabled = WIFI_PS_MIN_MODEM;
#endif

WiFiGenericClass::WiFiGenericClass() {}

const char *WiFiGenericClass::disconnectReasonName(wifi_err_reason_t reason) {
  return WiFi.STA.disconnectReasonName(reason);
}

const char *WiFiGenericClass::eventName(arduino_event_id_t id) {
  return Network.eventName(id);
}

const char *WiFiGenericClass::getHostname() {
  return NetworkManager::getHostname();
}

bool WiFiGenericClass::setHostname(const char *hostname) {
  return NetworkManager::setHostname(hostname);
}

/**
 * callback for WiFi events
 * @param arg
 */
void WiFiGenericClass::_eventCallback(arduino_event_t *event) {
  if (!event) {
    return;  //Null would crash this function
  }

  // log_d("Arduino Event: %d - %s", event->event_id, WiFi.eventName(event->event_id));
  if (event->event_id == ARDUINO_EVENT_WIFI_SCAN_DONE) {
    WiFiScanClass::_scanDone();
#if !CONFIG_ESP_WIFI_REMOTE_ENABLED
  } else if (event->event_id == ARDUINO_EVENT_SC_GOT_SSID_PSWD) {
    WiFi.begin(
      (const char *)event->event_info.sc_got_ssid_pswd.ssid, (const char *)event->event_info.sc_got_ssid_pswd.password, 0,
      ((event->event_info.sc_got_ssid_pswd.bssid_set == true) ? event->event_info.sc_got_ssid_pswd.bssid : NULL)
    );
  } else if (event->event_id == ARDUINO_EVENT_SC_SEND_ACK_DONE) {
    esp_smartconfig_stop();
    WiFiSTAClass::_smartConfigDone = true;
#endif
  }
}

/**
 * Return the current channel associated with the network
 * @return channel (1-13)
 */
int32_t WiFiGenericClass::channel(void) {
  uint8_t primaryChan = 0;
  wifi_second_chan_t secondChan = WIFI_SECOND_CHAN_NONE;
  if (!lowLevelInitDone) {
    return primaryChan;
  }
  esp_wifi_get_channel(&primaryChan, &secondChan);
  return primaryChan;
}

/**
 * Set the WiFi channel configuration
 * @param primary primary channel. Depending on the region, not all channels may be available.
 * @param secondary secondary channel (WIFI_SECOND_CHAN_NONE, WIFI_SECOND_CHAN_ABOVE, WIFI_SECOND_CHAN_BELOW)
 * @return 0 on success, otherwise error
 */
int WiFiGenericClass::setChannel(uint8_t primary, wifi_second_chan_t secondary) {
  wifi_country_t country;
  esp_err_t ret;

  ret = esp_wifi_get_country(&country);
  if (ret != ESP_OK) {
    log_e("Failed to get country info 0x%x: %s", ret, esp_err_to_name(ret));
    return ret;
  }

  uint8_t min_chan = country.schan;
  uint8_t max_chan = min_chan + country.nchan - 1;

  if (primary < min_chan || primary > max_chan) {
    log_e("Invalid primary channel: %d. Valid range is %d-%d for country %s", primary, min_chan, max_chan, country.cc);
    return ESP_ERR_INVALID_ARG;
  }

  ret = esp_wifi_set_channel(primary, secondary);
  if (ret != ESP_OK) {
    log_e("Failed to set channel 0x%x: %s", ret, esp_err_to_name(ret));
    return ret;
  }

  return ESP_OK;
}

/**
 * store WiFi config in SDK flash area
 * @param persistent
 */
void WiFiGenericClass::persistent(bool persistent) {
  _persistent = persistent;
}

/**
 * enable WiFi long range mode
 * @param enable
 */
void WiFiGenericClass::enableLongRange(bool enable) {
  _long_range = enable;
}

#if CONFIG_SOC_WIFI_HE_SUPPORT
#define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AX)
#else
#define WIFI_PROTOCOL_DEFAULT (WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N)
#endif

#if SOC_WIFI_SUPPORT_5G
#if CONFIG_SOC_WIFI_HE_SUPPORT
#define WIFI_PROTOCOL_DEFAULT_5G (WIFI_PROTOCOL_11A | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AC | WIFI_PROTOCOL_11AX)
#else
#define WIFI_PROTOCOL_DEFAULT_5G (WIFI_PROTOCOL_11A | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_11AC)
#endif
#endif

static bool _wifi_is_lr_enabled(wifi_interface_t ifx) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) && SOC_WIFI_SUPPORT_5G
  wifi_band_mode_t band_mode = WIFI_BAND_MODE_AUTO;
  esp_err_t err = esp_wifi_get_band_mode(&band_mode);
  if (err != ESP_OK) {
    log_e("Failed to get Current Band Mode: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
  if (band_mode == WIFI_BAND_MODE_AUTO) {
    wifi_protocols_t protocols = {.ghz_2g = 0, .ghz_5g = 0};
    err = esp_wifi_get_protocols(ifx, &protocols);
    if (err != ESP_OK) {
      log_e("Failed to get Current Protocols: 0x%x: %s", err, esp_err_to_name(err));
      return false;
    }
    return protocols.ghz_2g == WIFI_PROTOCOL_LR;
  } else if (band_mode == WIFI_BAND_MODE_5G_ONLY) {
    return false;
  }
#endif
  uint16_t protocol_bitmap = 0;
  esp_err_t err2 = esp_wifi_get_protocol(ifx, (uint8_t *)&protocol_bitmap);
  if (err2 != ESP_OK) {
    log_e("Failed to get Current Protocols: 0x%x: %s", err2, esp_err_to_name(err2));
    return false;
  }
  return protocol_bitmap == WIFI_PROTOCOL_LR;
}

static bool _wifi_enable_lr(wifi_interface_t ifx) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) && SOC_WIFI_SUPPORT_5G
  wifi_band_mode_t band_mode = WIFI_BAND_MODE_AUTO;
  esp_err_t err = esp_wifi_get_band_mode(&band_mode);
  if (err != ESP_OK) {
    log_e("Failed to get Current Band Mode: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
  if (band_mode == WIFI_BAND_MODE_AUTO) {
    wifi_protocols_t protocols = {.ghz_2g = WIFI_PROTOCOL_LR, .ghz_5g = WIFI_PROTOCOL_DEFAULT_5G};
    err = esp_wifi_set_protocols(ifx, &protocols);
    if (err != ESP_OK) {
      log_e("Failed to set LR Protocol: 0x%x: %s", err, esp_err_to_name(err));
      return false;
    }
    return true;
  } else if (band_mode == WIFI_BAND_MODE_5G_ONLY) {
    log_e("LR Protocol can only be enabled on 2.4GHz WiFi");
    return false;
  }
#endif
  esp_err_t err2 = esp_wifi_set_protocol(ifx, WIFI_PROTOCOL_LR);
  if (err2 != ESP_OK) {
    log_e("Failed to set LR Protocol: 0x%x: %s", err2, esp_err_to_name(err2));
    return false;
  }
  return true;
}

static bool _wifi_disable_lr(wifi_interface_t ifx) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) && SOC_WIFI_SUPPORT_5G
  wifi_band_mode_t band_mode = WIFI_BAND_MODE_AUTO;
  esp_err_t err = esp_wifi_get_band_mode(&band_mode);
  if (err != ESP_OK) {
    log_e("Failed to get Current Band Mode: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
  if (band_mode == WIFI_BAND_MODE_AUTO) {
    wifi_protocols_t protocols = {.ghz_2g = WIFI_PROTOCOL_DEFAULT, .ghz_5g = WIFI_PROTOCOL_DEFAULT_5G};
    err = esp_wifi_set_protocols(ifx, &protocols);
    if (err != ESP_OK) {
      log_e("Failed to set Default Protocol: 0x%x: %s", err, esp_err_to_name(err));
      return false;
    }
    return true;
  } else if (band_mode == WIFI_BAND_MODE_5G_ONLY) {
    log_e("LR Protocol can only be disabled on 2.4GHz WiFi");
    return false;
  }
#endif
  esp_err_t err2 = esp_wifi_set_protocol(ifx, WIFI_PROTOCOL_DEFAULT);
  if (err2 != ESP_OK) {
    log_e("Failed to set Default Protocol: 0x%x: %s", err2, esp_err_to_name(err2));
    return false;
  }
  return true;
}

/**
 * set new mode
 * @param m WiFiMode_t
 */
bool WiFiGenericClass::mode(wifi_mode_t m) {
  wifi_mode_t cm = getMode();
  if (cm == m) {
    return true;
  }
  if (!cm && m) {
    // Turn ON WiFi
    if (!wifiLowLevelInit(_persistent)) {
      return false;
    }
    Network.onSysEvent(_eventCallback);
  }

  if (((m & WIFI_MODE_STA) != 0) && ((cm & WIFI_MODE_STA) == 0)) {
    // we are enabling STA interface
    WiFi.STA.onEnable();
  }
  if (((m & WIFI_MODE_AP) != 0) && ((cm & WIFI_MODE_AP) == 0)) {
    // we are enabling AP interface
    WiFi.AP.onEnable();
  }

  if (cm && !m) {
    // Turn OFF WiFi
    if (!espWiFiStop()) {
      return false;
    }
    if ((cm & WIFI_MODE_STA) != 0) {
      // we are disabling STA interface
      WiFi.STA.onDisable();
    }
    if ((cm & WIFI_MODE_AP) != 0) {
      // we are disabling AP interface
      WiFi.AP.onDisable();
    }
    Network.removeEvent(_eventCallback);
    return true;
  }

  esp_err_t err;
  if (((m & WIFI_MODE_STA) != 0) && ((cm & WIFI_MODE_STA) == 0)) {
    err = esp_netif_set_hostname(esp_netifs[ESP_IF_WIFI_STA], NetworkManager::getHostname());
    if (err) {
      log_e("Could not set hostname! 0x%x: %s", err, esp_err_to_name(err));
      return false;
    }
  }
  err = esp_wifi_set_mode(m);
  if (err) {
    log_e("Could not set mode! 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }

  if (((m & WIFI_MODE_STA) == 0) && ((cm & WIFI_MODE_STA) != 0)) {
    // we are disabling STA interface (but AP is ON)
    WiFi.STA.onDisable();
  }
  if (((m & WIFI_MODE_AP) == 0) && ((cm & WIFI_MODE_AP) != 0)) {
    // we are disabling AP interface (but STA is ON)
    WiFi.AP.onDisable();
  }

  if (_long_range) {
    if (m & WIFI_MODE_STA) {
      if (!_wifi_enable_lr(WIFI_IF_STA)) {
        log_e("Could not enable long range on STA!");
        return false;
      }
    }
    if (m & WIFI_MODE_AP) {
      if (!_wifi_enable_lr(WIFI_IF_AP)) {
        log_e("Could not enable long range on AP!");
        return false;
      }
    }
  } else {
    if (m & WIFI_MODE_STA) {
      if (_wifi_is_lr_enabled(WIFI_IF_STA)) {
        log_v("Disabling long range on STA");
        if (!_wifi_disable_lr(WIFI_IF_STA)) {
          log_e("Could not disable long range on STA!");
          return false;
        }
      }
    }
    if (m & WIFI_MODE_AP) {
      if (_wifi_is_lr_enabled(WIFI_IF_AP)) {
        log_v("Disabling long range on AP");
        if (!_wifi_disable_lr(WIFI_IF_AP)) {
          log_e("Could not disable long range on AP!");
          return false;
        }
      }
    }
  }
  if (!espWiFiStart()) {
    return false;
  }

#ifdef BOARD_HAS_DUAL_ANTENNA
  if (!setDualAntennaConfig(ANT1, ANT2, WIFI_RX_ANT_AUTO, WIFI_TX_ANT_AUTO)) {
    log_e("Dual Antenna Config failed!");
    return false;
  }
#endif

  return true;
}

/**
 * get WiFi mode
 * @return WiFiMode
 */
wifi_mode_t WiFiGenericClass::getMode() {
  if (!lowLevelInitDone || !_esp_wifi_started) {
    return WIFI_MODE_NULL;
  }
  wifi_mode_t mode;
  if (esp_wifi_get_mode(&mode) != ESP_OK) {
    log_w("WiFi not started");
    return WIFI_MODE_NULL;
  }
  return mode;
}

/**
 * control STA mode
 * @param enable bool
 * @return ok
 */
bool WiFiGenericClass::enableSTA(bool enable) {

  wifi_mode_t currentMode = getMode();
  bool isEnabled = ((currentMode & WIFI_MODE_STA) != 0);

  if (isEnabled != enable) {
    if (enable) {
      return mode((wifi_mode_t)(currentMode | WIFI_MODE_STA));
    }
    return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_STA)));
  }
  return true;
}

/**
 * control AP mode
 * @param enable bool
 * @return ok
 */
bool WiFiGenericClass::enableAP(bool enable) {

  wifi_mode_t currentMode = getMode();
  bool isEnabled = ((currentMode & WIFI_MODE_AP) != 0);

  if (isEnabled != enable) {
    if (enable) {
      return mode((wifi_mode_t)(currentMode | WIFI_MODE_AP));
    }
    return mode((wifi_mode_t)(currentMode & (~WIFI_MODE_AP)));
  }
  return true;
}

/**
 * Enable or disable WiFi modem power save mode
 * @param enable bool
 * @return ok
 */
bool WiFiGenericClass::setSleep(bool enabled) {
  return setSleep(enabled ? WIFI_PS_MIN_MODEM : WIFI_PS_NONE);
}

/**
 * Set WiFi modem power save mode
 * @param mode wifi_ps_type_t
 * @return ok
 */
bool WiFiGenericClass::setSleep(wifi_ps_type_t sleepType) {
  if (sleepType > WIFI_PS_MAX_MODEM) {
    return false;
  }

  if (!WiFi.STA.started()) {
    _sleepEnabled = sleepType;
    return true;
  }

  esp_err_t err = esp_wifi_set_ps(sleepType);
  if (err != ESP_OK) {
    log_e("esp_wifi_set_ps failed!: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }

  _sleepEnabled = sleepType;
  return true;
}

/**
 * Get WiFi modem power save mode
 * @return wifi_ps_type_t
 */
wifi_ps_type_t WiFiGenericClass::getSleep() {
  return _sleepEnabled;
}

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
/**
 * control wifi band mode
 * @param band_mode enum possible band modes
 * @return ok
 */
bool WiFiGenericClass::setBandMode(wifi_band_mode_t band_mode) {
#if SOC_WIFI_SUPPORT_5G
  if (!WiFi.STA.started() && !WiFi.AP.started()) {
    log_e("You need to start WiFi first");
    return false;
  }
  wifi_band_mode_t bm = WIFI_BAND_MODE_AUTO;
  esp_err_t err = esp_wifi_get_band_mode(&bm);
  if (err != ESP_OK) {
    log_e("Failed to get Current Band Mode: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  } else if (bm == band_mode) {
    log_d("No change in Band Mode");
    return true;
  } else {
    log_d("Switching Band Mode from %d to %d", bm, band_mode);
  }
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_ERROR
  if (WiFi.STA.connected() || WiFi.AP.connected()) {
    log_e("Your network will get disconnected!");
  }
#endif
  err = esp_wifi_set_band_mode(band_mode);
  if (err != ESP_OK) {
    log_e("Failed to set Band Mode: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
  delay(100);
  return true;
#else
  if (band_mode == WIFI_BAND_MODE_5G_ONLY) {
    log_e("This chip supports only 2.4GHz WiFi");
  }
  return band_mode != WIFI_BAND_MODE_5G_ONLY;
#endif
}

/**
 * get the current enabled wifi band mode
 * @return enum band mode
 */
wifi_band_mode_t WiFiGenericClass::getBandMode() {
#if SOC_WIFI_SUPPORT_5G
  wifi_band_mode_t band_mode = WIFI_BAND_MODE_AUTO;
  if (!WiFi.STA.started() && !WiFi.AP.started()) {
    log_e("You need to start WiFi first");
    return band_mode;
  }
  esp_err_t err = esp_wifi_get_band_mode(&band_mode);
  if (err != ESP_OK) {
    log_e("Failed to get Band Mode: 0x%x: %s", err, esp_err_to_name(err));
  }
  return band_mode;
#else
  return WIFI_BAND_MODE_2G_ONLY;
#endif
}
#endif

/**
 * get the current active wifi band
 * @return enum band
 */
wifi_band_t WiFiGenericClass::getBand() {
#if SOC_WIFI_SUPPORT_5G
  wifi_band_t band = WIFI_BAND_2G;
  if (!WiFi.STA.started() && !WiFi.AP.started()) {
    log_e("You need to start WiFi first");
    return band;
  }
  esp_err_t err = esp_wifi_get_band(&band);
  if (err != ESP_OK) {
    log_e("Failed to get Band: 0x%x: %s", err, esp_err_to_name(err));
  }
  return band;
#else
  return WIFI_BAND_2G;
#endif
}

/**
 * control wifi tx power
 * @param power enum maximum wifi tx power
 * @return ok
 */
bool WiFiGenericClass::setTxPower(wifi_power_t power) {
  if (!WiFi.STA.started() && !WiFi.AP.started()) {
    log_w("Neither AP or STA has been started");
    return false;
  }
  esp_err_t err = esp_wifi_set_max_tx_power(power);
  if (err != ESP_OK) {
    log_e("Failed to set TX Power: 0x%x: %s", err, esp_err_to_name(err));
  }
  return err == ESP_OK;
}

wifi_power_t WiFiGenericClass::getTxPower() {
  int8_t power;
  if (!WiFi.STA.started() && !WiFi.AP.started()) {
    log_w("Neither AP or STA has been started");
    return WIFI_POWER_19_5dBm;
  }
  esp_err_t err = esp_wifi_get_max_tx_power(&power);
  if (err != ESP_OK) {
    log_e("Failed to get TX Power: 0x%x: %s", err, esp_err_to_name(err));
    return WIFI_POWER_19_5dBm;
  }
  return (wifi_power_t)power;
}

/**
 * Initiate FTM Session.
 * @param frm_count Number of FTM frames requested in terms of 4 or 8 bursts (allowed values - 0(No pref), 16, 24, 32, 64)
 * @param burst_period Requested time period between consecutive FTM bursts in 100's of milliseconds (allowed values - 0(No pref), 2 - 255)
 * @param channel Primary channel of the FTM Responder
 * @param mac MAC address of the FTM Responder
 * @return true on success
 */
bool WiFiGenericClass::initiateFTM(uint8_t frm_count, uint16_t burst_period, uint8_t channel, const uint8_t *mac) {
  wifi_ftm_initiator_cfg_t ftmi_cfg = {
    .resp_mac = {0, 0, 0, 0, 0, 0}, .channel = channel, .frm_count = frm_count, .burst_period = burst_period, .use_get_report_api = true
  };
  if (mac != NULL) {
    memcpy(ftmi_cfg.resp_mac, mac, 6);
  }
  // Request FTM session with the Responder
  esp_err_t err = esp_wifi_ftm_initiate_session(&ftmi_cfg);
  if (ESP_OK != err) {
    log_e("Failed to initiate FTM session: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
  return true;
}

/**
 * Configure Dual antenna.
 * @param gpio_ant1 Configure the GPIO number for the antenna 1 connected to the RF switch (default GPIO2 on ESP32-WROOM-DA)
 * @param gpio_ant2 Configure the GPIO number for the antenna 2 connected to the RF switch (default GPIO25 on ESP32-WROOM-DA)
 * @param rx_mode Set the RX antenna mode. See wifi_rx_ant_t for the options.
 * @param tx_mode Set the TX antenna mode. See wifi_tx_ant_t for the options.
 * @return true on success
 */
bool WiFiGenericClass::setDualAntennaConfig(uint8_t gpio_ant1, uint8_t gpio_ant2, wifi_rx_ant_t rx_mode, wifi_tx_ant_t tx_mode) {
#if !CONFIG_ESP_WIFI_REMOTE_ENABLED

  esp_phy_ant_gpio_config_t wifi_ant_io;

  esp_err_t err = esp_phy_get_ant_gpio(&wifi_ant_io);
  if (ESP_OK != err) {
    log_e("Failed to get antenna configuration: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }

  wifi_ant_io.gpio_cfg[0].gpio_num = gpio_ant1;
  wifi_ant_io.gpio_cfg[0].gpio_select = 1;
  wifi_ant_io.gpio_cfg[1].gpio_num = gpio_ant2;
  wifi_ant_io.gpio_cfg[1].gpio_select = 1;

  err = esp_phy_set_ant_gpio(&wifi_ant_io);
  if (ESP_OK != err) {
    log_e("Failed to set antenna GPIO configuration: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }

  // Set antenna default configuration
  esp_phy_ant_config_t ant_config = {
    .rx_ant_mode = ESP_PHY_ANT_MODE_AUTO,
    .rx_ant_default = ESP_PHY_ANT_MAX,  // Ignored in AUTO mode
    .tx_ant_mode = ESP_PHY_ANT_MODE_AUTO,
    .enabled_ant0 = 1,
    .enabled_ant1 = 2,
  };

  switch (rx_mode) {
    case WIFI_RX_ANT0: ant_config.rx_ant_mode = ESP_PHY_ANT_MODE_ANT0; break;
    case WIFI_RX_ANT1: ant_config.rx_ant_mode = ESP_PHY_ANT_MODE_ANT1; break;
    case WIFI_RX_ANT_AUTO:
      log_i("TX Antenna will be automatically selected");
      ant_config.rx_ant_default = ESP_PHY_ANT_ANT0;
      ant_config.rx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      // Force TX for AUTO if RX is AUTO
      ant_config.tx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      goto set_ant;
      break;
    default:
      log_e("Invalid default antenna! Falling back to AUTO");
      ant_config.rx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      break;
  }

  switch (tx_mode) {
    case WIFI_TX_ANT0: ant_config.tx_ant_mode = ESP_PHY_ANT_MODE_ANT0; break;
    case WIFI_TX_ANT1: ant_config.tx_ant_mode = ESP_PHY_ANT_MODE_ANT1; break;
    case WIFI_TX_ANT_AUTO:
      log_i("RX Antenna will be automatically selected");
      ant_config.rx_ant_default = ESP_PHY_ANT_ANT0;
      ant_config.tx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      // Force RX for AUTO if RX is AUTO
      ant_config.rx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      break;
    default:
      log_e("Invalid default antenna! Falling back to AUTO");
      ant_config.rx_ant_default = ESP_PHY_ANT_ANT0;
      ant_config.tx_ant_mode = ESP_PHY_ANT_MODE_AUTO;
      break;
  }

set_ant:
  err = esp_phy_set_ant(&ant_config);
  if (ESP_OK != err) {
    log_e("Failed to set antenna configuration: 0x%x: %s", err, esp_err_to_name(err));
    return false;
  }
#endif
  return true;
}

// -----------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------ Generic Network function ---------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------

/*
 * Deprecated Methods
*/
int WiFiGenericClass::hostByName(const char *aHostname, IPAddress &aResult) {
  return Network.hostByName(aHostname, aResult);
}

IPAddress WiFiGenericClass::calculateNetworkID(IPAddress ip, IPAddress subnet) {
  IPAddress networkID;

  for (size_t i = 0; i < 4; i++) {
    networkID[i] = subnet[i] & ip[i];
  }

  return networkID;
}

IPAddress WiFiGenericClass::calculateBroadcast(IPAddress ip, IPAddress subnet) {
  IPAddress broadcastIp;

  for (int i = 0; i < 4; i++) {
    broadcastIp[i] = ~subnet[i] | ip[i];
  }

  return broadcastIp;
}

uint8_t WiFiGenericClass::calculateSubnetCIDR(IPAddress subnetMask) {
  uint8_t CIDR = 0;

  for (uint8_t i = 0; i < 4; i++) {
    if (subnetMask[i] == 0x80) {  // 128
      CIDR += 1;
    } else if (subnetMask[i] == 0xC0) {  // 192
      CIDR += 2;
    } else if (subnetMask[i] == 0xE0) {  // 224
      CIDR += 3;
    } else if (subnetMask[i] == 0xF0) {  // 242
      CIDR += 4;
    } else if (subnetMask[i] == 0xF8) {  // 248
      CIDR += 5;
    } else if (subnetMask[i] == 0xFC) {  // 252
      CIDR += 6;
    } else if (subnetMask[i] == 0xFE) {  // 254
      CIDR += 7;
    } else if (subnetMask[i] == 0xFF) {  // 255
      CIDR += 8;
    }
  }

  return CIDR;
}

wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventCb cbEvent, arduino_event_id_t event) {
  return Network.onEvent(cbEvent, event);
}

wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventFuncCb cbEvent, arduino_event_id_t event) {
  return Network.onEvent(cbEvent, event);
}

wifi_event_id_t WiFiGenericClass::onEvent(WiFiEventSysCb cbEvent, arduino_event_id_t event) {
  return Network.onEvent(cbEvent, event);
}

void WiFiGenericClass::removeEvent(WiFiEventCb cbEvent, arduino_event_id_t event) {
  Network.removeEvent(cbEvent, event);
}

void WiFiGenericClass::removeEvent(WiFiEventSysCb cbEvent, arduino_event_id_t event) {
  Network.removeEvent(cbEvent, event);
}

void WiFiGenericClass::removeEvent(wifi_event_id_t id) {
  Network.removeEvent(id);
}

int WiFiGenericClass::setStatusBits(int bits) {
  return Network.setStatusBits(bits);
}

int WiFiGenericClass::clearStatusBits(int bits) {
  return Network.clearStatusBits(bits);
}

int WiFiGenericClass::getStatusBits() {
  return Network.getStatusBits();
}

int WiFiGenericClass::waitStatusBits(int bits, uint32_t timeout_ms) {
  return Network.waitStatusBits(bits, timeout_ms);
}

#endif /* SOC_WIFI_SUPPORTED */
