/*
 * This file is part of the cyw43-driver
 *
 * Copyright (C) 2019-2022 George Robotics Pty Ltd
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Any redistribution, use, or modification in source or binary form is done
 *    solely for personal benefit and not for any commercial purpose or for
 *    monetary gain.
 *
 * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT OWNER BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software is also available for use with certain devices under different
 * terms, as set out in the top level LICENSE file.  For commercial licensing
 * options please email contact@georgerobotics.com.au.
 */

#include <stdio.h>
#include <string.h>

#include "cyw43.h"
#include "cyw43_stats.h"

#if !CYW43_USE_SPI
#include "cyw43_sdio.h"
#endif

#ifdef CYW43_PIN_WL_HOST_WAKE
#define USE_SDIOIT (0)
#else
#define USE_SDIOIT (1)
#endif

// Bits 0-3 are an enumeration, while bits 8-11 are flags.
#define WIFI_JOIN_STATE_KIND_MASK (0x000f)
#define WIFI_JOIN_STATE_ACTIVE  (0x0001)
#define WIFI_JOIN_STATE_FAIL    (0x0002)
#define WIFI_JOIN_STATE_NONET   (0x0003)
#define WIFI_JOIN_STATE_BADAUTH (0x0004)
#define WIFI_JOIN_STATE_AUTH    (0x0200)
#define WIFI_JOIN_STATE_LINK    (0x0400)
#define WIFI_JOIN_STATE_KEYED   (0x0800)
#define WIFI_JOIN_STATE_ALL     (0x0e01)

#define CYW43_STA_IS_ACTIVE(self) (((self)->itf_state >> CYW43_ITF_STA) & 1)
#define CYW43_AP_IS_ACTIVE(self) (((self)->itf_state >> CYW43_ITF_AP) & 1)

cyw43_t cyw43_state;
void (*cyw43_poll)(void);
uint32_t cyw43_sleep;

#ifndef CYW43_POST_POLL_HOOK
#define CYW43_POST_POLL_HOOK
#endif

STATIC void cyw43_poll_func(void);
STATIC void cyw43_wifi_ap_init(cyw43_t *self);
STATIC void cyw43_wifi_ap_set_up(cyw43_t *self, bool up);

static inline uint32_t cyw43_get_be16(const uint8_t *buf) {
    return buf[0] << 8 | buf[1];
}

static inline uint32_t cyw43_get_be32(const uint8_t *buf) {
    return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
}

/*******************************************************************************/
// Initialisation and polling

void cyw43_init(cyw43_t *self) {
    #ifdef CYW43_PIN_WL_HOST_WAKE
    cyw43_hal_pin_config(CYW43_PIN_WL_HOST_WAKE, CYW43_HAL_PIN_MODE_INPUT, CYW43_HAL_PIN_PULL_NONE, 0);
    #endif
    cyw43_hal_pin_config(CYW43_PIN_WL_REG_ON, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0);
    cyw43_hal_pin_low(CYW43_PIN_WL_REG_ON);
    #ifdef CYW43_PIN_WL_RFSW_VDD
    cyw43_hal_pin_config(CYW43_PIN_WL_RFSW_VDD, CYW43_HAL_PIN_MODE_OUTPUT, CYW43_HAL_PIN_PULL_NONE, 0); // RF-switch power
    cyw43_hal_pin_low(CYW43_PIN_WL_RFSW_VDD);
    #endif

    cyw43_ll_init(&self->cyw43_ll, self);

    self->itf_state = 0;
    self->wifi_scan_state = 0;
    self->wifi_join_state = 0;
    self->pend_disassoc = false;
    self->pend_rejoin = false;
    self->pend_rejoin_wpa = false;
    self->ap_channel = 3;
    self->ap_ssid_len = 0;
    self->ap_key_len = 0;

    cyw43_poll = NULL;
    self->initted = true;
}

