/*
 * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */

/* Multiple Network Interface Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <lwip/dns.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "nvs_flash.h"
#include "iface_info.h"

iface_info_t *example_eth_init(int prio);
iface_info_t *example_wifi_init(int prio);
iface_info_t *example_ppp_init(int prio);
esp_err_t check_connectivity(const char *host);

#define HOST        "www.espressif.com"
#define ETH_PRIO    200
#define WIFI_PRIO   100
#define PPP_PRIO    50

static const char *TAG = "app_main";

static ssize_t get_default(iface_info_t *list[], size_t num)
{
    esp_netif_t *default_netif = esp_netif_get_default_netif();
    if (default_netif == NULL) {
        ESP_LOGE(TAG, "default netif is NULL!");
        return -1;
    }
    ESP_LOGI(TAG, "Default netif: %s", esp_netif_get_desc(default_netif));

    for (int i = 0; i < num; ++i) {
        if (list[i] && list[i]->netif == default_netif) {
            ESP_LOGI(TAG, "Default interface: %s", list[i]->name);
            return i;
        }
    }
    // not found
    return -2;
}

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_netif_init());
    // Create default event loop that running in background
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // all interfaces
    iface_info_t *ifaces[] = {
        example_eth_init(ETH_PRIO),
        example_wifi_init(WIFI_PRIO),
        example_ppp_init(PPP_PRIO),
    };
    size_t num_of_ifaces = sizeof(ifaces) / sizeof(ifaces[0]);

    while (true) {
#ifdef CONFIG_EXAMPLE_DEMONSTRATE_DNS_CLEAR_CACHE
        // For demonstration purposes we clear DNS table every iteration to exercise
        // a condition of DNS servers being misconfigured
        dns_clear_cache();
#endif
        vTaskDelay(pdMS_TO_TICKS(2000));
        ssize_t i = get_default(ifaces, num_of_ifaces);
        if (i == -1) {  // default netif is NULL, probably all interfaces are down -> retry
            continue;
        } else if (i < 0) {
            break;      // some other error, exit
        }

        esp_err_t connect_status = check_connectivity(HOST);
        if (connect_status == ESP_OK) {
            // connectivity ok
            continue;
        }
        if (connect_status == ESP_ERR_NOT_FOUND) {
#ifndef CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF
            // set the default DNS info to global DNS server list
            // manually if DNS_PER_DEFAULT_NETIF if OFF or not-supported
            for (int j = 0; j < 2; ++j) {
                esp_netif_dns_info_t dns_info;
                esp_netif_get_dns_info(ifaces[i]->netif, j, &dns_info);
                if (memcmp(&dns_info.ip, &ifaces[i]->dns[j].ip, sizeof(esp_ip_addr_t)) == 0) {
                    connect_status = ESP_FAIL;
                } else {
                    esp_netif_set_dns_info(ifaces[i]->netif, j, &ifaces[i]->dns[j]);
                    ESP_LOGI(TAG, "Reconfigured DNS%i=" IPSTR, j, IP2STR(&ifaces[i]->dns[j].ip.u_addr.ip4));
                }
            }
#else
            // simulate that the (default) netif is brought UP
            // this is only needed, since we explicitly clear DNS servers every iteration using dns_clear_cache()
            // (for demonstration purpose only, won't be needed in your project, unless you delete DNS info for some reasons)
            esp_netif_action_connected(ifaces[i]->netif, NULL, 0, NULL);
#endif
        }
        if (connect_status == ESP_FAIL) {
            ESP_LOGE(TAG, "No connection via the default netif!");
            // try to switch interfaces manually
            // WARNING: Once we set_default_netif() manually, we disable the automatic prio-routing
            int next = (i + 1) % num_of_ifaces;
            while (ifaces[i] != ifaces[next]) {
                if (ifaces[next]->connected) {
                    ESP_LOGE(TAG, "Trying another interface: %s", ifaces[next]->name);
                    esp_netif_set_default_netif(ifaces[next]->netif);
                    break;
                }
                ++next;
                next = next % num_of_ifaces;
            }
        }
    }

    ESP_LOGI(TAG, "Stop and cleanup all interfaces");
    for (int i = 0; i < num_of_ifaces; ++i) {
        if (ifaces[i]) {
            ifaces[i]->destroy(ifaces[i]);
        }
    }
}
