#include "wifi.h"

#include <netinet/in.h>

#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_netif_types.h"
#include "esp_err.h"

#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_WRN
#define SYS_LOG_DOMAIN "WIFI"
#include "sys_log.h"

static struct
{
    uint8_t init;
    uint8_t curr_mode; // 0: 已关闭, 1: ap, 2: sta, 3: ap+sta
    wifi_netif_mode_t cfg_mode;
    wifi_init_t init_param;
    esp_netif_t *netif;
    uint8_t mac[6];
    union
    {
        wifi_ap_connect_status_t ap_status;
        wifi_sta_connect_status_t sta_status;
    };
} s_cm;

static void _wifi_start_ap(void);
static void _wifi_start_sta(void);
static void _wiri_stop_ap(void);
static void _wiri_stop_sta(void);
static void _wifi_event_handler_ap(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);  // 由 _wifi_start_ap() 启动时设置的事件回调函数，处理 AP 模式下客户端的连接状态
static void _wifi_event_handler_sta(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); // 由 _wifi_start_sta() 启动时设置的事件回调函数，处理 STA 模式下的连接状态

/**
 * @brief 初始化设置。可重复设置
 *
 * @param init_struct
 */
void wifi_netif_init(wifi_init_t *init_struct)
{
    if (s_cm.init == 0)
    {
        s_cm.init = 1;
        ESP_ERROR_CHECK(esp_netif_init());
    }
    s_cm.init_param = *init_struct;
}

/**
 * @brief 设置 WIFI 模式。在下次执行 wifi_start() 时根据此模式启动 WIFI
 *
 * @param mode @ref wifi_netif_mode_t
 */
void wifi_set_mode(wifi_netif_mode_t mode)
{
    s_cm.cfg_mode = mode;
}

void wifi_start(void)
{
    switch (s_cm.cfg_mode)
    {
    case WIFI_NETIF_MODE_AP:
        _wifi_start_ap();
        break;

    case WIFI_NETIF_MODE_STA:
    default:
        _wifi_start_sta();
        break;
    }
}

/**
 * @brief 关闭 WIFI
 *
 */
void wifi_stop(void)
{
    if (s_cm.curr_mode == 0)
    {
        SYS_LOG_WRN("wifi has been stoped");
        return;
    }

    if (s_cm.curr_mode == 1)
    {
        _wiri_stop_ap();
    }
    else if (s_cm.curr_mode == 2)
    {
        _wiri_stop_sta();
    }

    s_cm.curr_mode = 0;
}

/**
 * @brief （仅在 AP 模式下）获取已连接的工作站信息
 *
 * @param netif_sta_list
 * @return int
 */
int wifi_get_sta_list(wifi_sta_mac_ip_list_t *netif_sta_list)
{
    wifi_sta_list_t sta_list;
    memset(&sta_list, 0, sizeof(sta_list));
    memset(netif_sta_list, 0, sizeof(wifi_sta_mac_ip_list_t));
    esp_err_t err = esp_wifi_ap_get_sta_list(&sta_list);
    if (err != ESP_OK)
    {
        return err;
    }

    return esp_wifi_ap_get_sta_list_with_ip(&sta_list, netif_sta_list);
}

/**
 * @brief （仅在 AP 模式下）获取已连接的 AP 信息
 *
 * @param netif_ap[out]
 * @return int
 */
int wifi_get_ap_info(esp_netif_ip_info_t *netif_ap)
{
    if (s_cm.curr_mode == 2)
    {
        return esp_netif_get_ip_info(s_cm.netif, netif_ap);
    }
    return -1;
}

/**
 * @brief 初始化 WIFI 协议栈，启动 WIFI 的 AP 模式，启动 DHCP 服务
 *
 */