void cyw43_deinit(cyw43_t *self) {
    CYW43_THREAD_ENTER;
    if (cyw43_poll == NULL) {
        CYW43_THREAD_EXIT;
        return;
    }

    // Stop the TCP/IP network interfaces.
    cyw43_cb_tcpip_deinit(self, 0);
    cyw43_cb_tcpip_deinit(self, 1);

    // Turn off the SDIO bus.
    #if USE_SDIOIT
    cyw43_sdio_set_irq(false);
    #endif
    #if !CYW43_USE_SPI
    cyw43_sdio_deinit();
    #endif

    // Power off the WLAN chip and make sure all state is reset.
    cyw43_ll_deinit(&self->cyw43_ll);
    cyw43_init(self);

    CYW43_THREAD_EXIT;
}

STATIC int cyw43_ensure_up(cyw43_t *self) {
    CYW43_THREAD_LOCK_CHECK;

#ifndef NDEBUG
    assert(cyw43_is_initialized(self)); // cyw43_init has not been called
#endif
    if (cyw43_poll != NULL) {
        cyw43_ll_bus_sleep(&self->cyw43_ll, false);
        return 0;
    }

    // Disable the netif if it was previously up
    cyw43_cb_tcpip_deinit(self, CYW43_ITF_STA);
    cyw43_cb_tcpip_deinit(self, CYW43_ITF_AP);
    self->itf_state = 0;

    // Reset and power up the WL chip
    cyw43_hal_pin_low(CYW43_PIN_WL_REG_ON);
    cyw43_delay_ms(20);
    cyw43_hal_pin_high(CYW43_PIN_WL_REG_ON);
    cyw43_delay_ms(50);

    #if !CYW43_USE_SPI
    // Initialise SDIO bus
    // IRQ priority only needs to be higher than CYW43_THREAD_ENTER/EXIT protection (PENDSV)
    cyw43_sdio_init();
    #endif

    // Initialise the low-level driver
    #if !CYW43_USE_OTP_MAC
    cyw43_hal_get_mac(CYW43_HAL_MAC_WLAN0, self->mac);

    int ret = cyw43_ll_bus_init(&self->cyw43_ll, self->mac);
    #else
    // Not setting mac address. It should come from otp
    int ret = cyw43_ll_bus_init(&self->cyw43_ll, NULL);
    #endif

    if (ret != 0) {
        return ret;
    }

    #if CYW43_USE_OTP_MAC
    // Get our mac address cyw43_hal_get_mac can get this from cyw43_state.mac
    cyw43_ll_wifi_get_mac(&self->cyw43_ll, self->mac);
    #endif

    CYW43_DEBUG("cyw43 loaded ok, mac %02x:%02x:%02x:%02x:%02x:%02x\n",
        self->mac[0], self->mac[1], self->mac[2], self->mac[3], self->mac[4], self->mac[5]);

    // Enable async events from low-level driver
    cyw43_sleep = CYW43_SLEEP_MAX;
    cyw43_poll = cyw43_poll_func;
    #if USE_SDIOIT
    cyw43_sdio_set_irq(true);
    #elif !CYW43_USE_SPI
    extern void extint_set(const pin_obj_t *pin, uint32_t mode);
    extint_set(CYW43_PIN_WL_HOST_WAKE, GPIO_MODE_IT_FALLING);
    #endif

    // Kick things off
    cyw43_schedule_internal_poll_dispatch(cyw43_poll_func);

    return ret;
}

