/*
 * Copyright (c) 2018-2019 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 "nsapi.h"
#include "lwipopts.h"
#include "WhdSoftAPInterface.h"
#include "nsapi.h"
#include "lwipopts.h"
#include "lwip/etharp.h"
#include "lwip/ethip6.h"
#include "rtos.h"
#include "whd_emac.h"
#include "whd_wifi_api.h"


extern int whd_toerror(whd_result_t res);
extern nsapi_security_t whd_tosecurity(whd_security_t sec);
extern whd_security_t whd_fromsecurity(nsapi_security_t sec);
extern "C" void whd_emac_wifi_link_state_changed(whd_interface_t ifp, whd_bool_t state_up);

static const whd_event_num_t ap_events[] = { WLC_E_LINK, WLC_E_IF, WLC_E_DEAUTH, WLC_E_DEAUTH_IND, WLC_E_DISASSOC, WLC_E_DISASSOC_IND, WLC_E_ASSOC_IND, WLC_E_REASSOC_IND, WLC_E_NONE };

static void *whd_default_handle_softap_events(whd_interface_t ifp, const whd_event_header_t *event_header,
                                              const uint8_t *event_data, void *handler_user_data)
{
    whd_driver_t whd_driver = ifp->whd_driver;

    UNUSED_PARAMETER(event_header);
    UNUSED_PARAMETER(event_data);
    UNUSED_PARAMETER(handler_user_data);

    WHD_IOCTL_LOG_ADD_EVENT(whd_driver, event_header->event_type, event_header->flags, event_header->reason);

    if ((event_header->event_type == (whd_event_num_t)WLC_E_LINK) ||
            (event_header->event_type == WLC_E_IF)) {
        if (osSemaphoreGetCount(whd_driver->ap_info.whd_wifi_sleep_flag) < 1) {
            osStatus_t result = osSemaphoreRelease(whd_driver->ap_info.whd_wifi_sleep_flag);
            if (result != osOK) {
                printf("Release whd_wifi_sleep_flag ERROR: %d", result);
            }
        }
    }
    return handler_user_data;
}


WhdSoftAPInterface::WhdSoftAPInterface(WHD_EMAC &emac, OnboardNetworkStack &stack, whd_interface_shared_info_t &shared)
    : EMACInterface(emac, stack),
      _whd_emac(emac),
      _iface_shared(shared)
{

}


int WhdSoftAPInterface::start(const char *ssid, const char *pass, nsapi_security_t security, uint8_t channel,
                              bool start_dhcp_server, const whd_custom_ie_info_t *ie_info, bool ap_sta_concur)
{
    ScopedMutexLock lock(_iface_shared.mutex);

    nsapi_error_t        err;
    // power up primary emac interface first
    if (ap_sta_concur) {
        WHD_EMAC &emac_prime = WHD_EMAC::get_instance(WHD_STA_ROLE);
        if (!emac_prime.power_up()) {
            printf("Primary interface power up ERROR!\n");
            return NSAPI_ERROR_DEVICE_ERROR;
        }
    }

    // set concurrency mode and power up secondary, the bsp init is done by primary emac
    _whd_emac.ap_sta_concur = ap_sta_concur;
    if (!_whd_emac.power_up()) {
        printf("Secondary interface power up ERROR!\n");
        return NSAPI_ERROR_DEVICE_ERROR;
    }

    // setup ssid
    whd_ssid_t whd_ssid;
    strncpy((char *)whd_ssid.value, ssid, SSID_NAME_SIZE);
    whd_ssid.value[SSID_NAME_SIZE - 1] = '\0';
    whd_ssid.length = strlen((char *)whd_ssid.value);

    // choose network security
    whd_security_t whd_security = whd_fromsecurity(security);

    /* set up the AP info */
    err = whd_wifi_init_ap(_whd_emac.ifp, &whd_ssid, whd_security, (const uint8_t *)pass,
                           strlen(pass), channel);
    if (err != NSAPI_ERROR_OK) {
        printf("whd_wifi_init_ap() ERROR: %d\n", err);
        return err;
    }

    // update default softap interface event handler
    err = unregister_event_handler();
    if (err != NSAPI_ERROR_OK) {
        printf("unregister_event_handler() ERROR: %d\n", err);
        return err;
    }
    err = register_event_handler(whd_default_handle_softap_events);
    if (err != NSAPI_ERROR_OK) {
        printf("register_event_handler() ERROR: %d\n", err);
        return err;
    }

    _iface_shared.if_status_flags |= IF_STATUS_SOFT_AP_UP;
    if (!ap_sta_concur || (_iface_shared.default_if_cfg == DEFAULT_IF_NOT_SET)) {
        _iface_shared.default_if_cfg = DEFAULT_IF_SOFT_AP;
    }
    if (!_interface) {
        nsapi_error_t err = _stack.add_ethernet_interface(_whd_emac, _iface_shared.default_if_cfg == DEFAULT_IF_SOFT_AP, &_interface);
        if (err != NSAPI_ERROR_OK) {
            _interface = NULL;
            return err;
        }
        _interface->attach(_connection_status_cb);
        _iface_shared.iface_softap = _interface;
    } else if (_iface_shared.default_if_cfg == DEFAULT_IF_SOFT_AP) {
        _stack.set_default_interface(_interface);
    }

    if (ie_info) {
        err = whd_wifi_manage_custom_ie(_whd_emac.ifp, WHD_ADD_CUSTOM_IE, (const uint8_t *)ie_info->oui,
                                        ie_info->subtype, (const void *)ie_info->data, ie_info->length, ie_info->which_packets);
        if (err != NSAPI_ERROR_OK) {
            printf("whd_wifi_manage_custom_ie() ERROR: %d\n", err);
            return err;
        }
    }

    err = whd_wifi_start_ap(_whd_emac.ifp);
    if (err != NSAPI_ERROR_OK) {
        printf("whd_wifi_start_ap() ERROR: %d\n", err);
        return err;
    }

    // Set static IP address for SoftAP and bring up
    set_dhcp(false);

    if (whd_wifi_is_ready_to_transceive(_whd_emac.ifp) == WHD_SUCCESS) {
        whd_emac_wifi_link_state_changed(_whd_emac.ifp, WHD_TRUE);
    }

    err = _interface->bringup(_dhcp,
                              _ip_address[0] ? _ip_address : 0,
                              _netmask[0] ? _netmask : 0,
                              _gateway[0] ? _gateway : 0,
                              DEFAULT_STACK);
    if (err != NSAPI_ERROR_OK) {
        printf("bringup() ERROR: %d\n", err);
    }

    if (start_dhcp_server) {
        _dhcp_server = std::make_unique<CyDhcpServer>(get_stack(), this);
        if (CY_RSLT_SUCCESS != _dhcp_server->start()) {
            err = NSAPI_ERROR_DHCP_FAILURE;
        }
    }
    return err;
}



