/* SPWFSAxx Devices
 * Copyright (c) 2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mbed_debug.h"

#include "SpwfSAInterface.h" /* must be included first */
#include "SPWFSAxx.h"

static const char out_delim[] = {SPWFSAxx::_cr_, '\0'};

SPWFSAxx::SPWFSAxx(PinName tx, PinName rx,
                   PinName rts, PinName cts,
                   SpwfSAInterface &ifce, bool debug,
                   PinName wakeup, PinName reset)
: _serial(tx, rx, SPWFXX_DEFAULT_BAUD_RATE), _parser(&_serial, out_delim),
  _wakeup(wakeup, 1), _reset(reset, 1),
  _rts(rts), _cts(cts),
  _timeout(SPWF_INIT_TIMEOUT), _dbg_on(debug),
  _pending_sockets_bitmap(0),
  _network_lost_flag(false),
  _associated_interface(ifce),
  _call_event_callback_blocked(0),
  _callback_func(),
  _packets(0), _packets_end(&_packets)
{
    memset(_pending_pkt_sizes, 0, sizeof(_pending_pkt_sizes));

    _serial.sigio(Callback<void()>(this, &SPWFSAxx::_event_handler));
    _parser.debug_on(debug);
    _parser.set_timeout(_timeout);

    /* unlikely OOBs */
    _parser.oob("+WIND:5:WiFi Hardware Failure", callback(this, &SPWFSAxx::_wifi_hwfault_handler));
    _parser.oob("+WIND:33:WiFi Network Lost", callback(this, &SPWFSAxx::_network_lost_handler_th));
#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1
    _parser.oob("+WIND:24:WiFi Up::", callback(this, &SPWFSAxx::_skip_oob));
#endif
    _parser.oob("+WIND:8:Hard Fault", callback(this, &SPWFSAxx::_hard_fault_handler));

    /* most likely OOBs */
    _parser.oob(SPWFXX_OOB_ERROR, callback(this, &SPWFSAxx::_error_handler));
    _parser.oob("+WIND:58:Socket Closed", callback(this, &SPWFSAxx::_server_gone_handler));
    _parser.oob("+WIND:55:Pending Data", callback(this, &SPWFSAxx::_packet_handler_th));
}