static void _wifi_start_ap(void)
{
    if (s_cm.curr_mode == 1)
    {
        SYS_LOG_WRN("wifi has been started");
        return;
    }

    if (s_cm.curr_mode)
    {
        wifi_stop();
    }

    s_cm.curr_mode = 1;

    // Initialize networking stack
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // start the DHCP server
    if (s_cm.netif == NULL)
    {
        s_cm.netif = esp_netif_create_default_wifi_ap();
        assert(s_cm.netif);
        // stop DHCP server
        ESP_ERROR_CHECK(esp_netif_dhcps_stop(s_cm.netif));
        // assign a static IP to the network interface
        esp_netif_ip_info_t info;
        memset(&info, 0, sizeof(info));
        IP4_ADDR(&info.ip, s_cm.init_param.ap.ip_v4[0], s_cm.init_param.ap.ip_v4[1], s_cm.init_param.ap.ip_v4[2], s_cm.init_param.ap.ip_v4[3]);
        IP4_ADDR(&info.gw, s_cm.init_param.ap.gw_v4[0], s_cm.init_param.ap.gw_v4[1], s_cm.init_param.ap.gw_v4[2], s_cm.init_param.ap.gw_v4[3]); // ESP acts as router, so gw addr will be its own addr
        IP4_ADDR(&info.netmask, s_cm.init_param.ap.mask_v4[0], s_cm.init_param.ap.mask_v4[1], s_cm.init_param.ap.mask_v4[2], s_cm.init_param.ap.mask_v4[3]);
        ESP_ERROR_CHECK(esp_netif_set_ip_info(s_cm.netif, &info));
        ESP_ERROR_CHECK(esp_netif_dhcps_start(s_cm.netif));
    }

    /* Initialise ESP32 in SoftAP curr_mode */
    const char *ssid = s_cm.init_param.ap.ssid;
    const char *password = s_cm.init_param.ap.password;
    wifi_config_t wifi_config;
    memset(&wifi_config, 0, sizeof(wifi_config_t));
    memcpy(wifi_config.ap.ssid, ssid, strlen(ssid));
    wifi_config.ap.ssid_len = strlen(ssid);
    wifi_config.ap.max_connection = s_cm.init_param.ap.max_connection;
    if (strlen(password) == 0)
    {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }
    else
    {
        strcpy((char *)wifi_config.ap.password, password);
        wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
    }

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, _wifi_event_handler_ap, NULL));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb;
    if (connect_cb)
    {
        s_cm.ap_status = WIFI_AP_CONNECT_GOT_STA;
        connect_cb(s_cm.ap_status, 0);
    }

    SYS_LOG_INF("done");
}

/**
 * @brief 初始化 WIFI 协议栈，启动 WIFI 的 STA 模式
 *
 */
static void _wifi_start_sta(void)
{
    if (s_cm.curr_mode == 2)
    {
        SYS_LOG_WRN("wifi has been started");
        return;
    }

    if (s_cm.curr_mode)
    {
        wifi_stop();
    }

    s_cm.curr_mode = 2;

    /* 参考自 examples/wifi/fast_scan/main/fast_scan.c */
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // Initialize default station as network interface instance (esp-netif)
    if (s_cm.netif == NULL)
    {
        s_cm.netif = esp_netif_create_default_wifi_sta();
        assert(s_cm.netif);
        ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &_wifi_event_handler_sta, s_cm.netif, NULL));
        ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &_wifi_event_handler_sta, s_cm.netif, NULL));
    }

    // Initialize and start WiFi
    const char *ssid = s_cm.init_param.sta.ssid;
    const char *password = s_cm.init_param.sta.password;
    wifi_config_t wifi_config;
    memset(&wifi_config, 0, sizeof(wifi_config_t));
    strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
    strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));
    wifi_config.sta.scan_method = WIFI_FAST_SCAN;
    wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL;
    wifi_config.sta.threshold.rssi = -127;
    wifi_config.sta.threshold.authmode = WIFI_AUTH_OPEN;

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb;
    if (connect_cb)
    {
        uint8_t ip_v4[4];
        memset(ip_v4, 0, sizeof(ip_v4));
        s_cm.sta_status = WIFI_STA_CONNECT_CONNECTING;
        connect_cb(s_cm.sta_status, ip_v4);
    }

    SYS_LOG_INF("done");
}

