/* mbed Microcontroller Library
 * Copyright (c) 20015 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.
 */

/**
  ******************************************************************************
  * @file    SpwfSAInterface.cpp
  * @author  STMicroelectronics
  * @brief   Implementation of the NetworkStack for the SPWF Device
  ******************************************************************************
  * @copy
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * <h2><center>&copy; COPYRIGHT 2016 STMicroelectronics</center></h2>
  ******************************************************************************
  */

#include "SpwfSAInterface.h"
#include "mbed_debug.h"
#include "BlockExecuter.h"

#if MBED_CONF_RTOS_PRESENT
#define SYNC_HANDLER ScopedMutexLock sync_handler(_spwf_mutex)  // assuming a recursive mutex
#else
#define SYNC_HANDLER
#endif


SpwfSAInterface::SpwfSAInterface(PinName tx, PinName rx,
                                 PinName rts, PinName cts, bool debug,
                                 PinName wakeup, PinName reset)
: _spwf(tx, rx, rts, cts, *this, debug, wakeup, reset),
  _dbg_on(debug)
{
    inner_constructor();
    reset_credentials();
}

nsapi_error_t SpwfSAInterface::init(void)
{
    _spwf.setTimeout(SPWF_INIT_TIMEOUT);

    if(_spwf.startup(0)) {
        return NSAPI_ERROR_OK;
    }
    else return NSAPI_ERROR_DEVICE_ERROR;
}

nsapi_error_t SpwfSAInterface::connect(void)
{
    int mode;
    char *pass_phrase = ap_pass;
    SYNC_HANDLER;

    // check for valid SSID
    if(ap_ssid[0] == '\0') {
        return NSAPI_ERROR_PARAMETER;
    }

    switch(ap_sec)
    {
        case NSAPI_SECURITY_NONE:
            mode = 0;
            pass_phrase = NULL;
            break;
        case NSAPI_SECURITY_WEP:
            mode = 1;
            break;
        case NSAPI_SECURITY_WPA:
        case NSAPI_SECURITY_WPA2:
            mode = 2;
            break;
        default:
            mode = 2;
            break;
    }

    // First: disconnect
    if(_connected_to_network) {
        if(!disconnect()) {
            return NSAPI_ERROR_DEVICE_ERROR;
        }
    }

    //initialize the device before connecting
    if(!_isInitialized)
    {
        if(init() != NSAPI_ERROR_OK) return NSAPI_ERROR_DEVICE_ERROR;
        _isInitialized=true;
    }

    // Then: (re-)connect
    _spwf.setTimeout(SPWF_CONNECT_TIMEOUT);

    if (!_spwf.connect(ap_ssid, pass_phrase, mode)) {
        return NSAPI_ERROR_AUTH_FAILURE;
    }

    if (!_spwf.getIPAddress()) {
        return NSAPI_ERROR_DHCP_FAILURE;
    }

    _connected_to_network = true;
    return NSAPI_ERROR_OK;
}

nsapi_error_t SpwfSAInterface::connect(const char *ssid, const char *pass, nsapi_security_t security,
                                       uint8_t channel)
{
    nsapi_error_t ret;
    SYNC_HANDLER;

    if (channel != 0) {
        return NSAPI_ERROR_UNSUPPORTED;
    }

    ret = set_credentials(ssid, pass, security);
    if(ret != NSAPI_ERROR_OK) return ret;

    return connect();
}

nsapi_error_t SpwfSAInterface::disconnect(void)
{
    SYNC_HANDLER;

    _spwf.setTimeout(SPWF_DISCONNECT_TIMEOUT);

    if (!_spwf.disconnect()) {
        return NSAPI_ERROR_DEVICE_ERROR;
    }

    return NSAPI_ERROR_OK;
}

const char *SpwfSAInterface::get_ip_address(void)
{
    SYNC_HANDLER;

    _spwf.setTimeout(SPWF_MISC_TIMEOUT);
    return _spwf.getIPAddress();
}

const char *SpwfSAInterface::get_mac_address(void)
{
    SYNC_HANDLER;

    _spwf.setTimeout(SPWF_MISC_TIMEOUT);
    return _spwf.getMACAddress();
}

const char *SpwfSAInterface::get_gateway(void)
{
    SYNC_HANDLER;

    if(!_connected_to_network) return NULL;

    _spwf.setTimeout(SPWF_MISC_TIMEOUT);
    return _spwf.getGateway();
}

const char *SpwfSAInterface::get_netmask(void)
{
    SYNC_HANDLER;

    if(!_connected_to_network) return NULL;

    _spwf.setTimeout(SPWF_MISC_TIMEOUT);
    return _spwf.getNetmask();
}