// This function must always be executed at the level where CYW43_THREAD_ENTER is effectively active
STATIC void cyw43_poll_func(void) {
    CYW43_THREAD_LOCK_CHECK;

    if (cyw43_poll == NULL) {
        // Poll scheduled during deinit, just ignore it
        return;
    }

    CYW43_STAT_INC(CYW43_RUN_COUNT);

    cyw43_t *self = &cyw43_state;
    if (cyw43_ll_has_work(&self->cyw43_ll)) {
        cyw43_ll_process_packets(&self->cyw43_ll);
    }

    if (self->pend_disassoc) {
        self->pend_disassoc = false;
        cyw43_ll_ioctl(&self->cyw43_ll, CYW43_IOCTL_SET_DISASSOC, 0, NULL, CYW43_ITF_STA);
    }

    if (self->pend_rejoin_wpa) {
        self->pend_rejoin_wpa = false;
        cyw43_ll_wifi_set_wpa_auth(&self->cyw43_ll);
    }

    if (self->pend_rejoin) {
        self->pend_rejoin = false;
        cyw43_ll_wifi_rejoin(&self->cyw43_ll);
        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;
    }

    if (cyw43_sleep == 0) {
        cyw43_ll_bus_sleep(&self->cyw43_ll, true);
        #if !USE_SDIOIT && !CYW43_USE_SPI
        cyw43_sdio_deinit(); // save power while WLAN bus sleeps
        #endif
    }

    #if USE_SDIOIT
    cyw43_sdio_set_irq(true);
    #endif

    #ifdef CYW43_POST_POLL_HOOK
    CYW43_POST_POLL_HOOK
    #endif

}

/*******************************************************************************/
// Callback interface to low-level driver

int cyw43_cb_read_host_interrupt_pin(void *cb_data) {
    (void)cb_data;
    #ifdef CYW43_PIN_WL_HOST_WAKE
    return cyw43_hal_pin_read(CYW43_PIN_WL_HOST_WAKE);
    #else
    return cyw43_hal_pin_read(CYW43_PIN_WL_SDIO_1);
    #endif
}

void cyw43_cb_ensure_awake(void *cb_data) {
    (void)cb_data;
    cyw43_sleep = CYW43_SLEEP_MAX;
    #if !USE_SDIOIT && !CYW43_USE_SPI
    cyw43_sdio_reinit();
    #endif
}

STATIC const char *cyw43_async_event_name_table[89] = {
    //[0 ... 88] = NULL,
    [CYW43_EV_SET_SSID] = "SET_SSID",
    [CYW43_EV_JOIN] = "JOIN",
    [CYW43_EV_AUTH] = "AUTH",
    [CYW43_EV_DEAUTH_IND] = "DEAUTH_IND",
    [CYW43_EV_ASSOC] = "ASSOC",
    [CYW43_EV_DISASSOC] = "DISASSOC",
    [CYW43_EV_DISASSOC_IND] = "DISASSOC_IND",
    [CYW43_EV_LINK] = "LINK",
    [CYW43_EV_PSK_SUP] = "PSK_SUP",
    [CYW43_EV_ESCAN_RESULT] = "ESCAN_RESULT",
    [CYW43_EV_CSA_COMPLETE_IND] = "CSA_COMPLETE_IND",
    [CYW43_EV_ASSOC_REQ_IE] = "ASSOC_REQ_IE",
    [CYW43_EV_ASSOC_RESP_IE] = "ASSOC_RESP_IE",
};

STATIC void cyw43_dump_async_event(const cyw43_async_event_t *ev) {
    CYW43_PRINTF("[% 8d] ASYNC(%04x,",
        (int)cyw43_hal_ticks_ms(),
        (unsigned int)ev->flags
        );
    if (ev->event_type < CYW43_ARRAY_SIZE(cyw43_async_event_name_table)
        && cyw43_async_event_name_table[ev->event_type] != NULL) {
        CYW43_PRINTF("%s", cyw43_async_event_name_table[ev->event_type]);
    } else {
        CYW43_PRINTF("%u", (unsigned int)ev->event_type);
    }
    CYW43_PRINTF(",%u,%u,%u)\n",
        (unsigned int)ev->status,
        (unsigned int)ev->reason,
        (unsigned int)ev->interface
        );
}

