/*
 * Copyright (c) 2014-2018, Arm Limited and affiliates.
 * 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.
 */

/*
 * \file mac_data_poll.c
 * \brief Add short description about this file!!!
 *
 */

#include "nsconfig.h"
#include "ns_types.h"
#include "eventOS_event.h"
#include "eventOS_scheduler.h"
#include "eventOS_callback_timer.h"
#include "string.h"
#include "ns_trace.h"
#include "nsdynmemLIB.h"
#include "mac_api.h"
#include "mlme.h"

#include "NWK_INTERFACE/Include/protocol_stats.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "platform/arm_hal_interrupt.h"
#include "common_functions.h"
#include "6LoWPAN/Bootstraps/protocol_6lowpan.h"
#include "6LoWPAN/Bootstraps/network_lib.h"
#include "6LoWPAN/Thread/thread_common.h"
#include "6LoWPAN/Thread/thread_bootstrap.h"
#ifndef NO_MLE
#include "MLE/mle.h"
#endif
#include "6LoWPAN/MAC/mac_helper.h"
#include "6LoWPAN/MAC/mac_data_poll.h"
#include "Service_Libs/mac_neighbor_table/mac_neighbor_table.h"

#define TRACE_GROUP "mPol"

#define MAC_DATA_POLL_DEFAULT_RATE 20

#define MAC_DATA_POLL_REQUEST_EVENT 1

static int8_t mac_data_poll_tasklet = -1;

static void mac_data_poll_cb_run(int8_t interface_id);


static void mac_data_poll_cb(arm_event_s *event)
{
    uint8_t event_type = event->event_type;

    switch (event_type) {
        case MAC_DATA_POLL_REQUEST_EVENT:
            mac_data_poll_cb_run((int8_t)event->event_id);
            break;

        default:

            break;
    }
}

static int8_t mac_data_poll_tasklet_init(void)
{
    if (mac_data_poll_tasklet < 0) {
        mac_data_poll_tasklet = eventOS_event_handler_create(&mac_data_poll_cb, 0);
    }

    return mac_data_poll_tasklet;
}

static int8_t host_link_configuration(bool rx_on_idle, protocol_interface_info_entry_t *cur)
{
    bool backUp_bool = cur->mac_parameters->RxOnWhenIdle;
    if (rx_on_idle) {
        cur->lowpan_info &= ~INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE;
    } else {
        tr_debug("Enable Poll state by host Link");
        cur->lowpan_info |= INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE;
    }
    mac_helper_pib_boolean_set(cur, macRxOnWhenIdle, rx_on_idle);

    if (thread_info(cur)) {
        if(rx_on_idle != backUp_bool){
            //If mode have been changed, send child update
            thread_bootstrap_child_update_trig(cur);
        }
        return 0;
    } else {
        if (protocol_6lowpan_child_update(cur) == 0) {
            mac_data_poll_init_protocol_poll(cur);
            return 0;
        }
    }

    //Swap back If Send Fail
    if (!backUp_bool) {
        cur->lowpan_info |= INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE;
    } else {
        cur->lowpan_info &= ~INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE;
    }
    mac_helper_pib_boolean_set(cur, macRxOnWhenIdle, backUp_bool);
    return -1;
}

static void mac_data_poll_protocol_internal(protocol_interface_info_entry_t *cur)
{
    if (cur->rfd_poll_info->protocol_poll == 0) {
        if (cur->rfd_poll_info->host_mode == NET_HOST_SLOW_POLL_MODE) {
            if (!cur->rfd_poll_info->pollActive) {
                mac_poll_timer_trig(1, cur);
            }
        }
    }
    cur->rfd_poll_info->protocol_poll++;
    tr_debug("Protocol Poll: %02x", cur->rfd_poll_info->protocol_poll);
}

static void mac_data_poll_protocol_poll_internal_cancel(struct protocol_interface_info_entry *cur)
{
    nwk_rfd_poll_setups_s *rf_ptr = cur->rfd_poll_info;

    if (rf_ptr->protocol_poll == 0) {
        return;
    }

    if (--rf_ptr->protocol_poll == 0) {
        tr_debug("Disable Protocol Poll");
        if (!rf_ptr->pollActive) {
            if (rf_ptr->nwk_app_poll_time) {
                mac_poll_timer_trig(1, cur);
            } else {
                eventOS_event_timer_cancel(cur->id, mac_data_poll_tasklet);
                tr_debug("Stop Poll");
            }
        }
    }
}

void mac_data_poll_init_protocol_poll(protocol_interface_info_entry_t *cur)
{
    if (!cur || !cur->rfd_poll_info || !(cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE)) {
        return;
    }

    mac_data_poll_protocol_internal(cur);

}