nsapi_error_t SpwfSAInterface::socket_open(void **handle, nsapi_protocol_t proto)
{
    int internal_id;
    SYNC_HANDLER;

    for (internal_id = 0; internal_id < SPWFSA_SOCKET_COUNT; internal_id++) {
        if(_ids[internal_id].internal_id == SPWFSA_SOCKET_COUNT) break;
    }

    if(internal_id == SPWFSA_SOCKET_COUNT) {
        debug_if(_dbg_on, "NO Socket ID Error\r\n");
        return NSAPI_ERROR_NO_SOCKET;
    }

    spwf_socket_t *socket = &_ids[internal_id];
    socket->internal_id = internal_id;
    socket->spwf_id = SPWFSA_SOCKET_COUNT;
    socket->server_gone = false;
    socket->no_more_data = false;
    socket->proto = proto;
    socket->addr = SocketAddress();

    *handle = socket;
    return NSAPI_ERROR_OK;
}

nsapi_error_t SpwfSAInterface::socket_connect(void *handle, const SocketAddress &addr)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    SYNC_HANDLER;

    MBED_ASSERT(((unsigned int)socket->internal_id) < ((unsigned int)SPWFSA_SOCKET_COUNT));

    if(_socket_has_connected(socket->internal_id)) {
        return NSAPI_ERROR_IS_CONNECTED;
    }

    _spwf.setTimeout(SPWF_OPEN_TIMEOUT);

    const char *proto = (socket->proto == NSAPI_UDP) ? "u" : "t"; //"s" for secure socket?

    if(addr.get_ip_version() != NSAPI_IPv4) { // IPv6 not supported (yet)
        return NSAPI_ERROR_UNSUPPORTED;
    }

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

        /* block asynchronous indications */
        if(!_spwf._winds_off()) {
            return NSAPI_ERROR_DEVICE_ERROR;
        }

        {
            BlockExecuter bh_handler(Callback<void()>(&_spwf, &SPWFSAxx::_execute_bottom_halves));
            {
                BlockExecuter winds_enabler(Callback<void()>(&_spwf, &SPWFSAxx::_winds_on));

                if(!_spwf.open(proto, &socket->spwf_id, addr.get_ip_address(), addr.get_port())) {
                    MBED_ASSERT(_spwf._call_event_callback_blocked == 1);

                    return NSAPI_ERROR_DEVICE_ERROR;
                }

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

                _internal_ids[socket->spwf_id] = socket->internal_id;
                socket->addr = addr;

                MBED_ASSERT(_spwf._call_event_callback_blocked == 1);

                return NSAPI_ERROR_OK;
            }
        }
    }
}

nsapi_error_t SpwfSAInterface::socket_bind(void *handle, const SocketAddress &address)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

nsapi_error_t SpwfSAInterface::socket_listen(void *handle, int backlog)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

nsapi_error_t SpwfSAInterface::socket_accept(nsapi_socket_t server, nsapi_socket_t *handle, SocketAddress *address)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

nsapi_error_t SpwfSAInterface::socket_close(void *handle)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    int internal_id = socket->internal_id;
    SYNC_HANDLER;

    if(!_socket_is_open(internal_id)) return NSAPI_ERROR_NO_SOCKET;

    if(_socket_has_connected(socket)) {
        _spwf.setTimeout(SPWF_CLOSE_TIMEOUT);
        if (!_spwf.close(socket->spwf_id)) {
            return NSAPI_ERROR_DEVICE_ERROR;
        }
        _internal_ids[socket->spwf_id] = SPWFSA_SOCKET_COUNT;
    }

    _ids[internal_id].internal_id = SPWFSA_SOCKET_COUNT;
    _ids[internal_id].spwf_id = SPWFSA_SOCKET_COUNT;

    return NSAPI_ERROR_OK;
}

nsapi_size_or_error_t SpwfSAInterface::socket_send(void *handle, const void *data, unsigned size)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    SYNC_HANDLER;

    CHECK_NOT_CONNECTED_ERR();

    _spwf.setTimeout(SPWF_SEND_TIMEOUT);
    return _spwf.send(socket->spwf_id, data, size, socket->internal_id);
}

nsapi_size_or_error_t SpwfSAInterface::socket_recv(void *handle, void *data, unsigned size)
{
    SYNC_HANDLER;

    return _socket_recv(handle, data, size, false);
}

nsapi_size_or_error_t SpwfSAInterface::_socket_recv(void *handle, void *data, unsigned size, bool datagram)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;

    CHECK_NOT_CONNECTED_ERR();

    if(!_socket_has_connected(socket)) {
        return NSAPI_ERROR_WOULD_BLOCK;
    } else if(socket->no_more_data) {
        return 0;
    }

    _spwf.setTimeout(SPWF_RECV_TIMEOUT);

    int32_t recv = _spwf.recv(socket->spwf_id, (char*)data, (uint32_t)size, datagram);

    MBED_ASSERT(!_spwf._is_event_callback_blocked());
    MBED_ASSERT((recv != 0) || (size == 0));

    if (recv < 0) {
        if(!_socket_is_still_connected(socket)) {
            socket->no_more_data = true;
            return 0;
        }

        return NSAPI_ERROR_WOULD_BLOCK;
    }

    return recv;
}