void cyw43_cb_process_async_event(void *cb_data, const cyw43_async_event_t *ev) {
    cyw43_t *self = cb_data;

    if (self->trace_flags & CYW43_TRACE_ASYNC_EV) {
        cyw43_dump_async_event(ev);
    }

    if (ev->event_type == CYW43_EV_ESCAN_RESULT && self->wifi_scan_state == 1) {
        // Escan result event
        if (ev->status == 8) {
            // Partial result
            int ret = self->wifi_scan_cb(self->wifi_scan_env, &ev->u.scan_result);
            if (ret != 0) {
                // TODO need to abort scan, or just ignore any more results
            }
        } else if (ev->status == 0) {
            // Scan complete
            self->wifi_scan_state = 2;
        }

    } else if (ev->event_type == CYW43_EV_DISASSOC) {
        cyw43_cb_tcpip_set_link_down(self, CYW43_ITF_STA);
        self->wifi_join_state = 0x0000;

    /*
    } else if (ev->event_type == CYW43_EV_DISASSOC_IND) {
        if (ev->interface == CYW43_ITF_AP) {
            // Station disassociated with our AP, let DHCP server know so it can free the IP address
            dhcp_server_disassoc(&self->dhcp_server, buf + 24);
        }
    */

    // WiFi join events
    } else if (ev->event_type == CYW43_EV_PRUNE) {
        if (ev->status == 0 && ev->reason == 8) {
            // RSN mismatch, retry join with WPA auth
            self->pend_rejoin = true;
            self->pend_rejoin_wpa = true;
            cyw43_schedule_internal_poll_dispatch(cyw43_poll_func);
        }
    } else if (ev->event_type == CYW43_EV_SET_SSID) {
        if (ev->status == 0) {
            // Success setting SSID
        } else if (ev->status == 3 && ev->reason == 0) {
            self->wifi_join_state = WIFI_JOIN_STATE_NONET;
            // No matching SSID found (could be out of range, or down)
        } else {
            // Other failure setting SSID
            self->wifi_join_state = WIFI_JOIN_STATE_FAIL;
        }
    } else if (ev->event_type == CYW43_EV_AUTH) {
        if (ev->status == 0) {
            if ((self->wifi_join_state & WIFI_JOIN_STATE_KIND_MASK) == WIFI_JOIN_STATE_BADAUTH) {
                // A good-auth follows a bad-auth, so change the join state back to active.
                self->wifi_join_state = (self->wifi_join_state & ~WIFI_JOIN_STATE_KIND_MASK) | WIFI_JOIN_STATE_ACTIVE;
            }
            self->wifi_join_state |= WIFI_JOIN_STATE_AUTH;
        } else if (ev->status == 6) {
            // Unsolicited auth packet, ignore it
        } else {
            // Cannot authenticate
            self->wifi_join_state = WIFI_JOIN_STATE_BADAUTH;
        }
    } else if (ev->event_type == CYW43_EV_DEAUTH_IND) {
        if (ev->status == 0 && ev->reason == 2) {
            // Deauth, probably because password was wrong; disassociate
            self->pend_disassoc = true;
            cyw43_schedule_internal_poll_dispatch(cyw43_poll_func);
        }
    } else if (ev->event_type == CYW43_EV_LINK) {
        if (ev->status == 0) {
            if (ev->flags & 1) {
                // Link is up
                if (ev->interface == CYW43_ITF_STA) {
                    self->wifi_join_state |= WIFI_JOIN_STATE_LINK;
                } else {
                    cyw43_cb_tcpip_set_link_up(self, ev->interface);
                }
            } else {
                // Link is down
                cyw43_cb_tcpip_set_link_down(self, ev->interface);
            }
        }
    } else if (ev->event_type == CYW43_EV_PSK_SUP) {
        if (ev->status == 6) { // WLC_SUP_KEYED
            self->wifi_join_state |= WIFI_JOIN_STATE_KEYED;
        } else if ((ev->status == 4 || ev->status == 8 || ev->status == 11) && ev->reason == 15) {
            // Timeout waiting for key exchange M1/M3/G1
            // Probably at edge of the cell, retry
            self->pend_rejoin = true;
            cyw43_schedule_internal_poll_dispatch(cyw43_poll_func);
        } else {
            // PSK_SUP failure
            self->wifi_join_state = WIFI_JOIN_STATE_BADAUTH;
        }
    }

    if (self->wifi_join_state == WIFI_JOIN_STATE_ALL) {
        // STA connected
        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;
        cyw43_cb_tcpip_set_link_up(self, CYW43_ITF_STA);
    }
}