uint32_t mac_data_poll_host_poll_time_max(protocol_interface_info_entry_t *cur)
{
    if (!cur || !cur->rfd_poll_info) {
        return 0;
    }

    return cur->rfd_poll_info->slow_poll_rate_seconds;
}

uint32_t mac_data_poll_host_timeout(protocol_interface_info_entry_t *cur)
{
    if (!cur || !cur->rfd_poll_info) {
        return 0;
    }

    return cur->rfd_poll_info->timeOutInSeconds;
}

uint32_t mac_data_poll_get_max_sleep_period(protocol_interface_info_entry_t *cur)
{
    if (cur && cur->if_stack_buffer_handler && (cur->rfd_poll_info && !cur->rfd_poll_info->pollActive)) { // Verify That TX is not active
        if (cur->rfd_poll_info->protocol_poll) {
            return 300;
        } else {
            return cur->rfd_poll_info->nwk_app_poll_time;
        }
    }
    return 0;
}

void mac_data_poll_protocol_poll_mode_decrement(struct protocol_interface_info_entry *cur)
{
    if (!cur || !cur->rfd_poll_info) {
        return;
    }

    if (!(cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE)) {
        return;
    }

    mac_data_poll_protocol_poll_internal_cancel(cur);
}

void mac_data_poll_protocol_poll_mode_disable(struct protocol_interface_info_entry *cur)
{
    if (!cur || !cur->rfd_poll_info) {
        return;
    }

    if (!(cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE)) {
        return;
    }

    mac_data_poll_protocol_poll_internal_cancel(cur);
}

void mac_data_poll_disable(struct protocol_interface_info_entry *cur)
{
    if (!cur || !cur->rfd_poll_info) {
        return;
    }

    eventOS_event_timer_cancel(cur->id, mac_data_poll_tasklet);
    cur->rfd_poll_info->protocol_poll = 0;
    cur->rfd_poll_info->pollActive = false;
}

void mac_data_poll_enable_check(protocol_interface_info_entry_t *cur)
{
    if (!cur || !cur->rfd_poll_info ) {
        return;
    }

    if (!(cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE)) {
        return;
    }

    mac_data_poll_protocol_internal(cur);
}

int8_t mac_data_poll_host_mode_get(struct protocol_interface_info_entry *cur, net_host_mode_t *mode)
{
    if (!cur || !cur->rfd_poll_info) {
        return -1;
    }

    *mode = cur->rfd_poll_info->host_mode;
    return 0;
}

void mac_poll_timer_trig(uint32_t poll_time, protocol_interface_info_entry_t *cur)
{
    if( !cur ){
        return;
    }
    eventOS_event_timer_cancel(cur->id, mac_data_poll_tasklet);

    if (poll_time) {
        if (cur->lowpan_info & INTERFACE_NWK_CONF_MAC_RX_OFF_IDLE) {

            if (poll_time < 20) {
                arm_event_s event = {
                        .receiver = mac_data_poll_tasklet,
                        .sender = mac_data_poll_tasklet,
                        .event_id = cur->id,
                        .data_ptr = NULL,
                        .event_type = MAC_DATA_POLL_REQUEST_EVENT,
                        .priority = ARM_LIB_MED_PRIORITY_EVENT,
                };

                if (eventOS_event_send(&event) != 0) {
                    tr_error("eventOS_event_send() failed");
                }
            } else {
                if (eventOS_event_timer_request(cur->id, MAC_DATA_POLL_REQUEST_EVENT, mac_data_poll_tasklet, poll_time)!= 0) {
                    tr_error("Poll Timer start Fail");
                }
            }

        }
    }
}
static mac_neighbor_table_entry_t *neighbor_data_poll_referesh(protocol_interface_info_entry_t *cur, uint8_t *address, addrtype_t type)
{
    mac_neighbor_table_entry_t *entry = mac_neighbor_table_address_discover(mac_neighbor_info(cur), address, type);

    if (!entry) {
        return NULL;
    }

    if (!entry->connected_device) {
        return NULL;
    }

    if (!entry->nud_active) {
        entry->lifetime = entry->link_lifetime;
    }
    return entry;
}