bool SPWFSAxx::startup(int mode)
{
    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    /*Reset module*/
    if(!hw_reset()) {
        debug_if(_dbg_on, "\r\nSPWF> HW reset failed\r\n");
        return false;
    }

    /* factory reset */
    if(!(_parser.send(SPWFXX_SEND_FWCFG) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error restore factory default settings\r\n");
        return false;
    }

    /*switch off led*/
    if(!(_parser.send("AT+S.SCFG=blink_led,0") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error stop blinking led (%d)\r\n", __LINE__);
        return false;
    }

    /*set local echo to 0*/
    if(!(_parser.send(SPWFXX_SEND_DISABLE_LE) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error local echo set\r\n");
        return false;
    }

    /*set the operational rates*/
    if(!(_parser.send("AT+S.SCFG=wifi_opr_rate_mask,0x003FFFCF") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error setting ht_mode\r\n");
        return false;
    }

    /*enable the 802.11n mode*/
    if(!(_parser.send("AT+S.SCFG=wifi_ht_mode,1") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error setting operational rates\r\n");
        return false;
    }

    /*set idle mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/
    if(!(_parser.send("AT+S.SCFG=wifi_mode,%d", mode) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set idle (%d)\r\n", __LINE__);
        return false;
    }

#if defined(MBED_MAJOR_VERSION)
#if !DEVICE_SERIAL_FC || (MBED_VERSION < MBED_ENCODE_VERSION(5, 7, 0))
    /*disable HW flow control*/
    if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n");
        return false;
    }
#else // DEVICE_SERIAL_FC && (MBED_VERSION >= MBED_ENCODE_VERSION(5, 7, 0))
    if((_rts != NC) && (_cts != NC)) {
        /*enable HW flow control*/
        if(!(_parser.send(SPWFXX_SEND_ENABLE_FC) && _recv_ok()))
        {
            debug_if(_dbg_on, "\r\nSPWF> error enabling HW flow control\r\n");
            return false;
        }

        /*configure pins for HW flow control*/
        _serial.set_flow_control(SerialBase::RTSCTS, _rts, _cts);
    } else {
        /*disable HW flow control*/
        if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok()))
        {
            debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n");
            return false;
        }
    }
#endif // DEVICE_SERIAL_FC && (MBED_VERSION >= MBED_ENCODE_VERSION(5, 7, 0))
#else // !defined(MBED_MAJOR_VERSION) - Assuming `master` branch
#if !DEVICE_SERIAL_FC
    /*disable HW flow control*/
    if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n");
        return false;
    }
#else // DEVICE_SERIAL_FC
    if((_rts != NC) && (_cts != NC)) {
        /*enable HW flow control*/
        if(!(_parser.send(SPWFXX_SEND_ENABLE_FC) && _recv_ok()))
        {
            debug_if(_dbg_on, "\r\nSPWF> error enabling HW flow control\r\n");
            return false;
        }

        /*configure pins for HW flow control*/
        _serial.set_flow_control(SerialBase::RTSCTS, _rts, _cts);
    } else {
        /*disable HW flow control*/
        if(!(_parser.send(SPWFXX_SEND_DISABLE_FC) && _recv_ok()))
        {
            debug_if(_dbg_on, "\r\nSPWF> error disabling HW flow control\r\n");
            return false;
        }
    }
#endif // DEVICE_SERIAL_FC
#endif // !defined(MBED_MAJOR_VERSION)

    /* Disable selected WINDs */
    _winds_on();

    /* sw reset */
    if(!reset()) {
        debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__);
        return false;
    }

#ifndef NDEBUG
    if (!(_parser.send(SPWFXX_SEND_GET_CONS_STATE)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting console state\r\n");
        return false;
    }

    if (!(_parser.send(SPWFXX_SEND_GET_CONS_SPEED)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting console speed\r\n");
        return false;
    }

    if (!(_parser.send(SPWFXX_SEND_GET_HWFC_STATE)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting hwfc state\r\n");
        return false;
    }

#if (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) || defined(IDW01M1_FW_REL_35X)
    /* betzw: IDW01M1 FW versions <3.5 seem to have problems with the following two commands.
     *        For the sake of simplicity, just excluding them for IDW01M1 in general.
     */
    if (!(_parser.send(SPWFXX_SEND_GET_CONS_DELIM)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting console delimiter\r\n");
        return false;
    }

    if (!(_parser.send(SPWFXX_SEND_GET_CONS_ERRS)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting console error setting\r\n");
        return false;
    }
#endif // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) || defined(IDW01M1_FW_REL_35X)

    if (!(_parser.send("AT+S.GCFG=sleep_enabled")
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting sleep state enabled\r\n");
        return false;
    }

    if (!(_parser.send("AT+S.GCFG=wifi_powersave")
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting powersave mode\r\n");
        return false;
    }

    if (!(_parser.send("AT+S.GCFG=standby_enabled")
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> error getting standby state enabled\r\n");
        return false;
    }
#endif

    return true;
}

bool SPWFSAxx::_wait_console_active(void) {
    int trials = 0;

    while(true) {
        if (_parser.recv("+WIND:0:Console active\n") && _recv_delim_lf()) {
            debug_if(_dbg_on, "AT^ +WIND:0:Console active\r\n");
            return true;
        }
        if(++trials >= SPWFXX_MAX_TRIALS) {
            debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__);
            empty_rx_buffer();
            return false;
        }
    }
}

bool SPWFSAxx::_wait_wifi_hw_started(void) {
    int trials = 0;

    while(true) {
        if (_parser.recv("+WIND:32:WiFi Hardware Started\n") && _recv_delim_lf()) {
            debug_if(_dbg_on, "AT^ +WIND:32:WiFi Hardware Started\r\n");
            return true;
        }
        if(++trials >= SPWFXX_MAX_TRIALS) {
            debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__);
            empty_rx_buffer();
            return false;
        }
    }
}

bool SPWFSAxx::hw_reset(void)
{
#if (MBED_CONF_IDW0XX1_EXPANSION_BOARD != IDW04A1) || !defined(IDW04A1_WIFI_HW_BUG_WA) // betzw: HW reset doesn't work as expected on unmodified X_NUCLEO_IDW04A1 expansion boards
    _reset.write(0);
    wait_ms(200);
    _reset.write(1); 
#else // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) && defined(IDW04A1_WIFI_HW_BUG_WA): substitute with SW reset
    _parser.send(SPWFXX_SEND_SW_RESET);
#endif // (MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1) && defined(IDW04A1_WIFI_HW_BUG_WA)
    return _wait_console_active();
}

bool SPWFSAxx::reset(void)
{
    bool ret;

    /* save current setting in flash */
    if(!(_parser.send(SPWFXX_SEND_SAVE_SETTINGS) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error saving configuration to flash (%s, %d)\r\n", __func__, __LINE__);
        return false;
    }

    if(!_parser.send(SPWFXX_SEND_SW_RESET)) return false; /* betzw - NOTE: "keep the current state and reset the device".
                                                                     We assume that the module informs us about the
                                                                     eventual closing of sockets via "WIND" asynchronous
                                                                     indications! So everything regarding the clean-up
                                                                     of these situations is handled there. */

    /* waiting for HW to start */
    ret = _wait_wifi_hw_started();

    return ret;
}

/* Security Mode
   None          = 0, 
   WEP           = 1,
   WPA_Personal  = 2,
 */
bool SPWFSAxx::connect(const char *ap, const char *passPhrase, int securityMode)
{
    int trials;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    //AT+S.SCFG=wifi_wpa_psk_text,%s
    if(!(_parser.send("AT+S.SCFG=wifi_wpa_psk_text,%s", passPhrase) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error pass set\r\n");
        return false;
    } 

    //AT+S.SSIDTXT=%s
    if(!(_parser.send("AT+S.SSIDTXT=%s", ap) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error ssid set\r\n");
        return false;
    }

    //AT+S.SCFG=wifi_priv_mode,%d
    if(!(_parser.send("AT+S.SCFG=wifi_priv_mode,%d", securityMode) && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error security mode set\r\n");
        return false;
    }

    /*set STA mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/
    if(!(_parser.send("AT+S.SCFG=wifi_mode,1") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set 1 (STA)\r\n");
        return false;
    }

    /* sw reset */
    if(!reset()) {
        debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__);
        return false;
    }

    trials = 0;
    while(true) {
        if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf())
        {
            if(strstr(_msg_buffer, ":24:") != NULL) { // WiFi Up
                debug_if(_dbg_on, "AT^ %s\n", _msg_buffer);
                if(strchr(_msg_buffer, '.') != NULL) { // IPv4 address
                    break;
                } else {
                    continue;
                }
            }
            if(strstr(_msg_buffer, ":40:") != NULL) { // Deauthentication
                debug_if(_dbg_on, "AT~ %s\n", _msg_buffer);
                if(++trials < SPWFXX_MAX_TRIALS) { // give it three trials
                    continue;
                }
                disconnect();
                empty_rx_buffer();
                return false;
            } else {
                debug_if(_dbg_on, "AT] %s\n", _msg_buffer);
            }
            continue;
        }
        if(++trials >= SPWFXX_MAX_TRIALS) {
            debug("\r\nSPWF> ERROR: Should never happen! (%s, %d)\r\n", __func__, __LINE__);
            empty_rx_buffer();
            return false;
        }
    }

    return true;
}

bool SPWFSAxx::disconnect(void)
{
    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1
    /*disable Wi-Fi device*/
    if(!(_parser.send("AT+S.WIFI=0") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error disabling WiFi\r\n");
        return false;
    }
#endif // IDW04A1

    /*set idle mode (0->idle, 1->STA,3->miniAP, 2->IBSS)*/
    if(!(_parser.send("AT+S.SCFG=wifi_mode,0") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error WiFi mode set idle (%d)\r\n", __LINE__);
        return false;
    }

#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1
    /*enable Wi-Fi device*/
    if(!(_parser.send("AT+S.WIFI=1") && _recv_ok()))
    {
        debug_if(_dbg_on, "\r\nSPWF> error enabling WiFi\r\n");
        return false;
    }
#endif // IDW04A1

    // reset module
    if(!reset()) {
        debug_if(_dbg_on, "\r\nSPWF> SW reset failed (%s, %d)\r\n", __func__, __LINE__);
        return false;
    }

    /* clean up state */
    _associated_interface.inner_constructor();
    _free_all_packets();

    return true;
}

const char *SPWFSAxx::getIPAddress(void)
{
    unsigned int n1, n2, n3, n4;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    if (!(_parser.send("AT+S.STS=ip_ipaddr")
            && _parser.recv(SPWFXX_RECV_IP_ADDR, &n1, &n2, &n3, &n4)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> get IP address error\r\n");
        return NULL;
    }

    debug_if(_dbg_on, "AT^ ip_ipaddr = %u.%u.%u.%u\r\n", n1, n2, n3, n4);

    sprintf((char*)_ip_buffer,"%u.%u.%u.%u", n1, n2, n3, n4);
    return _ip_buffer;
}

const char *SPWFSAxx::getGateway(void)
{
    unsigned int n1, n2, n3, n4;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    if (!(_parser.send("AT+S.STS=ip_gw")
            && _parser.recv(SPWFXX_RECV_GATEWAY, &n1, &n2, &n3, &n4)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> get gateway error\r\n");
        return NULL;
    }

    debug_if(_dbg_on, "AT^ ip_gw = %u.%u.%u.%u\r\n", n1, n2, n3, n4);

    sprintf((char*)_gateway_buffer,"%u.%u.%u.%u", n1, n2, n3, n4);
    return _gateway_buffer;
}

const char *SPWFSAxx::getNetmask(void)
{
    unsigned int n1, n2, n3, n4;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    if (!(_parser.send("AT+S.STS=ip_netmask")
            && _parser.recv(SPWFXX_RECV_NETMASK, &n1, &n2, &n3, &n4)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> get netmask error\r\n");
        return NULL;
    }

    debug_if(_dbg_on, "AT^ ip_netmask = %u.%u.%u.%u\r\n", n1, n2, n3, n4);

    sprintf((char*)_netmask_buffer,"%u.%u.%u.%u", n1, n2, n3, n4);
    return _netmask_buffer;
}

int8_t SPWFSAxx::getRssi(void)
{
    int ret;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    if (!(_parser.send("AT+S.PEERS=0,rx_rssi")
            && _parser.recv(SPWFXX_RECV_RX_RSSI, &ret)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> get RX rssi error\r\n");
        return 0;
    }

    return (int8_t)ret;
}

const char *SPWFSAxx::getMACAddress(void)
{
    unsigned int n1, n2, n3, n4, n5, n6;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    if (!(_parser.send("AT+S.GCFG=nv_wifi_macaddr")
            && _parser.recv(SPWFXX_RECV_MAC_ADDR, &n1, &n2, &n3, &n4, &n5, &n6)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> get MAC address error\r\n");
        return 0;
    }

    debug_if(_dbg_on, "AT^ nv_wifi_macaddr = %x:%x:%x:%x:%x:%x\r\n", n1, n2, n3, n4, n5, n6);

    sprintf((char*)_mac_buffer,"%02X:%02X:%02X:%02X:%02X:%02X", n1, n2, n3, n4, n5, n6);
    return _mac_buffer;
}

bool SPWFSAxx::isConnected(void)
{
    return _associated_interface._connected_to_network;
}

nsapi_size_or_error_t SPWFSAxx::send(int spwf_id, const void *data, uint32_t amount, int internal_id)
{
    uint32_t sent = 0U, to_send;
    nsapi_size_or_error_t ret;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    _process_winds(); // perform async indication handling (to early detect eventually closed sockets)

    /* betzw - WORK AROUND module FW issues: split up big packages in smaller ones */
    for(to_send = (amount > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : amount;
            sent < amount;
            to_send = ((amount - sent) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (amount - sent)) {
        {
            BlockExecuter bh_handler(Callback<void()>(this, &SPWFSAxx::_execute_bottom_halves));

            // betzw - TODO: handle different errors more accurately!
            if (!_associated_interface._socket_is_still_connected(internal_id)) {
                debug_if(_dbg_on, "\r\nSPWF> Socket not connected anymore: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__);
                break;
            } else if(!_parser.send("AT+S.SOCKW=%d,%d", spwf_id, (unsigned int)to_send)) {
                debug_if(_dbg_on, "\r\nSPWF> Sending command failed: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__);
                break;
            } else if(_parser.write(((char*)data)+sent, (int)to_send) != (int)to_send) {
                debug_if(_dbg_on, "\r\nSPWF> Sending data failed: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__);
                break;
            } else if(!_recv_ok()) {
                debug_if(_dbg_on, "\r\nSPWF> Sending did not receive OK: sent=%u, to_send=%u! (%s, %d)\r\n", sent, to_send, __func__, __LINE__);
                break;
            }
        }

        sent += to_send;
    }

    if(sent > 0) { // `sent == 0` indicates a potential error
        ret = sent;
    } else if(amount == 0) {
        ret = NSAPI_ERROR_OK;
    } else if(_associated_interface._socket_is_still_connected(internal_id)) {
        ret = NSAPI_ERROR_DEVICE_ERROR;
    } else {
        ret = NSAPI_ERROR_CONNECTION_LOST;
    }

    return ret;
}

int SPWFSAxx::_read_len(int spwf_id) {
    unsigned int amount;

    if (!(_parser.send("AT+S.SOCKQ=%d", spwf_id)
            && _parser.recv(SPWFXX_RECV_DATALEN, &amount)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed\r\n", __func__);
        return SPWFXX_ERR_LEN;
    }

    if(amount > 0) {
        debug_if(_dbg_on, "\r\nSPWF> %s():\t\t%d:%d\r\n", __func__, spwf_id, amount);
    }

    MBED_ASSERT(((int)amount) >= 0);

    return (int)amount;
}

#define SPWFXX_WINDS_OFF "0xFFFFFFFF"

void SPWFSAxx::_winds_on(void) {
    MBED_ASSERT(_is_event_callback_blocked());

    if(!(_parser.send(SPWFXX_SEND_WIND_OFF_HIGH SPWFXX_WINDS_HIGH_ON) && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
    }
    if(!(_parser.send(SPWFXX_SEND_WIND_OFF_MEDIUM SPWFXX_WINDS_MEDIUM_ON) && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
    }
    if(!(_parser.send(SPWFXX_SEND_WIND_OFF_LOW SPWFXX_WINDS_LOW_ON) && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
    }
}

/* Define beyond macro in case you want to report back failures in switching off WINDs to the caller */
// #define SPWFXX_SOWF
/* Note: in case of error blocking has been (tried to be) lifted */
bool SPWFSAxx::_winds_off(void) {
    MBED_ASSERT(_is_event_callback_blocked());

    if (!(_parser.send(SPWFXX_SEND_WIND_OFF_LOW SPWFXX_WINDS_OFF)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
#ifdef SPWFXX_SOWF // betzw: try to continue
        _winds_on();
        return false;
#endif
    }

    if (!(_parser.send(SPWFXX_SEND_WIND_OFF_MEDIUM SPWFXX_WINDS_OFF)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
#ifdef SPWFXX_SOWF // betzw: try to continue
        _winds_on();
        return false;
#endif
    }

    if (!(_parser.send(SPWFXX_SEND_WIND_OFF_HIGH SPWFXX_WINDS_OFF)
            && _recv_ok())) {
        debug_if(_dbg_on, "\r\nSPWF> %s failed at line #%d\r\n", __func__, __LINE__);
#ifdef SPWFXX_SOWF // betzw: try to continue
        _winds_on();
        return false;
#endif
    }

    return true;
}

void SPWFSAxx::_execute_bottom_halves(void) {
    _network_lost_handler_bh();
    _packet_handler_bh();
}

void SPWFSAxx::_read_in_pending(void) {
    static int internal_id_cnt = 0;

    while(_is_data_pending()) {
        if(_associated_interface._socket_has_connected(internal_id_cnt)) {
            int spwf_id = _associated_interface._ids[internal_id_cnt].spwf_id;

            if(_is_data_pending(spwf_id)) {
                int amount;

                amount = _read_in_pkt(spwf_id, false);
                if(amount == SPWFXX_ERR_OOM) { /* consider only 'SPWFXX_ERR_OOM' as non recoverable */
                    return;
                }
            }

            if(!_is_data_pending(spwf_id)) {
                internal_id_cnt++;
                internal_id_cnt %= SPWFSA_SOCKET_COUNT;
            }
        } else {
            internal_id_cnt++;
            internal_id_cnt %= SPWFSA_SOCKET_COUNT;
        }
    }
}

/* Note: returns
 * 'SPWFXX_ERR_OK'   in case of success
 * 'SPWFXX_ERR_OOM'  in case of "out of memory"
 * 'SPWFXX_ERR_READ' in case of `_read_in()` error
 */
int SPWFSAxx::_read_in_packet(int spwf_id, uint32_t amount) {
    struct packet *packet = (struct packet*)malloc(sizeof(struct packet) + amount);
    if (!packet) {
#ifndef NDEBUG
        error("\r\nSPWF> %s(%d): Out of memory!\r\n", __func__, __LINE__);
#else // NDEBUG
        debug("\r\nSPWF> %s(%d): Out of memory!\r\n", __func__, __LINE__);
#endif
        debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__);
        return SPWFXX_ERR_OOM; /* out of memory: give up here! */
    }

    /* init packet */
    packet->id = spwf_id;
    packet->len = amount;
    packet->next = 0;

    /* read data in */
    if(!(_read_in((char*)(packet + 1), spwf_id, amount) > 0)) {
        free(packet);
        debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__);
        return SPWFXX_ERR_READ;
    } else {
        debug_if(_dbg_on, "\r\nSPWF> %s():\t%d:%d\r\n", __func__, spwf_id, amount);

        /* append to packet list */
        *_packets_end = packet;
        _packets_end = &packet->next;

        /* force call of (external) callback */
        _call_callback();
    }

    return SPWFXX_ERR_OK;
}

void SPWFSAxx::_free_packets(int spwf_id) {
    // check if any packets are ready for `spwf_id`
    for(struct packet **p = &_packets; *p;) {
        if ((*p)->id == spwf_id) {
            struct packet *q = *p;
            if (_packets_end == &(*p)->next) {
                _packets_end = p;
            }
            *p = (*p)->next;
            free(q);
        } else {
            p = &(*p)->next;
        }
    }
}

void SPWFSAxx::_free_all_packets() {
    for (int spwf_id = 0; spwf_id < SPWFSA_SOCKET_COUNT; spwf_id++) {
        _free_packets(spwf_id);
    }
}

bool SPWFSAxx::close(int spwf_id)
{
    bool ret = false;

    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* disable calling (external) callback in IRQ context */

    MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT)); // `spwf_id` is valid

    for(int retry_cnt = 0; retry_cnt < SPWFXX_MAX_TRIALS; retry_cnt++) {
        Timer timer;
        timer.start();

        // Flush out pending data
        while(true) {
            int amount = _read_in_pkt(spwf_id, true);
            if(amount < 0) { // SPWFXX error
                /* empty RX buffer & try to close */
                empty_rx_buffer();
                break;
            }
            if(amount == 0) break; // no more data to be read

            /* Try to work around module API bug:
             * break out & try to close after 20 seconds
             */
            if(timer.read() > 20) {
                break;
            }

            /* immediately free packet(s) (to avoid "out of memory") */
            _free_packets(spwf_id);

            /* interleave bottom halves */
            _execute_bottom_halves();
        }

        // Close socket
        if (_parser.send("AT+S.SOCKC=%d", spwf_id)
                && _recv_ok()) {
            ret = true;
            break; // finish closing
        } else { // close failed
            debug_if(_dbg_on, "\r\nSPWF> %s failed (%d)\r\n", __func__, __LINE__);
            /* interleave bottom halves */
            _execute_bottom_halves();

            /* free packets */
            _free_packets(spwf_id);
        }
    }

    /* anticipate bottom halves */
    _execute_bottom_halves();

    if(ret) {
        /* clear pending data flag (should be redundant) */
        _clear_pending_data(spwf_id);

        /* free packets for this socket */
        _free_packets(spwf_id);

        /* reset pending data sizes */
        _reset_pending_pkt_sizes(spwf_id);
    } else {
        debug_if(_dbg_on, "\r\nSPWF> SPWFSAxx::close failed (%d)\r\n", __LINE__);

        int internal_id = _associated_interface.get_internal_id(spwf_id);
        if(!_associated_interface._socket_is_still_connected(internal_id)) {
            /* clear pending data flag (should be redundant) */
            _clear_pending_data(spwf_id);

            /* free packets for this socket */
            _free_packets(spwf_id);

            /* reset pending data sizes */
            _reset_pending_pkt_sizes(spwf_id);

            ret = true;
        }
    }

    return ret;
}

/*
 * Buffered serial event handler
 *
 * Note: executed in IRQ context!
 * Note: do not call (external) callback in IRQ context while performing critical module operations
 */
void SPWFSAxx::_event_handler(void)
{
    if(!_is_event_callback_blocked()) {
        _call_callback();
    }
}

/*
 * Common error handler
 */
void SPWFSAxx::_error_handler(void)
{
    if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) {
        debug_if(_dbg_on, "AT^ ERROR:%s (%d)\r\n", _msg_buffer, __LINE__);
    } else {
        debug_if(_dbg_on, "\r\nSPWF> Unknown ERROR string in SPWFSAxx::_error_handler (%d)\r\n", __LINE__);
    }

    /* force call of (external) callback */
    _call_callback();
}

/*
 * Handling oob ("+WIND:33:WiFi Network Lost")
 */
void SPWFSAxx::_network_lost_handler_th(void)
{
#ifndef NDEBUG
    static unsigned int net_loss_cnt = 0;
    net_loss_cnt++;
#endif

    _recv_delim_cr_lf();

    debug_if(_dbg_on, "AT^ +WIND:33:WiFi Network Lost\r\n");

#ifndef NDEBUG
    debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_th: %d\r\n", net_loss_cnt);
#else // NDEBUG
    debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_th: %d\r\n", __LINE__);
#endif // NDEBUG

    /* set flag to signal network loss */
    _network_lost_flag = true;

    /* force call of (external) callback */
    _call_callback();

    return;
}

/* betzw - WORK AROUND module FW issues: split up big packages in smaller ones */
void SPWFSAxx::_add_pending_packet_sz(int spwf_id, uint32_t size) {
    uint32_t to_add;
    uint32_t added = _get_cumulative_size(spwf_id);

    if(size <= added) { // might happen due to delayed WIND delivery
        debug_if(_dbg_on, "\r\nSPWF> WARNING: %s failed at line #%d\r\n", __func__, __LINE__);
        return;
    }

    for(to_add = ((size - added) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (size - added);
            added < size;
            to_add = ((size - added) > SPWFXX_SEND_RECV_PKTSIZE) ? SPWFXX_SEND_RECV_PKTSIZE : (size - added)) {
        _add_pending_pkt_size(spwf_id, added + to_add);
        added += to_add;
    }

    /* force call of (external) callback */
    _call_callback();

    /* set that data is pending */
    _set_pending_data(spwf_id);
}

/*
 * Handling oob ("+WIND:55:Pending Data")
 */
void SPWFSAxx::_packet_handler_th(void)
{
    int internal_id, spwf_id;
    int amount;

    /* parse out the socket id & amount */
    if (!(_parser.recv(SPWFXX_RECV_PENDING_DATA, &spwf_id, &amount) && _recv_delim_lf())) {
#ifndef NDEBUG
        error("\r\nSPWF> SPWFSAxx::%s failed!\r\n", __func__);
#endif
        return;
    }

    debug_if(_dbg_on, "AT^ +WIND:55:Pending Data:%d:%d\r\n", spwf_id, amount);

    /* check for the module to report a valid id */
    MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT));

    /* set that there is pending data for socket */
    /* NOTE: it seems as if asynchronous indications might report not up-to-date data length values
     *       therefore we just record the socket id without considering the `amount` of data reported!
     */
    internal_id = _associated_interface.get_internal_id(spwf_id);
    if(internal_id != SPWFSA_SOCKET_COUNT) {
        debug_if(_dbg_on, "AT^ +WIND:55:Pending Data:%d:%d - #2\r\n", spwf_id, amount);
        _add_pending_packet_sz(spwf_id, amount);

        MBED_ASSERT(_get_pending_pkt_size(spwf_id) != 0);
    } else {
        debug_if(_dbg_on, "\r\nSPWFSAxx::%s got invalid id %d\r\n", __func__, spwf_id);
    }
}

void SPWFSAxx::_network_lost_handler_bh(void)
{
    if(!_network_lost_flag) return;
    _network_lost_flag = false;

    {
        bool were_connected;
        BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                     Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* do not call (external) callback in IRQ context as long as network is lost */
        Timer timer;
        timer.start();

        _parser.set_timeout(SPWF_NETLOST_TIMEOUT);

        were_connected = isConnected();
        _associated_interface._connected_to_network = false;

        if(were_connected) {
            unsigned int n1, n2, n3, n4;

            while(true) {
                if (timer.read_ms() > SPWF_CONNECT_TIMEOUT) {
                    debug_if(_dbg_on, "\r\nSPWF> SPWFSAxx::_network_lost_handler_bh() #%d\r\n", __LINE__);
                    disconnect();
                    empty_rx_buffer();
                    goto nlh_get_out;
                }

                if((_parser.recv(SPWFXX_RECV_WIFI_UP, &n1, &n2, &n3, &n4)) && _recv_delim_lf()) {
                    debug_if(_dbg_on, "\r\nSPWF> Re-connected (%u.%u.%u.%u)!\r\n", n1, n2, n3, n4);

                    _associated_interface._connected_to_network = true;
                    goto nlh_get_out;
                }
            }
        } else {
            debug_if(_dbg_on, "\r\nSPWF> Leaving SPWFSAxx::_network_lost_handler_bh\r\n");
            goto nlh_get_out;
        }

    nlh_get_out:
        debug_if(_dbg_on, "\r\nSPWF> Getting out of SPWFSAxx::_network_lost_handler_bh\r\n");
        _parser.set_timeout(_timeout);

        /* force call of (external) callback */
        _call_callback();

        return;
    }
}

void SPWFSAxx::_recover_from_hard_faults(void) {
    disconnect();
    empty_rx_buffer();

    /* force call of (external) callback */
    _call_callback();
}

/*
 * Handling oob ("+WIND:8:Hard Fault")
 */
void SPWFSAxx::_hard_fault_handler(void)
{
    _parser.set_timeout(SPWF_RECV_TIMEOUT);
    if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) {
#ifndef NDEBUG
        error("\r\nSPWF> hard fault error:\r\n%s\r\n", _msg_buffer);
#else // NDEBUG
        debug("\r\nSPWF> hard fault error:\r\n%s\r\n", _msg_buffer);
#endif // NDEBUG
    } else {
#ifndef NDEBUG
        error("\r\nSPWF> unknown hard fault error\r\n");
#else // NDEBUG
        debug("\r\nSPWF> unknown hard fault error\r\n");
#endif // NDEBUG
    }

    // This is most likely the best we can do to recover from this module hard fault
    _parser.set_timeout(SPWF_HF_TIMEOUT);
    _recover_from_hard_faults();
    _parser.set_timeout(_timeout);

    /* force call of (external) callback */
    _call_callback();
}

/*
 * Handling oob ("+WIND:5:WiFi Hardware Failure")
 */
void SPWFSAxx::_wifi_hwfault_handler(void)
{
    unsigned int failure_nr;

    /* parse out the socket id & amount */
    _parser.recv(":%u\n", &failure_nr);
    _recv_delim_lf();

#ifndef NDEBUG
    error("\r\nSPWF> WiFi HW fault error: %u\r\n", failure_nr);
#else // NDEBUG
    debug("\r\nSPWF> WiFi HW fault error: %u\r\n", failure_nr);

    // This is most likely the best we can do to recover from this module hard fault
    _parser.set_timeout(SPWF_HF_TIMEOUT);
    _recover_from_hard_faults();
    _parser.set_timeout(_timeout);
#endif // NDEBUG

    /* force call of (external) callback */
    _call_callback();
}

/*
 * Handling oob ("+WIND:58:Socket Closed")
 * when server closes a client connection
 *
 * NOTE: When a socket client receives an indication about socket server gone (only for TCP sockets, WIND:58),
 *       the socket connection is NOT automatically closed!
 */
void SPWFSAxx::_server_gone_handler(void)
{
    int spwf_id, internal_id;

    if(!(_parser.recv(SPWFXX_RECV_SOCKET_CLOSED, &spwf_id) && _recv_delim_lf())) {
#ifndef NDEBUG
        error("\r\nSPWF> SPWFSAxx::%s failed!\r\n", __func__);
#endif
        goto _get_out;
    }

    debug_if(_dbg_on, "AT^ +WIND:58:Socket Closed:%d\r\n", spwf_id);

    /* check for the module to report a valid id */
    MBED_ASSERT(((unsigned int)spwf_id) < ((unsigned int)SPWFSA_SOCKET_COUNT));

    /* only set `server_gone`
     * user still can receive data & must still explicitly close the socket
     */
    internal_id = _associated_interface.get_internal_id(spwf_id);
    if(internal_id != SPWFSA_SOCKET_COUNT) {
        _associated_interface._ids[internal_id].server_gone = true;
    }

_get_out:
    /* force call of (external) callback */
    _call_callback();
}

#if MBED_CONF_IDW0XX1_EXPANSION_BOARD == IDW04A1
/*
 * Handling oob (currently only for "+WIND:24:WiFi Up::")
 */
void SPWFSAxx::_skip_oob(void)
{
    if(_parser.recv("%255[^\n]\n", _msg_buffer) && _recv_delim_lf()) {
        debug_if(_dbg_on, "AT^ +WIND:24:WiFi Up::%s\r\n", _msg_buffer);
    } else {
        debug_if(_dbg_on, "\r\nSPWF> Invalid string in SPWFSAxx::_skip_oob (%d)\r\n", __LINE__);
    }
}
#endif

void SPWFSAxx::setTimeout(uint32_t timeout_ms)
{
    _timeout = timeout_ms;
    _parser.set_timeout(timeout_ms);
}

void SPWFSAxx::attach(Callback<void()> func)
{
    _callback_func = func; /* do not call (external) callback in IRQ context during critical module operations */
}

/**
 *  Recv Function
 */
int32_t SPWFSAxx::recv(int spwf_id, void *data, uint32_t amount, bool datagram)
{
    BlockExecuter bh_handler(Callback<void()>(this, &SPWFSAxx::_execute_bottom_halves));

    while (true) {
        /* check if any packets are ready for us */
        for (struct packet **p = &_packets; *p; p = &(*p)->next) {
            if ((*p)->id == spwf_id) {
                debug_if(_dbg_on, "\r\nSPWF> Read done on ID %d and length of packet is %d\r\n",spwf_id,(*p)->len);
                struct packet *q = *p;

                MBED_ASSERT(q->len > 0);

                if(datagram) { // UDP => always remove pkt size
                    // will always consume a whole pending size
                    uint32_t ret;

                    debug_if(_dbg_on, "\r\nSPWF> %s():\t\t\t%d:%d (datagram)\r\n", __func__, spwf_id, q->len);

                    ret = (amount < q->len) ? amount : q->len;
                    memcpy(data, q+1, ret);

                    if (_packets_end == &(*p)->next) {
                        _packets_end = p;
                    }
                    *p = (*p)->next;
                    free(q);

                    return ret;
                } else { // TCP
                    if (q->len <= amount) { // return and remove full packet
                        memcpy(data, q+1, q->len);

                        if (_packets_end == &(*p)->next) {
                            _packets_end = p;
                        }
                        *p = (*p)->next;
                        uint32_t len = q->len;
                        free(q);

                        return len;
                    } else { // `q->len > amount`, return only partial packet
                        if(amount > 0) {
                            memcpy(data, q+1, amount);
                            q->len -= amount;
                            memmove(q+1, (uint8_t*)(q+1) + amount, q->len);
                        }

                        return amount;
                    }
                }
            }
        }

        /* check for pending data on module */
        {
            int len;

            len = _read_in_pkt(spwf_id, false);
            if(len <= 0)  { /* SPWFXX error or no more data to be read */
                return -1;
            }
        }
    }
}

void SPWFSAxx::_process_winds(void) {
    do {
        if(_parser.process_oob()) {
            /* nothing else to do! */;
        } else {
            debug_if(_dbg_on, "%s():\t\tNo (more) oob's found!\r\n", __func__);
            return; // no (more) oob's found
        }
    } while(true);
}

/* Note: returns
 * '>=0'             in case of success, amount of read in data (in bytes)
 * 'SPWFXX_ERR_OOM'  in case of "out of memory"
 * 'SPWFXX_ERR_READ' in case of other `_read_in_packet()` error
 * 'SPWFXX_ERR_LEN'  in case of `_read_len()` error
 */
int SPWFSAxx::_read_in_pkt(int spwf_id, bool close) {
    int pending;
    uint32_t wind_pending;
    BlockExecuter netsock_wa_obj(Callback<void()>(this, &SPWFSAxx::_unblock_event_callback),
                                 Callback<void()>(this, &SPWFSAxx::_block_event_callback)); /* do not call (external) callback in IRQ context while receiving */

    _process_winds(); // perform async indication handling

    if(close) { // read in all data
        wind_pending = pending = _read_len(spwf_id); // triggers also async indication handling!

        if(pending > 0) {
            /* reset pending data sizes */
            _reset_pending_pkt_sizes(spwf_id);
            /* create new entry for pending size */
            _add_pending_pkt_size(spwf_id, (uint32_t)pending);
#ifndef NDEBUG
            wind_pending = _get_pending_pkt_size(spwf_id);
            MBED_ASSERT(pending == (int)wind_pending);
#endif
        } else if(pending < 0) {
            debug_if(_dbg_on, "\r\nSPWF> %s(), #%d:`_read_len()` failed (%d)!\r\n", __func__, __LINE__, pending);
        }
    } else { // only read in already notified data
        pending = wind_pending = _get_pending_pkt_size(spwf_id);
        if(pending == 0) { // special handling for no packets pending (to WORK AROUND missing WINDs)!
            pending = _read_len(spwf_id); // triggers also async indication handling!

            if(pending > 0) {
                _process_winds(); // perform async indication handling (again)
                wind_pending = _get_pending_pkt_size(spwf_id);

                if(wind_pending == 0) {
                    /* betzw - WORK AROUND module FW issues: create new entry for pending size */
                    debug_if(_dbg_on, "%s():\t\tAdd packet w/o WIND (%d)!\r\n", __func__, pending);
                    _add_pending_packet_sz(spwf_id, (uint32_t)pending);

                    pending = wind_pending = _get_pending_pkt_size(spwf_id);
                    MBED_ASSERT(wind_pending > 0);
                }
            } else if(pending < 0) {
                debug_if(_dbg_on, "\r\nSPWF> %s(), #%d:`_read_len()` failed (%d)!\r\n", __func__, __LINE__, pending);
            }
        }
    }

    if((pending > 0) && (wind_pending > 0)) {
        int ret = _read_in_packet(spwf_id, wind_pending);
        if(ret < 0) { /* "out of memory" or `_read_in_packet()` error */
            /* we do not know if data is still pending at this point
               but leaving the pending data bit set might lead to an endless loop */
            _clear_pending_data(spwf_id);
            /* also reset pending data sizes */
            _reset_pending_pkt_sizes(spwf_id);

            return ret;
        }

        if((_get_cumulative_size(spwf_id) == 0) && (pending <= (int)wind_pending)) {
            _clear_pending_data(spwf_id);
        }
    } else if(pending < 0) { /* 'SPWFXX_ERR_LEN' error */
        MBED_ASSERT(pending == SPWFXX_ERR_LEN);
        /* we do not know if data is still pending at this point
           but leaving the pending data bit set might lead to an endless loop */
        _clear_pending_data(spwf_id);
        /* also reset pending data sizes */
        _reset_pending_pkt_sizes(spwf_id);

        return pending;
    } else if(pending == 0) {
        MBED_ASSERT(wind_pending == 0);
        _clear_pending_data(spwf_id);
    } else if(wind_pending == 0) { // `pending > 0`
        /* betzw: should never happen! */
        MBED_ASSERT(false);
    }

    return (int)wind_pending;
}
