/*
 * Copyright (c) 2015-2018, Arm Limited and affiliates.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * 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. Neither the name of the copyright holder nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */

/*
 * \file thread_network_synch.c
 *
 */
#include "nsconfig.h"
#ifdef HAVE_THREAD
#include <string.h>
#include <ns_types.h>
#include <nsdynmemLIB.h>
#include "eventOS_event.h"
#include "eventOS_event_timer.h"
#include "randLIB.h"
#include "common_functions.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "net_thread_test.h"
#include "libDHCPv6/libDHCPv6.h"
#include "libDHCPv6/libDHCPv6_server.h"
#include "ns_trace.h"
#include "6LoWPAN/Bootstraps/protocol_6lowpan.h"
#include "6LoWPAN/Thread/thread_common.h"
#include "6LoWPAN/Thread/thread_routing.h"
#include "6LoWPAN/Thread/thread_nd.h"
#include "6LoWPAN/Thread/thread_bootstrap.h"
#include "6LoWPAN/Thread/thread_host_bootstrap.h"
#include "6LoWPAN/Thread/thread_router_bootstrap.h"
#include "6LoWPAN/Thread/thread_management_internal.h"
#include "6LoWPAN/Thread/thread_joiner_application.h"
#include "6LoWPAN/Thread/thread_management_client.h"
#include "6LoWPAN/Thread/thread_network_synch.h"
#include "6LoWPAN/Thread/thread_neighbor_class.h"
#include "thread_management_if.h"
#include "thread_config.h"
#include "Common_Protocols/ipv6.h"
#include "Common_Protocols/icmpv6.h"
#include "Common_Protocols/icmpv6_radv.h"
#include "MLE/mle.h"
#include "Service_Libs/mac_neighbor_table/mac_neighbor_table.h"
#include "6LoWPAN/MAC/mac_helper.h"

#define TRACE_GROUP "tsyn"

typedef struct thread_sync_child_info {
    uint32_t mle_frame_counter;
    uint32_t mac_frame_counter;
    uint8_t long_addr[8];
    uint16_t short_addr;
    uint8_t mode;
} thread_sync_child_info_t;

typedef struct thread_network_dynamic_data_store {
    thread_leader_info_t *leader_private_data;
    thread_parent_info_t *thread_endnode_parent;
    thread_attach_state_e thread_attached_state;
    uint16_t shortAddress;
    thread_sync_child_info_t children[THREAD_MAX_CHILD_COUNT];
} thread_network_dynamic_data_store_t;


typedef struct thread_network_dynamic_data_entry {
    int8_t interfaceId;
    ns_list_link_t link; /*!< List link entry */
    thread_network_dynamic_data_store_t networ_dynamic_data_parameters;
} thread_network_dynamic_data_entry_t;

static thread_sync_child_info_t *thread_dynamic_storage_free_child_find(int8_t interface_id);
static thread_sync_child_info_t *thread_dynamic_storage_child_info_find(int8_t interface_id, mac_neighbor_table_entry_t *child);
static NS_LIST_DEFINE(thread_network_dynamic_data_info, thread_network_dynamic_data_entry_t, link);

thread_network_dynamic_data_entry_t *thread_network_synch_create(int8_t interfaceId)
{
    thread_network_dynamic_data_entry_t *newEntry = ns_dyn_mem_alloc(sizeof(thread_network_dynamic_data_entry_t));
    if (newEntry) {
        tr_debug("Allocate New Store");
        newEntry->interfaceId = interfaceId;
        newEntry->networ_dynamic_data_parameters.thread_attached_state = THREAD_STATE_NETWORK_DISCOVER;
        newEntry->networ_dynamic_data_parameters.shortAddress = 0xfffe;
        newEntry->networ_dynamic_data_parameters.leader_private_data = NULL;
        newEntry->networ_dynamic_data_parameters.thread_endnode_parent = NULL;
        for (int i = 0; i < THREAD_MAX_CHILD_COUNT; ++i) {
            memset(&newEntry->networ_dynamic_data_parameters.children[i], 0, sizeof(thread_sync_child_info_t));
        }
        ns_list_add_to_end(&thread_network_dynamic_data_info, newEntry);
    }
    return newEntry;
}

thread_network_dynamic_data_entry_t *thread_network_synch_find(int8_t interfaceId)
{
    //Check current  List
    ns_list_foreach(thread_network_dynamic_data_entry_t, cur, &thread_network_dynamic_data_info) {
        if (interfaceId == cur->interfaceId) {
            return cur;
        }
    }
    return NULL;
}

int thread_network_synch_delete(int8_t interfaceId)
{
    thread_network_dynamic_data_entry_t *newEntry = thread_network_synch_find(interfaceId);
    if (newEntry) {
        //Free
        if (newEntry->networ_dynamic_data_parameters.leader_private_data) {
            ns_dyn_mem_free(newEntry->networ_dynamic_data_parameters.leader_private_data);
        }

        if (newEntry->networ_dynamic_data_parameters.thread_endnode_parent) {
            ns_dyn_mem_free(newEntry->networ_dynamic_data_parameters.thread_endnode_parent);
        }

        newEntry->networ_dynamic_data_parameters.thread_attached_state = THREAD_STATE_NETWORK_DISCOVER;
        newEntry->networ_dynamic_data_parameters.shortAddress = 0xfffe;
        newEntry->networ_dynamic_data_parameters.leader_private_data = NULL;
        newEntry->networ_dynamic_data_parameters.thread_endnode_parent = NULL;

        return 0;
    }
    return -1;
}

//Call This when clean synched networkdata
int thread_network_synch_data_free(int8_t interface_id)
{
    return thread_network_synch_delete(interface_id);
}