/*******************************************************************************/
// Ioctl and Ethernet interface

int cyw43_ioctl(cyw43_t *self, uint32_t cmd, size_t len, uint8_t *buf, uint32_t iface) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    ret = cyw43_ll_ioctl(&self->cyw43_ll, cmd, len, buf, iface);
    CYW43_THREAD_EXIT;

    return ret;
}

int cyw43_send_ethernet(cyw43_t *self, int itf, size_t len, const void *buf, bool is_pbuf) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    ret = cyw43_ll_send_ethernet(&self->cyw43_ll, itf, len, buf, is_pbuf);
    CYW43_THREAD_EXIT;

    return ret;
}

/*******************************************************************************/
// WiFi control

STATIC int cyw43_wifi_on(cyw43_t *self, uint32_t country) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    #ifdef CYW43_PIN_WL_RFSW_VDD
    // Turn the RF-switch on
    cyw43_hal_pin_high(CYW43_PIN_WL_RFSW_VDD);
    #endif

    ret = cyw43_ll_wifi_on(&self->cyw43_ll, country);
    CYW43_THREAD_EXIT;

    return ret;
}

int cyw43_wifi_pm(cyw43_t *self, uint32_t pm_in) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    // pm_in: 0x00adbrrm
    uint32_t pm = pm_in & 0xf;
    uint32_t pm_sleep_ret = (pm_in >> 4) & 0xff;
    uint32_t li_bcn = (pm_in >> 12) & 0xf;
    uint32_t li_dtim = (pm_in >> 16) & 0xf;
    uint32_t li_assoc = (pm_in >> 20) & 0xf;

    ret = cyw43_ll_wifi_pm(&self->cyw43_ll, pm, pm_sleep_ret, li_bcn, li_dtim, li_assoc);
    CYW43_THREAD_EXIT;

    return ret;
}

int cyw43_wifi_get_mac(cyw43_t *self, int itf, uint8_t mac[6]) {
    (void)self;
    (void)itf;
    cyw43_hal_get_mac(CYW43_HAL_MAC_WLAN0, &mac[0]);
    return 0;
}

void cyw43_wifi_set_up(cyw43_t *self, int itf, bool up, uint32_t country) {
    CYW43_THREAD_ENTER;
    if (up) {
        if (self->itf_state == 0) {
            if (cyw43_wifi_on(self, country) != 0) {
                CYW43_THREAD_EXIT;
                return;
            }
            cyw43_wifi_pm(self, CYW43_DEFAULT_PM);
        }
        if (itf == CYW43_ITF_AP) {
            cyw43_wifi_ap_init(self);
            cyw43_wifi_ap_set_up(self, true);
        }
        if ((self->itf_state & (1 << itf)) == 0) {
            cyw43_cb_tcpip_deinit(self, itf);
            cyw43_cb_tcpip_init(self, itf);
            self->itf_state |= 1 << itf;
        }
    } else {
        if (itf == CYW43_ITF_AP) {
            cyw43_wifi_ap_set_up(self, false);
        }
    }
    CYW43_THREAD_EXIT;
}

int cyw43_wifi_scan(cyw43_t *self, cyw43_wifi_scan_options_t *opts, void *env, int (*result_cb)(void *, const cyw43_ev_scan_result_t *)) {
    CYW43_THREAD_ENTER;
    if (self->itf_state == 0) {
        CYW43_THREAD_EXIT;
        return -CYW43_EPERM;
    }

    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    // Set state and callback data
    self->wifi_scan_state = 1;
    self->wifi_scan_env = env;
    self->wifi_scan_cb = result_cb;

    // Start the scan
    ret = cyw43_ll_wifi_scan(&self->cyw43_ll, opts);

    CYW43_THREAD_EXIT;

    return ret;
}