void mac_mlme_poll_confirm(protocol_interface_info_entry_t *cur, const mlme_poll_conf_t *confirm)
{
    if( !cur || !confirm ){
        return;
    }

    uint32_t poll_time = 1;
    nwk_rfd_poll_setups_s *rf_ptr = cur->rfd_poll_info;
    if( !rf_ptr ){
        return;
    }

    rf_ptr->pollActive = false;
    mac_neighbor_table_entry_t *entry = NULL;

    switch (confirm->status) {
        case MLME_SUCCESS:
            //tr_debug("Poll Confirm: Data with Data");
            rf_ptr->nwk_parent_poll_fail = 0;
            //Trig new Data Poll immediately
            entry = neighbor_data_poll_referesh(cur, rf_ptr->poll_req.CoordAddress, (addrtype_t)rf_ptr->poll_req.CoordAddrMode);
            poll_time = 1;
            break;

        case MLME_NO_DATA:
            //Start next case timer
            rf_ptr->nwk_parent_poll_fail = 0;
            entry = neighbor_data_poll_referesh(cur, rf_ptr->poll_req.CoordAddress, (addrtype_t)rf_ptr->poll_req.CoordAddrMode);
            //tr_debug("Poll Confirm: No Data");

            if (rf_ptr->protocol_poll == 0) {
                poll_time = rf_ptr->nwk_app_poll_time;
            } else {
                poll_time = 300;
            }

            break;
        default:
            tr_debug("Poll Confirm: fail %x", confirm->status);
            rf_ptr->nwk_parent_poll_fail++;
            if (rf_ptr->nwk_parent_poll_fail  > 3) {
                //tr_debug("Parent Poll Fail");
                poll_time = 0;
                rf_ptr->nwk_parent_poll_fail = 0;
                if( rf_ptr->pollFailCb ){
                    rf_ptr->pollFailCb(cur->id);
                }
            } else {
                poll_time = 2000;
            }
            break;
    }
    if (thread_info(cur) && entry) {
        thread_neighbor_communication_update(cur, entry->index);
    }

    mac_poll_timer_trig(poll_time, cur);


}


static void mac_data_poll_cb_run(int8_t interface_id)
{
    protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id);
    if (!cur) {
        return;
    }
    nwk_rfd_poll_setups_s *rf_ptr = cur->rfd_poll_info;

    if (!rf_ptr) {
        return;
    }

    rf_ptr->poll_req.CoordAddrMode = (unsigned)mac_helper_coordinator_address_get(cur, rf_ptr->poll_req.CoordAddress);

    if (rf_ptr->poll_req.CoordAddrMode == MAC_ADDR_MODE_NONE) {
        return;
    }

    memset(&rf_ptr->poll_req.Key, 0, sizeof(mlme_security_t));
    rf_ptr->poll_req.CoordPANId = mac_helper_panid_get(cur);

    rf_ptr->poll_req.Key.SecurityLevel = mac_helper_default_security_level_get(cur);
    if (rf_ptr->poll_req.Key.SecurityLevel) {

        rf_ptr->poll_req.Key.KeyIndex = mac_helper_default_key_index_get(cur);
        rf_ptr->poll_req.Key.KeyIdMode = mac_helper_default_security_key_id_mode_get(cur);
    }

    if (cur->mac_api && cur->mac_api->mlme_req) {
        rf_ptr->pollActive = true;
        cur->mac_api->mlme_req(cur->mac_api, MLME_POLL, (void*) &rf_ptr->poll_req);
    } else {
        tr_error("MAC not registered to interface");
    }
}