int WhdSoftAPInterface::stop(void)
{
    ScopedMutexLock lock(_iface_shared.mutex);

    if (_dhcp_server && CY_RSLT_SUCCESS != _dhcp_server->stop()) {
        return NSAPI_ERROR_DHCP_FAILURE;
    }
    _dhcp_server.reset();

    // bring down
    int err = _interface->bringdown();
    if (err) {
        return err;
    }

    _iface_shared.if_status_flags &= ~IF_STATUS_SOFT_AP_UP;
    if ((_iface_shared.if_status_flags & IF_STATUS_STA_UP) == 0) {
        _iface_shared.default_if_cfg = DEFAULT_IF_NOT_SET;
    }

    // stop softap
    whd_result_t res = whd_wifi_stop_ap(_whd_emac.ifp);
    if (res != WHD_SUCCESS) {
        return whd_toerror(res);
    }
    return NSAPI_ERROR_OK;
}

int WhdSoftAPInterface::remove_custom_ie(const whd_custom_ie_info_t *ie_info)
{
    return whd_wifi_manage_custom_ie(_whd_emac.ifp, WHD_REMOVE_CUSTOM_IE, (const uint8_t *)ie_info->oui,
                                     ie_info->subtype, (const void *)ie_info->data, ie_info->length, ie_info->which_packets);
}

int WhdSoftAPInterface::get_associated_client_list(void *client_list_buffer, uint16_t buffer_length)
{

    return whd_wifi_get_associated_client_list(_whd_emac.ifp, client_list_buffer, buffer_length);
}

int WhdSoftAPInterface::register_event_handler(whd_event_handler_t softap_event_handler)
{
    uint16_t ap_events_entry = _whd_emac.ifp->event_reg_list[WHD_AP_EVENT_ENTRY];
    return whd_management_set_event_handler(_whd_emac.ifp, ap_events, softap_event_handler, NULL, &ap_events_entry);
}

int WhdSoftAPInterface::unregister_event_handler(void)
{
    return whd_wifi_deregister_event_handler(_whd_emac.ifp, _whd_emac.ifp->event_reg_list[WHD_AP_EVENT_ENTRY]);
}