int cyw43_wifi_link_status(cyw43_t *self, int itf) {
    if (itf == CYW43_ITF_STA) {
        int s = self->wifi_join_state & WIFI_JOIN_STATE_KIND_MASK;
        if (s == WIFI_JOIN_STATE_ACTIVE) {
            return CYW43_LINK_JOIN;
        } else if (s == WIFI_JOIN_STATE_FAIL) {
            return CYW43_LINK_FAIL;
        } else if (s == WIFI_JOIN_STATE_NONET) {
            return CYW43_LINK_NONET;
        } else if (s == WIFI_JOIN_STATE_BADAUTH) {
            return CYW43_LINK_BADAUTH;
        } else {
            return CYW43_LINK_DOWN;
        }
    } else {
        return CYW43_LINK_DOWN;
    }
}

/*******************************************************************************/
// WiFi STA

int cyw43_wifi_join(cyw43_t *self, size_t ssid_len, const uint8_t *ssid, size_t key_len, const uint8_t *key, uint32_t auth_type, const uint8_t *bssid, uint32_t channel) {
    CYW43_THREAD_ENTER;
    if (!CYW43_STA_IS_ACTIVE(self)) {
        CYW43_THREAD_EXIT;
        return -CYW43_EPERM;
    }

    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }

    ret = cyw43_ll_wifi_join(&self->cyw43_ll, ssid_len, ssid, key_len, key, auth_type, bssid, channel);

    if (ret == 0) {
        // Wait for responses: EV_AUTH, EV_LINK, EV_SET_SSID, EV_PSK_SUP
        // Will get EV_DEAUTH_IND if password is invalid
        self->wifi_join_state = WIFI_JOIN_STATE_ACTIVE;

        if (auth_type == 0) {
            // For open security we don't need EV_PSK_SUP, so set that flag indicator now
            self->wifi_join_state |= WIFI_JOIN_STATE_KEYED;
        }
    }

    CYW43_THREAD_EXIT;

    return ret;
}

int cyw43_wifi_leave(cyw43_t *self, int itf) {
    // Disassociate with SSID
    return cyw43_ioctl(self, CYW43_IOCTL_SET_DISASSOC, 0, NULL, itf);
}

/*******************************************************************************/
// WiFi AP

STATIC void cyw43_wifi_ap_init(cyw43_t *self) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return;
    }

    cyw43_ll_wifi_ap_init(&self->cyw43_ll, self->ap_ssid_len, self->ap_ssid, self->ap_auth, self->ap_key_len, self->ap_key, self->ap_channel);
    CYW43_THREAD_EXIT;
}

STATIC void cyw43_wifi_ap_set_up(cyw43_t *self, bool up) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return;
    }

    cyw43_ll_wifi_ap_set_up(&self->cyw43_ll, up);
    CYW43_THREAD_EXIT;
}

void cyw43_wifi_ap_get_max_stas(cyw43_t *self, int *max_stas) {
    cyw43_wifi_ap_get_stas(self, max_stas, NULL);
}

void cyw43_wifi_ap_get_stas(cyw43_t *self, int *num_stas, uint8_t *macs) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return;
    }

    cyw43_ll_wifi_ap_get_stas(&self->cyw43_ll, num_stas, macs);
    CYW43_THREAD_EXIT;
}

/*******************************************************************************/
// GPIO control

#if CYW43_GPIO

int cyw43_gpio_set(cyw43_t *self, int gpio, bool val) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }
    ret = cyw43_ll_gpio_set(&self->cyw43_ll, gpio, val);
    CYW43_THREAD_EXIT;
    return ret;
}

int cyw43_gpio_get(cyw43_t *self, int gpio, bool *val) {
    CYW43_THREAD_ENTER;
    int ret = cyw43_ensure_up(self);
    if (ret) {
        CYW43_THREAD_EXIT;
        return ret;
    }
    ret = cyw43_ll_gpio_get(&self->cyw43_ll, gpio, val);
    CYW43_THREAD_EXIT;
    return ret;
}

#endif