/*
 * Dynamic network data storage.
 */

void thread_dynamic_storage_child_info_store(protocol_interface_info_entry_t *cur_interface, mac_neighbor_table_entry_t *child)
{
    thread_network_dynamic_data_entry_t *storeEntry = thread_network_synch_find(cur_interface->id);

    if (!storeEntry) {
        storeEntry = thread_network_synch_create(cur_interface->id);
    }

    if (!storeEntry) {
        return;
    }

    uint32_t mle_frame_counter = mle_service_neighbor_frame_counter_get(cur_interface->id, child->index);

    thread_sync_child_info_t *child_info = thread_dynamic_storage_child_info_find(cur_interface->id, child);
    if (!child_info) {
        child_info = thread_dynamic_storage_free_child_find(cur_interface->id);
    }

    if (child_info) {
        uint8_t mode = mle_mode_write_from_mac_entry(child);
        mode |= thread_neighbor_class_mode_write_from_entry(&cur_interface->thread_info->neighbor_class, child->index);

        child_info->mode = mode;
        child_info->short_addr = child->mac16;
        child_info->mle_frame_counter = mle_frame_counter;
        child_info->mac_frame_counter = 0;
        memcpy(child_info->long_addr, child->mac64, 8);
        return;
    }
    return;
}

void thread_dynamic_storage_child_info_clear(int8_t interface_id, struct mac_neighbor_table_entry *child)
{
    thread_sync_child_info_t *child_info = thread_dynamic_storage_child_info_find(interface_id, child);

    if (child_info){
        // Clear child information
        memset (child_info,0,sizeof(thread_sync_child_info_t));
        tr_debug("Dynamic storage: cleared child; mac16=%04x", child->mac16);
        return;
    }
    return;
}

static thread_sync_child_info_t *thread_dynamic_storage_child_info_find(int8_t interface_id, mac_neighbor_table_entry_t *child)
{
    thread_network_dynamic_data_entry_t *storeEntry = thread_network_synch_find(interface_id);

    if (!storeEntry) {
        return NULL;
    }

    for (uint16_t i = 0; i < THREAD_MAX_CHILD_COUNT; ++i) {
        uint8_t *mac64 = storeEntry->networ_dynamic_data_parameters.children[i].long_addr;
        if (memcmp(mac64, child->mac64, 8) == 0) {
            return &storeEntry->networ_dynamic_data_parameters.children[i];
        }
    }
    return NULL;
}

static thread_sync_child_info_t *thread_dynamic_storage_free_child_find(int8_t interface_id)
{
    thread_network_dynamic_data_entry_t *storeEntry = thread_network_synch_find(interface_id);
    if (!storeEntry) {
        return NULL;
    }
    for (int i = 0; i < THREAD_MAX_CHILD_COUNT; ++i) {
        if (storeEntry->networ_dynamic_data_parameters.children[i].short_addr == 0) {
            return &storeEntry->networ_dynamic_data_parameters.children[i];
        }
    }
    return NULL;
}

void thread_dynamic_storage_build_mle_table(int8_t interface_id)
{
    tr_debug("Dynamic storage: building MLE table.");
    protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id);
    if (!cur || !cur->mac_parameters) {
        return;
    }

    thread_network_dynamic_data_entry_t *storeEntry = thread_network_synch_find(interface_id);
    bool new_entry_created;

    if (!storeEntry) {
        storeEntry = thread_network_synch_create(interface_id);
    }

    if (!storeEntry) {
        return;
    }

    mac_neighbor_table_list_t *mac_table_list = &mac_neighbor_info(cur)->neighbour_list;

    if (!mac_table_list) {
        return;
    }

    int i;
    for (i = 0; i < THREAD_MAX_CHILD_COUNT; ++i) {
        if (storeEntry->networ_dynamic_data_parameters.children[i].short_addr == 0) {
            // Skip empty child entry
            continue;
        }
        uint8_t *mac64 = storeEntry->networ_dynamic_data_parameters.children[i].long_addr;
        tr_debug("Child: %04x, %s", storeEntry->networ_dynamic_data_parameters.children[i].short_addr, trace_array(mac64, 8));

        mac_neighbor_table_entry_t *mac_entry = mac_neighbor_entry_get_by_mac64(mac_neighbor_info(cur), mac64, true, &new_entry_created);
        if (mac_entry) {

            mac_entry->mac16 = storeEntry->networ_dynamic_data_parameters.children[i].short_addr;
            mle_service_frame_counter_entry_add(interface_id, mac_entry->index, storeEntry->networ_dynamic_data_parameters.children[i].mle_frame_counter);
            mle_mode_parse_to_mac_entry(mac_entry, storeEntry->networ_dynamic_data_parameters.children[i].mode);

            // Set MAC layer frame counter for the child
            mlme_device_descriptor_t device_desc;
            thread_neighbor_class_update_link(&cur->thread_info->neighbor_class, mac_entry->index,64, new_entry_created);
            thread_neighbor_class_mode_parse_to_entry(&cur->thread_info->neighbor_class, mac_entry->index,storeEntry->networ_dynamic_data_parameters.children[i].mode);
            thread_neighbor_last_communication_time_update(&cur->thread_info->neighbor_class, mac_entry->index);
            mac_helper_device_description_write(cur, &device_desc, mac_entry->mac64, mac_entry->mac16,storeEntry->networ_dynamic_data_parameters.children[i].mac_frame_counter, false);
            mac_helper_devicetable_set(&device_desc, cur, mac_entry->index, cur->mac_parameters->mac_default_key_index, new_entry_created);
        }
    }
}
#endif