nsapi_size_or_error_t SpwfSAInterface::socket_sendto(void *handle, const SocketAddress &addr, const void *data, unsigned size)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    SYNC_HANDLER;

    CHECK_NOT_CONNECTED_ERR();

    if ((_socket_has_connected(socket)) && (socket->addr != addr)) {
        _spwf.setTimeout(SPWF_CLOSE_TIMEOUT);
        if (!_spwf.close(socket->spwf_id)) {
            return NSAPI_ERROR_DEVICE_ERROR;
        }
        _internal_ids[socket->spwf_id] = SPWFSA_SOCKET_COUNT;
        socket->spwf_id = SPWFSA_SOCKET_COUNT;
    }

    _spwf.setTimeout(SPWF_CONN_SND_TIMEOUT);
    if (!_socket_has_connected(socket)) {
        nsapi_error_t err = socket_connect(socket, addr);
        if (err < 0) {
            return err;
        }
    }

    return socket_send(socket, data, size);
}

nsapi_size_or_error_t SpwfSAInterface::socket_recvfrom(void *handle, SocketAddress *addr, void *data, unsigned size)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    nsapi_error_t ret;
    SYNC_HANDLER;

    ret = _socket_recv(socket, data, size, true);
    if (ret >= 0 && addr) {
        *addr = socket->addr;
    }

    return ret;
}

void SpwfSAInterface::socket_attach(void *handle, void (*callback)(void *), void *data)
{
    spwf_socket_t *socket = (spwf_socket_t*)handle;
    SYNC_HANDLER;

    if(!_socket_is_open(socket)) return; // might happen e.g. after module hard fault or voluntary disconnection

    _cbs[socket->internal_id].callback = callback;
    _cbs[socket->internal_id].data = data;
}

void SpwfSAInterface::event(void) {
    for (int internal_id = 0; internal_id < SPWFSA_SOCKET_COUNT; internal_id++) {
        if (_cbs[internal_id].callback && (_ids[internal_id].internal_id != SPWFSA_SOCKET_COUNT)) {
            _cbs[internal_id].callback(_cbs[internal_id].data);
        }
    }
}

nsapi_error_t SpwfSAInterface::set_credentials(const char *ssid, const char *pass, nsapi_security_t security)
{
    SYNC_HANDLER;

    if((ssid == NULL) || (strlen(ssid) == 0)) {
        return NSAPI_ERROR_PARAMETER;
    }

    if((pass != NULL) && (strlen(pass) > 0)) {
        if(strlen(pass) < sizeof(ap_pass)) {
            if(security == NSAPI_SECURITY_NONE) {
                return NSAPI_ERROR_PARAMETER;
            }
        } else {
            return NSAPI_ERROR_PARAMETER;
        }
    } else if(security != NSAPI_SECURITY_NONE) {
        return NSAPI_ERROR_PARAMETER;
    }

    reset_credentials();

    ap_sec = security;
    strncpy(ap_ssid, ssid, sizeof(ap_ssid) - 1);
    strncpy(ap_pass, pass, sizeof(ap_pass) - 1);

    return NSAPI_ERROR_OK;
}

nsapi_error_t SpwfSAInterface::set_channel(uint8_t channel)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

int8_t SpwfSAInterface::get_rssi(void)
{
    SYNC_HANDLER;

    if(!_connected_to_network) return 0;

    _spwf.setTimeout(SPWF_MISC_TIMEOUT);
    return _spwf.getRssi();
}

nsapi_size_or_error_t SpwfSAInterface::scan(WiFiAccessPoint *res, unsigned count)
{
    SYNC_HANDLER;

    nsapi_size_or_error_t ret;

    //initialize the device before scanning
    if(!_isInitialized)
    {
        if(init() != NSAPI_ERROR_OK) return NSAPI_ERROR_DEVICE_ERROR;
    }

    _spwf.setTimeout(SPWF_SCAN_TIMEOUT);

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

        /* block asynchronous indications */
        if(!_spwf._winds_off()) {
            MBED_ASSERT(_spwf._call_event_callback_blocked == 1);

            return NSAPI_ERROR_DEVICE_ERROR;
        }

        ret = _spwf.scan(res, count);

        /* unblock asynchronous indications */
        _spwf._winds_on();
    }

    MBED_ASSERT(!_spwf._is_event_callback_blocked());

    //de-initialize the device after scanning
    if(!_isInitialized)
    {
        nsapi_error_t err = disconnect();
        if(err != NSAPI_ERROR_OK) return err;
    }

    return ret;
}