static void _wiri_stop_ap(void)
{
    /* Deinitialise ESP32 netif */
    ESP_ERROR_CHECK(esp_wifi_stop());
    if (s_cm.netif)
    {
        esp_netif_dhcp_status_t status;
        ESP_ERROR_CHECK(esp_netif_dhcps_get_status(s_cm.netif, &status));
        if (ESP_NETIF_DHCP_STARTED == status)
        {
            ESP_ERROR_CHECK(esp_netif_dhcps_stop(s_cm.netif));
        }
        esp_netif_destroy_default_wifi(s_cm.netif);
        s_cm.netif = NULL;
    }
    ESP_ERROR_CHECK(esp_wifi_deinit());

    // Deinitialize networking stack
    ESP_ERROR_CHECK(esp_event_loop_delete_default());

    wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb;
    if (connect_cb)
    {
        s_cm.ap_status = WIFI_AP_CONNECT_STOPED;
        connect_cb(s_cm.ap_status, 0);
    }

    SYS_LOG_INF("done");

    s_cm.curr_mode = 0;
}

static void _wiri_stop_sta(void)
{
    /* Deinitialise ESP32 netif */
    ESP_ERROR_CHECK(esp_wifi_stop());
    if (s_cm.netif)
    {
        esp_netif_destroy_default_wifi(s_cm.netif);
        s_cm.netif = NULL;
    }
    ESP_ERROR_CHECK(esp_wifi_deinit());

    // Deinitialize networking stack
    ESP_ERROR_CHECK(esp_event_loop_delete_default());

    wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb;
    if (connect_cb)
    {
        uint8_t ip_v4[4];
        memset(ip_v4, 0, sizeof(ip_v4));
        s_cm.sta_status = WIFI_STA_CONNECT_STOPED;
        connect_cb(s_cm.sta_status, ip_v4);
    }

    SYS_LOG_INF("done");

    s_cm.curr_mode = 0;
}

static void _wifi_event_handler_ap(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    switch (event_id)
    {
    case WIFI_EVENT_AP_STACONNECTED:
    {
        wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
        SYS_LOG_INF("station " MACSTR " join, AID=%d",
                    MAC2STR(event->mac), event->aid);
        memcpy(s_cm.mac, event->mac, 6);
        break;
    }

    case WIFI_EVENT_AP_STADISCONNECTED:
    {
        wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
        SYS_LOG_INF("station " MACSTR " leave, AID=%d",
                    MAC2STR(event->mac), event->aid);
        break;
    }

    default:
        return;
    }

    wifi_ap_connect_cb connect_cb = s_cm.init_param.ap.connect_cb;
    if (connect_cb)
    {
        wifi_sta_mac_ip_list_t netif_sta_list;
        wifi_get_sta_list(&netif_sta_list);
        s_cm.ap_status = WIFI_AP_CONNECT_GOT_STA;
        connect_cb(s_cm.ap_status, netif_sta_list.num);
    }
}

void get_wifi_mac(uint8_t *mac)
{
    memcpy(mac, s_cm.mac, 6);
}

static void _wifi_event_handler_sta(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    wifi_sta_connect_status_t status;
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        status = WIFI_STA_CONNECT_CONNECTING;
        esp_wifi_connect();
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        status = WIFI_STA_CONNECT_CONNECTING;
        esp_wifi_connect();
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        status = WIFI_STA_CONNECT_GOT_IP;
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        SYS_LOG_INF("got ip: " IPSTR, IP2STR(&event->ip_info.ip));
    }
    else
    {
        return;
    }

    wifi_sta_connect_cb connect_cb = s_cm.init_param.sta.connect_cb;
    if (connect_cb)
    {
        uint8_t ip_v4[4];
        memset(ip_v4, 0, sizeof(ip_v4));
        if (s_cm.sta_status != status)
        {
            s_cm.sta_status = status;
            connect_cb(s_cm.sta_status, ip_v4);
        }
    }
}