int8_t mac_data_poll_host_mode_set(struct protocol_interface_info_entry *cur, net_host_mode_t mode, uint32_t poll_time)
{
#ifndef NO_MLE
    if( !cur){
        return -1;
    }
    int8_t ret_val = 0;
    nwk_rfd_poll_setups_s *rf_ptr = cur->rfd_poll_info;
    //Check IF Bootsrap Ready and type is Host
    if (!rf_ptr ) {
        return -1;
    }

    if (cur->lowpan_info & INTERFACE_NWK_ROUTER_DEVICE) {
        return -3;
    }


    switch (mode) {
        case NET_HOST_SLOW_POLL_MODE:
            //slow mode
            //Check Host current sleep state
            if (rf_ptr->host_mode == NET_HOST_FAST_POLL_MODE || rf_ptr->host_mode == NET_HOST_SLOW_POLL_MODE || rf_ptr->host_mode == NET_HOST_RX_ON_IDLE) {
                uint32_t new_poll_time;
                uint32_t new_Timeout;

                if (poll_time == 0 || poll_time > 864001) {
                    return -2;
                }

                //Calculate
                //Timeout from Poll Time
                new_Timeout = poll_time * 4;

                if (new_Timeout < 32) { //Keep Timeout always to 32 seconds min
                    new_Timeout = 32;
                }

                if (new_Timeout != rf_ptr->timeOutInSeconds) {
                    rf_ptr->timeOutInSeconds = new_Timeout;
                }

                rf_ptr->slow_poll_rate_seconds = poll_time;

                new_poll_time = (poll_time * 1000);
                if (rf_ptr->host_mode == NET_HOST_RX_ON_IDLE) {
                    tr_debug("Init Poll timer and period");
                }

                rf_ptr->nwk_app_poll_time = new_poll_time;
                tr_debug("Enable Poll state slow poll");
                if (host_link_configuration(false, cur) != 0) {
                    return -3;
                }
                mac_poll_timer_trig(1, cur);
                rf_ptr->host_mode = NET_HOST_SLOW_POLL_MODE;
            }
            break;
        case NET_HOST_FAST_POLL_MODE:
            //fast mode
            //Check Host current sleep state
            if (rf_ptr->host_mode == NET_HOST_FAST_POLL_MODE || rf_ptr->host_mode == NET_HOST_SLOW_POLL_MODE || rf_ptr->host_mode == NET_HOST_RX_ON_IDLE) {
                if (rf_ptr->host_mode != NET_HOST_FAST_POLL_MODE) {
                    tr_debug("Enable Fast poll mode");
                    if (rf_ptr->host_mode == NET_HOST_RX_ON_IDLE) {
                        tr_debug("Init Poll timer and period");
                        if (host_link_configuration(false, cur) != 0) {
                            return  -3;
                        }
                    }
                }
                tr_debug("Enable Poll By APP");
                mac_helper_pib_boolean_set(cur, macRxOnWhenIdle, false);
                mac_poll_timer_trig(1, cur);
                rf_ptr->nwk_app_poll_time = 300;
                rf_ptr->host_mode = NET_HOST_FAST_POLL_MODE;

            }
            break;

        case NET_HOST_RX_ON_IDLE:
            // Non-sleep mode
            if (rf_ptr->host_mode == NET_HOST_FAST_POLL_MODE || rf_ptr->host_mode == NET_HOST_SLOW_POLL_MODE) {
                if (host_link_configuration(true, cur) == 0) {
                    rf_ptr->host_mode = NET_HOST_RX_ON_IDLE;
                    rf_ptr->nwk_app_poll_time = 0;
                } else {
                    ret_val = -3;
                }
            }
            break;

        default:
            ret_val = -1;
            break;
    }

    return ret_val;
#else /* MLE_DRAFT */
    return -1;
#endif /* MLE_DRAFT */
}

void mac_data_poll_init(struct protocol_interface_info_entry *cur)
{
    if (!cur) {
        return;
    }

    if (cur->lowpan_info & INTERFACE_NWK_ROUTER_DEVICE) {
        if (cur->rfd_poll_info) {
            ns_dyn_mem_free(cur->rfd_poll_info);
            cur->rfd_poll_info = NULL;
        }
        mac_helper_pib_boolean_set(cur, macRxOnWhenIdle, true);
        return;
    }

    //Allocate and Init
    nwk_rfd_poll_setups_s *rfd_ptr = cur->rfd_poll_info;

    if (!rfd_ptr) {
        if (mac_data_poll_tasklet_init() < 0 ) {
            tr_error("Mac data poll tasklet init fail");
        } else {
            rfd_ptr = ns_dyn_mem_alloc(sizeof(nwk_rfd_poll_setups_s));
            if (rfd_ptr) {
                rfd_ptr->timeOutInSeconds = 0;
                rfd_ptr->nwk_parent_poll_fail = false;
                rfd_ptr->protocol_poll = 0;
                rfd_ptr->slow_poll_rate_seconds = 0;
                rfd_ptr->pollActive = false;
                cur->rfd_poll_info = rfd_ptr;
            }
        }
    }

    if (!rfd_ptr) {
        return;
    }

    //Set MAc Data poll Fail Callback
    rfd_ptr->pollFailCb = nwk_parent_poll_fail_cb;

    if (cur->mac_parameters->RxOnWhenIdle) {
        tr_debug("Set Non-Sleepy HOST");
        rfd_ptr->host_mode = NET_HOST_RX_ON_IDLE;
    } else {

        rfd_ptr->protocol_poll = 1;
        mac_poll_timer_trig(200, cur);
        tr_debug("Set Sleepy HOST configure");
        rfd_ptr->host_mode = NET_HOST_FAST_POLL_MODE;
        rfd_ptr->slow_poll_rate_seconds = 3;
        rfd_ptr->timeOutInSeconds = 32;
        rfd_ptr->nwk_app_poll_time = 300;
        rfd_ptr->nwk_parent_poll_fail = 0;
    }
}
